跳转至

OpenAI Chat Completion 集成

延续 基础用法 中的简单数学示例,本文介绍如何将 ToolRegistry 与 OpenAI Chat Completion API 配合使用。需要注意的是,你可以通过 OpenAI 客户端连接任何提供 OpenAI 兼容 API 的服务商。本指南以 DeepSeek 为例进行演示。

设置 ToolRegistry

我们创建一个包含两个数学函数的工具注册表:

from toolregistry import ToolRegistry

registry = ToolRegistry()


@registry.register
def add(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b


@registry.register
def subtract(a: float, b: float) -> float:
    """Subtract the second number from the first."""
    return a - b

导出工具 Schema

schemas = registry.get_schemas(api_format="openai-chat")

以上两种写法均会返回符合 OpenAI Chat Completion API 要求的工具 Schema。格式化后的 JSON 输出如下:

[
  {
    "type": "function",
    "function": {
      "name": "add",
      "description": "Add two numbers together.",
      "parameters": {
        "properties": {
          "a": {
            "title": "A",
            "type": "number"
          },
          "b": {
            "title": "B",
            "type": "number"
          }
        },
        "required": [
          "a",
          "b"
        ],
        "title": "addParameters",
        "type": "object"
      }
    }
  },
  {
    "type": "function",
    "function": {
      "name": "subtract",
      "description": "Subtract the second number from the first.",
      "parameters": {
        "properties": {
          "a": {
            "title": "A",
            "type": "number"
          },
          "b": {
            "title": "B",
            "type": "number"
          }
        },
        "required": [
          "a",
          "b"
        ],
        "title": "subtractParameters",
        "type": "object"
      }
    }
  }
]

使用工具 Schema 发送查询

将工具 JSON Schema 提供给 OpenAI 客户端的 Chat Completion 接口:

import os
from dotenv import load_dotenv
from openai import OpenAI

# Load environment variables from the .env file
load_dotenv()

# Configure the OpenAI client
client = OpenAI(
    api_key=os.getenv("API_KEY", "your-api-key"),
    base_url=os.getenv("BASE_URL", "https://api.deepseek.com/"),
)

messages = [
    {
        "role": "user",
        "content": "I have 15 chestnuts. Joe ate 3. How many chestnuts do I have left?",
    }
]
# Send the chat completion request
response = client.chat.completions.create(
    model="deepseek-chat",
    messages=messages,
    tools=schemas, # this is where we feed in the schema
    tool_choice="auto",
)

提取工具调用

如果模型(LLM)决定使用工具,它会在响应消息中返回 tool_calls

if response.choices[0].message.tool_calls:
    tool_calls = response.choices[0].message.tool_calls

函数调用的输出示例:

[ChatCompletionMessageToolCall(id='call_egkg4evbb19d8012bex83v8a', function=Function(arguments='{"a":15,"b":3}', name='subtract'), type='function', index=0)]

tool_calls 对象是一个 ChatCompletionMessageToolCallList。以下属性尤为重要:

  • id:在将结果反馈给 LLM 时需要用到
  • function核心字段,包含目标函数的参数和名称
  • index:在非流式模式下用处不大,但在流式输出时,它是拼接完整 tool_calls 信息的关键

执行工具调用

使用 ToolRegistry,你可以轻松处理所有 tool_calls 的执行结果。注意,有时 LLM 可能会同时调用多个工具。

# Execute tool calls
tool_responses = registry.execute_tool_calls(tool_calls)

注册表返回的工具执行结果是一个 Python 字典,键为 tool_call_id,值为对应的结果:

{ "call_0_bfa567b8-2f10-4113-953a-56e87b664e0f": 12 }

将结果反馈给 LLM

执行完工具调用后,我们还需要将结果告知 LLM,它才能回答原始问题。

为了在后续与 LLM 的交互中保持工具调用的上下文,我们需要重建两部分信息:

  1. 助手决定发起 tool_calls 的消息
  2. 实际的 tool_calls 执行结果
# Construct assistant messages with results
assistant_tool_messages = registry.build_tool_call_messages(
    tool_calls, tool_responses, api_format="openai-chat"
) # you can leave out api_format, it defaults to "openai-chat"
[
  {
    "content": null,
    "role": "assistant",
    "tool_calls": [
      {
        "id": "call_wAcYzTLh37jfrCmihEv7x4FC",
        "function": {
          "arguments": "{\"a\":15,\"b\":3}",
          "name": "subtract"
        },
        "type": "function"
      }
    ]
  },
  {
    "role": "tool",
    "tool_call_id": "call_wAcYzTLh37jfrCmihEv7x4FC",
    "content": "12"
  }
]

然后将这些重建的消息追加到之前发送给 LLM 的消息列表中。

messages.extend(assistant_tool_messages)

# Send the results back to the model
second_response = client.chat.completions.create(
    model="deepseek-chat", messages=messages
)

# Print final response
print(second_response.choices[0].message.content)

最终结果与注意事项

LLM 在处理完工具执行结果后,会返回最终答案:

You have **12 chestnuts** left after Joe ate 3.

重要的实现说明

实现时应处理连续函数调用的情况,因为对话可能需要多轮工具调用,每次 LLM 的响应都可能触发新的工具调用。

错误处理至关重要:在执行工具调用前,始终需要验证参数的合法性(ToolRegistry 已自动完成此操作)。需要处理工具可能失败或返回错误的情况,并考虑为长时间运行的操作添加超时机制。

状态管理涉及维护完整的对话历史(包括所有工具调用及其响应)、跟踪工具执行顺序以便调试,以及考虑对话状态的持久化。

性能方面需要注意减少不必要的工具调用、在适当时缓存频繁的工具响应结果,以及监控和优化工具执行时间。

完整 Python 脚本

以下是本示例使用的完整脚本。

import json
import os

from dotenv import load_dotenv
from openai import OpenAI

from toolregistry import ToolRegistry

# Load environment variables from .env file
load_dotenv()

model_name = os.getenv("MODEL", "deepseek-chat")
stream = os.getenv("STREAM", "True").lower() == "true"

registry = ToolRegistry()


@registry.register
def add(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b


@registry.register
def subtract(a: float, b: float) -> float:
    """Subtract the second number from the first."""
    return a - b


# Set up OpenAI client
client = OpenAI(
    api_key=os.getenv("API_KEY", "your-api-key"),
    base_url=os.getenv("BASE_URL", "https://api.deepseek.com/"),
)


messages = [
    {
        "role": "user",
        "content": "I have 15 chestnuts. Joe ate 3. How many chestnuts do I have left?",
    }
]

# Make the chat completion request
response = client.chat.completions.create(
    model=model_name,
    messages=messages,
    tools=registry.get_schemas(api_format="openai-chat"),
    tool_choice="auto",
)

# Handle tool calls using ToolRegistry
if response.choices[0].message.tool_calls:
    tool_calls = response.choices[0].message.tool_calls
    print(tool_calls)

    # Execute tool calls
    tool_responses = registry.execute_tool_calls(tool_calls)
    print(tool_responses)

    # Construct assistant messages with results
    assistant_tool_messages = registry.build_tool_call_messages(
        tool_calls, tool_responses, api_format="openai-chat"
    )
    print(json.dumps(assistant_tool_messages, indent=2))

    # Send the results back to the model
    messages.extend(assistant_tool_messages)
    second_response = client.chat.completions.create(
        model=model_name, messages=messages
    )

    # Print final response
    print(second_response.choices[0].message.content)