chatgpt tools 呼叫
1. 引入 openai, 建立 client
import json
import os
import subprocess
from openai import OpenAI
# api_key 可以填入自己的key
# base_url 可以使用國內的代理,海外可以使用官方地址
client = OpenAI(api_key="", base_url="https://api.openai-proxy.com/v1")
2. 建立一個 tool 的實現
這裡定義的是一個執行系統命令的方法, 接受傳入的命令,返回執行結果與執行錯誤
def do_command(command: str):
process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8")
stdout, stderr = process.communicate()
return {"stdout": stdout, "stderr": stderr}
3. 建立一個提供給 openai 的 tools 定義集合
在提供給 openai 時會有 schema 的校驗,校驗不透過時會報錯,引數的型別沒有 list
或者 array
tools = [{
"type": "function",
"function": {
"name": "do_command",
"description": "執行linux / windows命令",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "輸入的命令"}
},
"required": ["command"]
}
}
}]
4. 建立一個執行 tool 的方法
接收引數為 chatgpt 返回的 response 與 message 集合, 將執行完成的結果寫入到集合中
如果不需要執行 tool,則將 response 的 message 新增到 message 集合中
這裡有幾點需要注意:
- 如果第一次執行 tool 時返回的是錯誤資訊,openai 可能會讓重新執行 tool,但是返回的 tool_call_id 是不變的,這時不能把錯誤的結果與正確的結果都返回給 openai,不然會出現錯誤
- 需要 tool_call 的場景下,不代表 content 是空的;可能一邊有結果返回,一邊讓你再次執行;方法中沒有處理執行 tool 的 message,也沒有處理並行 tool 的場景(因為只有一個方法)
def parse_function_call(model_response, messages):
_messages = messages.copy()
_messages.append(model_response.choices[0].message.model_dump())
if model_response.choices[0].message.tool_calls:
tool_call = model_response.choices[0].message.tool_calls[0]
model = model_response.model
args = tool_call.function.arguments
function_result = {}
if tool_call.function.name == "do_command":
function_result = do_command(**json.loads(args))
_messages.append({
"role": "tool",
"content": f"{json.dumps(function_result)}",
"tool_call_id": tool_call.id
})
print(_messages)
response = client.chat.completions.create(
model=model, # 填寫需要呼叫的模型名稱
messages=_messages,
tools=tools,
)
if response.choices[0].message.tool_calls:
parse_function_call(response, messages)
else:
messages.append(response.choices[0].message.model_dump())
else:
messages.append(model_response.choices[0].message.model_dump())
5. 執行 chatgpt,並將工具的定義作為引數傳入
messages = []
messages.append({"role": "system", "content": "當前的執行環境是{}".format(os.name)})
messages.append({"role": "user", "content": "查詢python的版本"})
resp = client.chat.completions.create(messages=messages, model="gpt-4o-2024-05-13", tools=tools)
parse_function_call(resp, messages)
print(messages)
執行tool的message 記錄
[{'role': 'system', 'content': '當前的執行環境是nt'}, {'role': 'user', 'content': '查詢python的版本'}, {'content': None, 'role': 'assistant', 'function_call': None, 'tool_calls': [{'id': 'call_f3OaTCDet0p73o2fPvTRBFIs', 'function': {'arguments': '{"command":"python --version"}', 'name': 'do_command'}, 'type': 'function'}]}, {'role': 'tool', 'content': '{"stdout": "Python 3.11.6\\n", "stderr": ""}', 'tool_call_id': 'call_f3OaTCDet0p73o2fPvTRBFIs'}]
返回結果
[{'role': 'system', 'content': '當前的執行環境是nt'}, {'role': 'user', 'content': '查詢python的版本'}, {'content': '當前的Python版本是:**Python 3.11.6**。', 'role': 'assistant', 'function_call': None, 'tool_calls': None}]
6. 總結
tool 確實讓chatgpt 的使用變得更加靈活,後續應該擴充套件為系統命令建立一個穩定的執行環境,現在是單命令的場景;message可以新增一個快取機制,使讀取與寫入更加靈活一些
用openai原生的api與langchain的體驗不太一樣,原生的可以很明顯的感受的到是基於一個message的流來進行交流
langchian的LECL也有比較方便的地方,但是對於tool的呼叫很彆扭