做自己的chatGPT 打造自己的chat gpt 現在我們已經學會了openai api的用法接著就可以利用open ai api製作1個簡易的chat gpt程式 除了建構對答流程外,也會設計簡易的聊天歷史紀錄機制,以及效仿chatGPT的串流式輸出效果。 我們一樣先建立環境變數檔 .env檔: !pip install python-dotenv from dotenv import load_dotenv load_dotenv() 文字模式簡易聊天模式 這一小節我們會專注在聊天程式的流程,因此請先執行 以下,儲存格將與openai模組互動的部分獨立成get_reply()函式,方便稍後撰寫程式: def get_reply(messages): try: response = openai.ChatCompletion.create( model = "gpt-3.5-turbo", messages = messages ) reply = response["choices"][0]["message"]["content"] except openai.OpenAIError as err: reply = f"發生 {err.error.type} 錯誤\n{err.error.message}" return reply 這個函式很簡單,只要傳入訊息串列它就會送給模型並且從模型的回覆中取得文字內容 後返回。他同時也加上了簡易的錯誤處理,在發生錯誤時,傳回錯誤類型與原因組成的文字 接著,我們可以利用簡單的迴圈,設計1個文字模式的聊天程式,請執行下一個儲存格: while True: msg = input("你說:") if not msg.strip(): break messages = [{"role":"user", "content":msg}] reply = get_reply(messages) print(f"ElonMa:{reply}\n") 這個程式利用input函式接收輸入語句,並且會在直接按enter或是輸入資料是空白時跳出迴圈停止聊天,你可以跟他聊看看。 程式中幫ai模型取了個匿名,叫ElonMa 你說: 請介紹什麼是極光 你可能已經發現到雖然可以對答聊天,不過因為模型並沒有對答過程的資料,所以每次問答都是獨立的,像是在本例中,雖然一開始問他有關極光的主題,但第二次詢問時,他並沒有依循前面的脈絡,完全不知道我們問的是日本是否可以看到極光。 為了讓聊天程式可以保持脈絡,就要如同在之前的playground測試時所說明的,必須將對答過程也送回去給模型才能,讓模型擁有記憶適當的回答。 加入聊天紀錄,維持聊天脈絡: 以下我們預計簡單的使用串列來記錄對答過程。我們在原本的get_reply()函式外再包裝一層,加上 記錄對答過程的功能。請執行以下的儲存格定義這個新的函式chat()函式: hist = [] # 歷史對話紀錄 backtrace = 2 # 記錄幾組對話 def chat(sys_msg, user_msg): hist.append({"role":"user", "content":user_msg}) reply = get_reply(hist + [{"role":"system", "content":sys_msg}]) while len(hist) >= 2 * backtrace: # 超過記錄限制 hist.pop(0) # 移除最舊紀錄 hist.append({"role":"assistant", "content":reply}) return reply 第一行使用串列紀錄對答過程 第二行這裡假設只會記錄兩組對打過程,也就是問答兩次的內容,如果你希望模型可以記得更多,可自行修改這個數 第四行多加了一個參數,可以指定系統訊息的內容 第五行會把最新輸入的使用者訊息加入歷史紀錄, 第六至七行將目前的歷史紀錄,加上系統訊息後,送給模型,並取得回覆。 第八~十行將剛剛取得的回覆加入歷史記錄中,必要時會把最舊的訊息從歷史記錄中移除 定義好這個函示後,我們也順便修改一下聊天程式,請執行下1個儲存格使用會議紀錄,對答過程的聊天程式: sys_msg = input("你希望ElonMa扮演:") if not sys_msg.strip(): sys_msg = '小助理' print() while True: msg = input("你說:") if not msg.strip(): break reply = chat(sys_msg, msg) print(f"{sys_msg}:{reply}\n") hist = [] 利用這個簡單的機制,我們的聊天程式就不再應該是金魚腦,會記得剛剛聊過了什麼,現在讓我們再試看看: input中輸入:某個角色 利用這個簡單的做法,就可以讓模型擁有記憶不過還是要提醒的是,想要記得多可以調整backtrace的數值但相對的,傳送的提示內容也會變多,費用自然也會提高。 串流版本的聊天程式: 雖然目前我們已經完成了擁有記憶,可以維持脈絡的聊天程式,不過在測試時,我們可能會發現等待的時間會有點久(因為記錄歷史資訊,又跑了一些迴圈的關係),那這裡我們準備套用一種串流方式小技巧=> 生成器的應應(generator),讓使用者很快能夠看到AI的回應。 Tip: 使用串流方式時會以生成器(generator)的方式循序傳回片段內容給我們,因此就必須搭配同樣的方式,讓剛剛的get_reply() function函式也採用生成器來傳回內容,請執行下1個儲存格定義串流版本的get_reply_s()函式: def get_reply_s(messages): try: response = openai.ChatCompletion.create( model = "gpt-3.5-turbo", messages = messages, stream = True ) for chunk in response: if 'content' in chunk['choices'][0]['delta']: yield chunk["choices"][0]["delta"]["content"] except openai.OpenAIError as err: reply = f"發生 {err.error.type} 錯誤\n{err.error.message}" 由於更改,成生成器的方式因此函式的使用方式也要跟著改變,請執行下1個儲存格進行測試: for reply in get_reply_s([{ #以for迴圈一一取得生成內容 "role":"user", "content":"請介紹台北市" }]): print(reply, end=''). #因為要將生成片段皆在一起,所以不換行 print('') 上面code中執行後你可以發現到,雖然儲存格的執行扭還呈現轉轉圈圈的狀態,但是已經開始顯示出模型的回覆內容了,讓使用者在交談過程中可以有比較好的體驗,利用同樣的方式,也將剛剛的chat函式更改成以下的chat_s()函式: hist = [] # 歷史對話紀錄 backtrace = 2 # 記錄幾組對話 def chat_s(sys_msg, user_msg): hist.append({"role":"user", "content":user_msg}) reply_full = "" for reply in get_reply_s( # 使用串流版的函式 hist + [{"role":"system", "content":sys_msg}]): reply_full += reply # 記錄到目前為止收到的訊息 yield reply # 傳回本次收到的片段訊息 while len(hist) >= 2 * backtrace: # 超過記錄限制 hist.pop(0) # 移除最舊紀錄 hist.append({"role":"assistant", "content":reply_full}) 最後再將對答流程改為使用上述生成器版本的chat_s()函式: sys_msg = input("你希望ㄟ唉扮演:") if not sys_msg.strip(): sys_msg = '小助理' print() while True: msg = input("你說:") if not msg.strip(): break print(f"{sys_msg}:", end = "") for reply in chat_s(sys_msg, msg): print(reply, end = "") print('\n') hist = [] 這樣就可以設計出,不讓使用者癡癡等後的聊天程式了。 儲存歷史紀錄下次繼續聊 現在我們已經有了聊天程式的基本雛型,唯一還欠缺的就是聊天歷史紀錄在程式結束後就消失了。 以colab環境來說如果,中斷執行階段重新,執行就無法接續前一次聊天脈絡,不過由於我們的聊天歷史資料結構相當簡單,所以也可以利用python的pickle模組把記錄聊天歷史的串列與系統訊息儲存到檔案中,下次執行時,就可以從檔案讀取復原成python中的物件。 掛階google雲端硬碟 在之前曾提過colab中的儲存的檔案會在階段執行階段結束後消失就像是使用系統還原一樣,下次重新啟動執行階段,執行筆記本時就看不到了,為了讓歷史記錄存檔後,在新的執行階段也可以取用,我們採取的方式是存擋到google雲端要讓colab中的python程式可以取用google雲端硬碟必須先將雲端硬碟掛階到目前的colab虛擬機器中,請執行下一個儲存格以掛接google雲端: from google.colab import drive drive.mount('/content/drive') 要注意的是,雲端硬碟會出現在上述掛階路徑下的MyDrive資料夾內這個資料夾才是你的雲端硬碟的“根資料夾”。稍後我們就會把歷史記錄儲存到這個根資料夾下的hist.dat. 製作復原/儲存歷史紀錄的函式 現在就可以存取雲端硬碟中的檔案了首先,執行以下的儲存格匯入pickle模組: import pickle 接著執行下一個儲存格定義,將歷史紀錄與系統訊息組成一個“字典”後,存到檔案中的save_hist()函式: def save_hist(hist, sys_msg): try: with open('/content/drive/MyDrive/hist.dat', 'wb') as f: db = { 'hist': hist, 'sys_msg': sys_msg } pickle.dump(db, f) except: # 歷史檔開啟錯誤 print('無法寫入歷史檔') save_hist(hist, sys_msg) —> 執行看看 接著再,執行下一個儲存格定義檔案“讀取”內容復原成、歷史紀錄串列以及系統訊息的load_hist()函式。 有了這兩個函式之後就可以在聊天流程的前後加上復原力,使紀錄以及儲存歷史紀錄的動作,請執行下一個儲存格code測試: def load_hist(): try: with open('/content/drive/MyDrive/hist.dat', 'rb') as f: db = pickle.load(f) return db['hist'], db['sys_msg'] except: # 歷史檔不存在 print('無法開啟歷史檔') return [], '' load_hist() #—> 執行看看 接著就可以來試看看歷史紀錄存檔的功能: … 結束對話後,如果重新執行這個儲存格,就可以接續,剛剛的對話: 你也可以試看看執『行執行階段/中斷連線並刪除執行階段』功能表指令,等於是是進入還原系統重新開機,再重新執行聊天程式,看看聊天程式是不是還記得之前聊過的內容 tip:記得刪除執行階段後剛剛執行的內容就全部不見了所以要從頭執行,設定環境變數、匯入open ai的模組定義相關函式以及連線雲端硬碟的儲存格,否則一執行應該就會因為沒有定義的函式或名稱就出錯了。 如果想要把聊天記錄檔案刪除,可以在檔案窗格中展開路徑,drive/MyDrive/hist.dat 在hist.dat按右鍵執行刪除,檔案功能指令 本節我們用最簡單的方式帶大家實作文字版的chatGPT 在下一節我們還要挑戰突破chatGPT的時空限制讓ai發揮更大的潛能。 tip:在後續的課程中為了簡化程式都不會將聊天記錄存檔,你可以自行嘗試這個功能,在 之後的課程範例當中。