# Chapter6. 讓 AI 幫 AI - 自動串接流程 :+1: 完整程式碼在 https://github.com/iamalex33329/chatgpt-develop-guide-zhtw ## 其他章節 [Chapter1. OpenAI API 入門](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/S1cNMYi6T) [Chapter2. 使用 Python 呼叫 API](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/HyZBg5ia6) [Chapter3. API 參數解析與錯誤處理](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/BJWNtsh6p) [Chapter4. 打造自己的 ChatGPT](https://hackmd.io/@112356044/Hk81U96Tp) [Chapter5. 突破時空限制 - 整合搜尋功能](https://hackmd.io/@112356044/HkbVM-ApT) [Chapter7. 網頁版聊天程式與文字生圖 Image API](https://hackmd.io/@112356044/Hyf-AvgAT) [Chapter8. 設計 LINE AI 聊天機器人](https://hackmd.io/@112356044/r1d6HsgAa) [Chapter9. 自媒體業者必看!使用 AI 自動生成高品質字幕](https://hackmd.io/@112356044/rJ2T37V0T) [Chapter10. 把 AI 帶到 Discord](https://hackmd.io/@112356044/Sy_L-B40T) [Chapter11. AI 客製化投資理財應用實戰](https://hackmd.io/@112356044/HkUE0rER6) [Chapter12. 用 LangChain 實作新書宣傳自動小編](https://hackmd.io/@112356044/SybvbdN0p) ## 目錄結構 [TOC] ## 従 ChatGPT 外掛得到的啟示 訂閱 ChatGPT Plus 的用戶除了可以使用 GPT-4 的模型之外,還有一個功能就是可以使用 **Plugin(外掛)**。使用者可以開啟需要的外掛,而 ChatGPT 會依據輸入的語句**自動判斷**要使用哪一個外掛來回答問題。 例如上一章可以透過 `/w:` 讓 openai 搜尋 Google 的結果,但差別在於我們需要主動呼叫(或是根據關鍵字),這章會模擬 Plugin 功能,讓 OpenAI API 也可以自己串接額外功能。 ### 準備工作 載入需要的套件跟匯入模組 ``` python= from googlesearch import search import openai import apikey import json openai.api_key = apikey.OPENAI_API_KEY ``` ### 搭配串流/非串流模式的工具函式 這裡結合了前幾章寫的 get_reply() 成一個函式,可以透過參數設定要使用**串流**或是**非串流**的方式回傳 ``` python=+ def get_reply(messages, stream=False): try: response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=messages, stream=stream ) if stream: # In streaming mode, use a helper function to obtain complete text for res in response: if 'content' in res['choices'][0]['delta']: yield res['choices'][0]['delta']['content'] else: # In non-streaming mode, directly yield the complete reply text yield response['choices'][0]['message']['content'] except openai.OpenAIError as err: # Handle OpenAI errors reply = f"An {err.error.type} error occurred\n{err.error.message}" print(reply) yield reply ``` 測試一下: 串流(`stream=True`) ``` python=+ for reply in get_reply([{'role': 'user', 'content': 'how are you'}], stream=True): print(reply, end='') ``` 非串流(預設為 `False`) ``` python=+ for reply in get_reply([{'role': 'user', 'content': 'how are you'}]): print(reply) ``` ## 由 AI 自動判斷要額外進行的工作 上一章從 Google 上搜尋關鍵字是簡易版本,若我們想要詢問問題如下: ``` Q1. 誰是 2024 的台灣總統? A1. /.../ Q2. 那誰是副總統? A2. /.../ ``` Q2 的部分是:`那誰是[2024 的台灣]副總統?`,但先前的程式並沒辦法依循前幾個問題的脈絡來連貫回答。 而理想做法是藉由聊天記錄和目前輸入問題來推演出搜尋關鍵字,才能得到理想答案,而這個推演的工作正是語言模型的強項,因此可以交給模型來處理。 ### 讓 AI 自行決定是否需要搜尋 我們可以直接使用提示 ``` = 如果我想知道以下這件事,請確認是否需要網路搜尋才做得到? --- ${問題} --- 如果需要,請以下列 JSON 格式回答我,除了 JSON 格式資料以外,不要加上任何額外資訊,就算已經知道答案,也不要回覆我: --- { "search": "True", "keyword": "${你建議的搜尋關鍵字}" } --- { "search": "False", "keyword": "" } ``` 若將以上的 Prompt 送到 ChatGPT 3.5,並將問題的部分設定成「誰是 2024 台灣的總統」,則會得到以下輸出: ``` = { "search": "True", "keyword": "2024 台灣 總統" } ``` ### 撰寫判斷是否需要搜尋的工具函式 我們將剛剛的 Prompt 在 Python 中寫成工具變數: ``` python= prompt = ''' 如果我想知道以下這個問題, 請確認是否需要搜尋網路才知道? --- {} --- 如果需要,請以下列 JSON 格式回答,除了 JSON 格式資料外,不要加上額外資訊,就算你知道答案,也不要回覆: --- {{ "search": true, "keyword": "你建議的搜尋關鍵字" }} --- 如果不需要,請用下列 JSON 格式回答: --- {{ "search": false, "keyword": "" }} ''' ``` 上述程式碼片段的 line 5 就是要替換的問題,接著將該變數在 check_google() 函式中,讓模型判斷是否需要搜尋網路: ``` python= def check_google(hist, msg, verbose=False): # 將歷史訊息與使用者訊息組合成一條對話紀錄 combined_hist = hist + [{ 'role': 'user', 'content': prompt.format(msg) }] # 取得回覆 reply_generator = get_reply(combined_hist) for ans in reply_generator: # 若 verbose 為 True,則印出回覆 if verbose: print(ans) # 回傳回覆 return ans ``` 接著利用下面四個搜尋範例,來看看 openai 回傳了些什麼 > 每次測試結果可能都不一樣! ``` python=+ # 測試需要搜尋的狀況 ans = check_google( [], '2023 NBA 冠軍是哪一隊?', True ) # { # 'search': 'True', # 'keyword': '2023 NBA 冠軍' # } # 測試可能不需要搜尋的狀況 ans = check_google( [], '新冠疫情是哪一年開始的?', True ) # { # 'search': 'True', # 'keyword': 'COVID-19疫情起源年份' # } # 測試沒有前文脈絡的狀況 ans = check_google( [], '那台灣呢?', True ) # { # 'search': 'N', # 'keyword': '' # } # 測試包含前文脈絡的狀況 ans = check_google( [{'role':'assistant', 'content': '印度空污好嚴重'}], '那台灣呢?', True ) # { # 'search': 'True', # 'keyword': '台灣空氣品質' # } ``` 接著執行 google_res() 讓搜尋結果組合出合適的命令語句 ``` python=+ def google_res(user_msg, num_results=5, verbose=False): content = "The following are the facts that have occurred:\n" for res in search(user_msg, advanced=True, num_results=num_results, lang='en'): content += f"Title: {res.title}\nSummary: {res.description}\n\n" content += "Please answer the following questions based on the above facts:\n" if verbose: print(f'------------\n{content}') return res = google_res(user_msg='2024台灣總統當選人', num_results=3, verbose=True) ``` ### 可自行判斷是否進行網路搜尋的聊天程式 ``` python=+ hist = [] backtrace = 2 def chat_g(sys_msg, user_msg, stream=False, verbose=False): global hist messages = [{'role': 'user', 'content': user_msg}] ans = json.loads(check_google(hist, user_msg, verbose=verbose)) if ans['search'] is True: print(f'Trying to search online: {ans["keyword"]}....') res = google_res(ans['keyword'], verbose=verbose) messages = [{'role': 'user', 'content': res + user_msg}] reply_full = '' replies = get_reply( hist + messages + [{"role": "system", "content": sys_msg}], stream ) for reply in replies: reply_full += reply yield reply hist += [ {"role": "user", "content": user_msg}, {"role": "assistant", "content": reply_full} ] while len(hist) >= 2 * backtrace: hist.pop(0) ``` 1. `hist` 是用來儲存對話歷史紀錄的列表。每一條對話都是以字典的形式存儲,包含角色(用戶或助手)和對應的訊息內容。 2. `backtrace` 變數是用來設置要保留多少組對話紀錄。如果紀錄的對話超過這個數量,則會刪除最舊的對話紀錄。 3. `chat_g` 函式是對話的主要處理函式。它接收系統訊息(sys_msg)、用戶訊息(user_msg)、是否使用串流模式(stream),以及是否要輸出詳細資訊(verbose)等參數。 4. 函式中首先建立了一個用戶訊息的列表 `messages`,並將用戶最新的訊息加入其中。 5. 接著,通過調用 `check_google` 函式,獲取關於用戶訊息的相關資訊。如果需要透過網路搜索,則調用 `google_res` 函式獲取搜索結果。 6. 接下來,使用 `get_reply` 函式獲取對話回覆。這個函式用於獲取助手的回應,它根據過去的對話歷史、用戶訊息和系統訊息生成助手的回覆。在取得回覆後,將回覆內容存儲在 `reply_full` 變數中。 7. 最後,將新的對話紀錄添加到歷史紀錄中,包括用戶訊息和助手的回覆。如果歷史紀錄數量超過了設定的 `backtrace` 數量,則刪除最舊的對話紀錄。 ``` python= if __name__ == '__main__': sys_msg = 'Assistance' while True: msg = input('=> ') if not msg.strip(): break for reply in chat_g(sys_msg, msg, stream=False): print(reply, end='') print('\n') ``` 一些執行結果 Demo ``` = => 誰是台灣2024的新總統 Trying to search online: 2024 台灣 總統 候選人.... 根據上述事實,台灣2024年的新總統是賴清德。 => COVID-19 是從西元幾年開始的? COVID-19疫情最初於2019年開始爆發,因此可以說是在西元2019年開始的。 => GPTScript 是一個怎樣的套件? Trying to search online: GPTScript.... 根據上述事實,GPTScript是一個新的腳本語言,可用於自動化與大型語言模型(LLM),特別是OpenAI進行互動。該項目的最終目標是創建一種自然語言編程工具,該工具基於LLM所設計,可以用於情感分析等用途。具有情感分析功能的GPTScript工具是該項目的一部分。這個新的程式設計模型旨在圍繞LLM設計,該項目似乎是從Darren Shepherd提出的。另外,該專案還包括了一些有關圖像的功能和資訊。 => 第34屆金曲獎最佳華語男歌手獎是誰? Trying to search online: 第34屆金曲獎 最佳華語男歌手獎.... 第34屆金曲獎最佳華語男歌手獎由HUSH奪得。 ``` ## 可建構外掛系統的 Function Calling 機制 上一節的程式雖然可以讓模型自動判斷要不要使用網路搜索功能,但有幾個**問題**: 1. 如果 Prompt 設計不良,例如模型沒有如實傳回 JSON 格式,則 Python 後續處理資料時就會產生錯誤。 2. 不同人提供的工具會有不同的回覆格式,因此程式碼過於複雜,容易出錯! > 為了幫助開發協助語言模型使用**外部工具**的能力,OpenAI API 推出 function calling 的機制。 程式可以告訴語言模型有哪些功能的**外部工具函式**可以使用,由模型判斷並告知是否要使用某一函式,程式再去呼叫該函式,並傳回執行結果給模型做最後回覆。 流程如下: 1. 使用 API 傳送對話時,透過 `function` 以統一的語法告訴模型有哪些外部函式工具可以用。傳遞的內容包含: - 工具**名稱** - **功能**說明 - 參數的**資料型別**以及**用途** 2. 語言模型根據對話內容(Prompt)以及提供的外部工具,判斷是否需要外部函式的協助,若需要,則會在訊息回覆中以 `function_call` 告知要呼叫的**函式**以及**參數**。 3. 由程式依據回覆內容呼叫對應函式,並將執行結果以 function 角色的訊息傳回給模型。 4. 模型綜合 function 角色以及 user 角色的訊息回覆。 ### 告知語言模型可用的外部工具函式 以上一節的 `google_res()` 為例,讓模型知道有提供 google 搜尋的外部工具函式可以使用 ``` python= response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[{ "role": "user", "content": "2023 Golden Melody Awards winner?" }], functions=[{ # 可用的函式清單 "name": "google_res", # 函式名稱 "description": "Get Google search results", # 函式說明 "parameters": { "type": "object", "properties": { "user_msg": { "type": "string", "description": "Keyword to search" # 要搜尋的關鍵字 } }, "required": ["user_msg"], # 必要參數 }, }], function_call="auto", # 讓 AI 判斷是否需要叫用函式 ) ``` > `function` 參數必須傳入 `list()`,每個元素對應一個函式的描述,呼叫時需要名稱為 `user_msg` 的參數,傳入要搜尋的關鍵字 可使用的型別如下 | Type | Python Type | Description | | -------- | ----------- | ----------- | | string | str | 字串 | | number | int/float | 數值 | | object | dict | 字典 | | array | list | 串列 | | boolean | bool | 布林 | | null | None | 空值 | ### 取得語言模型的建議 語言模型會在取得傳入的資訊與外部工具函式之後,判斷是否要使用特定的工具函式 ``` python=+ print(response) ``` ``` json { "id": "chatcmpl-92dUQP8gVM9L332shUznvSvEDGfxN", "object": "chat.completion", "created": 1710415482, "model": "gpt-3.5-turbo-0125", "choices": [ { "index": 0, "message": { "role": "assistant", "content": null, "function_call": { "name": "google_res", "arguments": "{\"user_msg\":\"2023 Golden Melody Awards winner\"}" } }, "logprobs": null, "finish_reason": "function_call" } ], "usage": { "prompt_tokens": 58, "completion_tokens": 21, "total_tokens": 79 }, "system_fingerprint": "fp_4f0b692a78" } ``` 1. `message` 裡的 `content` 是 null,代表這不是實際回覆的內容 2. `message` 中多了一個 `function_call`。他的內容就是語言模型建議的工具函式 3. `finish` 中可以看到不是常見的 `stop` 或是 `length`,而是 `function_call` 我們可以將需要的資訊取出: ``` python=+ func_call = response['choices'][0]['message']['function_call'] func_name = func_call["name"] args = json.loads(func_call["arguments"]) arg_val = args.popitem()[1] print(f'{func_name}("{arg_val}")') # OUTPUT: # google_res("2023 Golden Melody Awards winner") ``` 這樣就知道只要我們執行 `google_res("2023 Golden Melody Awards winner")` 就可以得到答案了! ### 執行函式並傳回結果 > 語言模型不會自動幫我們執行函式,在收到建議後,必須自行執行函式,並傳回結果 ``` python=+ response = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {"role": "user", "content": "2023 Golden Melody Awards winner?"}, # 返回 AI 傳給我們的 function calling 結果 response["choices"][0]["message"], { # 以 function 角色加上 name 屬性指定函式名稱傳回執行結果 "role": "function", "name": func_name, "content": eval(f'{func_name}("{arg_val}")') } ] ) print(response['choices'][0]['message']['content']) # OUTPUT: # The winners of the 2023 Golden Melody Awards include A-Lin for Best Female Mandarin Singer and HUSH for Best Male Singer (Mandarin). ``` ### 以串流方式使用 function calling > 省略... ## 建立 API 外掛系統 上一節使用個別步驟來練習 function calling,這一節要將所有步驟自動化串接起來,建立簡單的外掛系統。 之後只需要加入外部工具函式,技能快速提供模型的功能。 ### 建議外部工具函式參考表 這裡需要把外部工具函式的描述**制式化** ``` python=+ func_table = [ { # 每個元素代表一個函式 "chain": True, # 函式執行結果是否要再傳回給 API "func": google_res, # 函式本身 "spec": { # 函式的規格 "name": "google_res", # 函式名稱 "description": "Get Google search results", # 函式說明 "parameters": { # 函式參數 "type": "object", "properties": { "user_msg": { # 參數名稱 "type": "string", # 參數類型 "description": "Keyword to search", # 參數說明 } }, "required": ["user_msg"], # 必要參數 }, } } ] ``` - `chain`:是否需要在執行函式後,再呼叫一次 API 傳回函式執行結果。譬如 `google_res()`,使用這個函式就是要取得結果,因此這裡是 `True`。 - `spec`:是實際要透過 functions 傳給模型的函式描述。 ### 建立協助 function calling 的工具函式 這裡定義了一個名為 `call_func()` 的函式,用來自動呼叫其他函式並傳回結果。 ``` python=+ def call_func(func_call): func_name = func_call['name'] # 取得要呼叫的函式名稱 args = json.loads(func_call['arguments']) # 取得函式的參數內容 for f in func_table: # 在函式表格中尋找符合函式名稱的項目 if func_name == f['spec']['name']: print(f"Attempting to call: {func_name}(**{args})") # 印出正在嘗試呼叫的函式 val = f['func'](**args) # 使用函式表格中的對應函式呼叫函式並傳入參數 return val, f['chain'] # 回傳呼叫函式的結果以及是否需要再次呼叫的標誌 return '', False # 如果找不到對應的函式,則回傳空字串和 False ``` 而我們再定義一個 `get_func_call()` 函式,他會根據 API 的回覆,來判斷是否建議呼叫函式還是一般的回覆,如果是**呼叫函式**,則會取出 function_call 的部分來傳回。 ``` python=+ def get_func_call(messages, stream=False, func_table=None, **kwargs): model = 'gpt-3.5-turbo' # 預設使用的模型版本 if 'model' in kwargs: # 如果使用者指定了模型版本,則使用指定的模型版本 model = kwargs['model'] funcs = {} # 初始化函式字典 if func_table: # 如果提供了函式表格,則將函式表格中的函式規格加入到 funcs 中 funcs = {'functions': [f['spec'] for f in func_table]} # 呼叫 ChatCompletion API 以獲取回應 response = openai.ChatCompletion.create( model=model, messages=messages, stream=stream, **funcs ) if stream: # 如果使用串流模式 chunk = next(response) # 獲取串流中的下一個回應 delta = chunk["choices"][0]["delta"] # 獲取回應中的變化部分 if 'function_call' in delta: # 如果變化部分中包含 function_calling func_call = delta['function_call'] # 獲取 function_calling 的內容 args = '' # 初始化參數字串 for chunk in response: # 從串流中獲取下一個回應 delta = chunk["choices"][0]["delta"] # 獲取回應中的變化部分 if 'function_call' in delta: # 如果變化部分中包含 function_calling args += delta['function_call']['arguments'] # 將參數內容加入到參數字串中 func_call['arguments'] = args # 將參數字串加入到 function_calling 中 return func_call, None # 返回 function_calling 和空值 else: # 如果不是串流模式 msg = response["choices"][0]["message"] # 獲取回應中的訊息部分 if 'function_call' in msg: # 如果訊息部分中包含 function_calling return msg['function_call'], None # 返回 function_calling 和空值 return None, response # 如果沒有找到 function_calling,則返回空值和回應 ``` 這個函式的作用是從 API 回傳的內容中找出 `function_calling` 的部分,並將其提取出來。如果是串流模式,則需要從多個回應中提取相關信息。 ### 建立 function_calling 版的 get_reply_f() 函式 這裡定義了一個名為 get_reply_f 的函式,用來處理從 API 回傳的訊息並回覆給使用者 ``` python=+ def get_reply_f(messages, stream=False, func_table=None, **kwargs): try: # 從 API 回傳的訊息中獲取 function_calling 的內容以及回應本身 func_call, response = get_func_call(messages, stream, func_table, **kwargs) if func_call: # 如果有 function_calling 的內容 # 呼叫函式並獲取函式執行結果以及是否需要將結果傳回給 API 再回覆 res, chain = call_func(func_call) if chain: # 如果需要將函式執行結果送回給 API 再回覆 # 準備訊息列表,將原本的 function_calling 的內容和函式執行結果加入其中 messages += [ { "role": "assistant", # 助手角色 "content": None, # 內容為空 "function_call": func_call # 傳回原本的 function_calling 的內容 }, { "role": "function", # 函式角色 "name": func_call['name'], # 傳回函式名稱 "content": res # 傳回函式執行結果 } ] # 遞迴呼叫自身,處理新的訊息並繼續尋找 function_calling yield from get_reply_f(messages, stream, func_table, **kwargs) else: yield res # 直接回覆函式執行結果 elif stream: # 如果是串流模式 for chunk in response: if 'content' in chunk['choices'][0]['delta']: # 獲取回應中的內容部分 yield chunk['choices'][0]['delta']['content'] # 回覆內容 else: # 如果不是串流模式 yield response['choices'][0]['message']['content'] # 回覆整個訊息的內容 except openai.OpenAIError as err: # 處理 OpenAI 錯誤 reply = f"An {err.error.type} error occurred.\n{err.error.message}" print(reply) yield reply # 回覆錯誤訊息 ``` 這個函式的作用是從 API 回傳的訊息中提取 `function_calling` 的內容,然後根據函式的執行結果來回覆用戶。 如果函式需要將執行結果傳回給 API 再回覆,則會進行遞迴呼叫,直到執行結果不需要再傳回給 API 為止。 ``` python=+ # 測試非串流方式呼叫 function_calling for chunk in get_reply_f([{'role': 'user', 'content': '2023 金曲歌后是誰?'}], func_table=func_table): print(chunk) ``` ``` python=+ # 測試串流方式呼叫 function_calling for chunk in get_reply_f([{"role":"user", "content":"2023 金曲歌后是誰?"}], stream=True, func_table=func_table): print(chunk, end='') ``` ### 建立 function calling 版本的 chat_f() 函式 ``` python=+ hist = [] # 歷史對話紀錄 backtrace = 2 # 記錄幾組對話 def chat_f(sys_msg, user_msg, stream=False, **kwargs): global hist # 使用函式功能版的函式來處理對話 replies = get_reply_f( hist + [{"role": "user", "content": user_msg}] + [{"role": "system", "content": sys_msg}], stream, func_table, **kwargs) reply_full = '' # 初始化回覆結果的字串 for reply in replies: reply_full += reply # 將每個回覆串連成一個完整的回覆內容 yield reply # 回傳每個回覆內容 # 將對話紀錄加入到 hist 中,包括用戶訊息、助手回覆和完整對話回覆 hist += [ {"role": "user", "content": user_msg}, {"role": "assistant", "content": reply_full} ] # 當 hist 的對話紀錄超過設定的回溯次數時,移除最舊的對話紀錄 while len(hist) >= 2 * backtrace: hist.pop(0) # 移除最舊的紀錄 ``` 這個函式的作用是進行對話。它將系統訊息、用戶訊息和歷史對話紀錄作為參數,並使用 `get_reply_f()` 函式來處理對話,獲取對應的回覆。 然後,將每個回覆串連成一個完整的回覆結果,並回傳給使用者。最後,將用戶訊息、助手回覆和完整對話回覆加入到歷史對話紀錄中,並根據設定的回溯次數來移除最舊的對話紀錄,以節省 tokens 的費用。 ``` python=+ sys_msg = 'Assistance' print() while True: msg = input("=> ") if not msg.strip(): break for reply in chat_f(sys_msg, msg, stream=True): print(reply, end="") print('\n') # OUTPUT: # => 2024的台灣總統是誰? # 2024年的台灣總統目前還無法確定,因為這是未來的事件。如果您想了解目前2024年的台灣總統是誰,我可以幫您查詢一下。您需要我查詢嗎? # => 2023年的金曲獎歌王是誰? # Attempting to call: google_res(**{'user_msg': '2023年金曲獎歌王是誰'}) # 根據2023年金曲獎的得獎名單,歌王是HUSH。 ``` > 可以看到有時候還是會出錯(Q1) ## 迭代式 function-calling 機制 ### gpt-4 模型真的比較厲害 有時候輸入的問題可能需要多次的網路搜尋,才能得到品質較好的答案,這時使用 gpt-4 可以明顯感覺出能力的差異。 ### 驗證答案必要時強制進行第二輪 為了解決上面遇到的情況,我們可以讓模型判斷目前的回覆**是否已經是完整解答**,如果不完整,就下達讓模型**接續**的指令。 以下修改 `chat_f()`,讓模型認為沒有回答完整的話,會將歷史紀錄再次送回給模型處理。 首先準備讓模型可以回覆 'Y' 或是 'N' 的內容 ``` python= verify_msg = { 'role': 'user', 'content': '如果之前是問句,且剛剛的回答內容確實已經回答了問題,請回覆 ${Y} 否則回覆 ${N},只要單一字母,不要加任何其他說明。' } ``` 接著將 `chat_f()` 放入原本的迴圈中,如果有傳入具名參數 `verify`,而且為 `True`,就會請模型進一步判斷是否為完整回覆,若不完整,就將歷史紀錄繼續餵給模型。 ``` python=+ def chat_v(sys_msg, user_msg, stream=False, **kwargs): global hist verify = kwargs.get('verify', False) # 檢查是否有提供驗證訊息 while True: replies = get_reply_f( hist + [{'role': 'user', 'content': user_msg}] + [{'role': 'system', 'content': sys_msg}], stream, func_table, **kwargs) reply_full = '' for reply in replies: reply_full += reply yield reply hist += [ {'role': 'user', 'content': user_msg}, {'role': 'assistant', 'content': reply_full} ] if not verify: break for reply in get_reply_f(hist + [verify_msg]): pass print(f'(已完成:{reply})') if reply == 'Y': break user_msg = '繼續' while len(hist) >= backtrace * 2: hist.pop(0) ``` ``` python=+ hist = [] 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_v( sys_msg, msg, stream=True, model='gpt-4', verify=True, debug=True ): print(reply, end = "") print('\n') ``` > 有時候可能會遇到模型誤判答案不完整的情況,你可以設定一些接續處理的次數,防止模型無止盡的回覆,甚至造成當機!