# 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')
```
> 有時候可能會遇到模型誤判答案不完整的情況,你可以設定一些接續處理的次數,防止模型無止盡的回覆,甚至造成當機!