chatgpt tools呼叫

diveYa發表於2024-06-13

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 集合中

這裡有幾點需要注意:

  1. 如果第一次執行 tool 時返回的是錯誤資訊,openai 可能會讓重新執行 tool,但是返回的 tool_call_id 是不變的,這時不能把錯誤的結果與正確的結果都返回給 openai,不然會出現錯誤
  2. 需要 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的呼叫很彆扭

相關文章