Tool Calling
让模型提出工具调用,由应用安全执行并回传结果。
Tool Calling 让模型根据对话选择一个或多个工具,并生成调用所需的结构化参数。模型不会替应用访问数据库、调用内部服务或执行写操作;真正的工具执行始终由你的程序负责。
它适合查询订单、读取天气、检索知识库、计算数据,以及经过授权后执行明确的业务操作。
不要直接执行模型生成的参数
工具名称和参数都是模型输出。执行前必须完成 JSON 解析、Schema 校验、权限检查和业务白名单;支付、删除、发送消息等高风险操作还应要求用户确认并使用幂等控制。
完整调用循环
1. 定义工具
使用清晰的名称、用途说明和 JSON Schema 告诉模型何时可以调用工具,以及参数应采用什么结构。
2. 请求模型
将用户输入和工具定义一起发送。模型可能直接回答,也可能返回零个、一个或多个工具调用。
3. 校验并执行
根据工具名称查找本地实现,校验全部参数和调用者权限,再设置超时执行。不要允许模型自行指定任意函数、URL、SQL 或 Shell 命令。
4. 回传工具结果
使用协议返回的调用 ID,把执行结果与原工具调用关联起来,然后再次请求模型。
5. 读取最终回答
模型可以基于工具结果生成面向用户的回答,也可能继续提出新的工具调用。应用应设置最大循环次数并识别终止条件。
定义一个只读工具
下面的工具只查询天气,不产生外部写入:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city", "unit"],
"additionalProperties": false
}
}
}工具描述应该说明“做什么”和“何时使用”。参数名保持稳定,枚举尽量收窄,并用 required 和 additionalProperties 限制不必要输入。
完成一次 Chat 工具循环
下面展示声明工具、读取调用、执行本地函数、回传结果和读取最终回答的完整主路径:
import json
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://api.agtcloud.cn/v1",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
},
},
"required": ["city", "unit"],
"additionalProperties": False,
},
},
}
]
def get_weather(city: str, unit: str) -> dict[str, str | int]:
if city != "上海" or unit != "celsius":
raise ValueError("示例只允许查询上海摄氏温度")
return {"city": city, "unit": unit, "temperature": 25}
messages = [{"role": "user", "content": "上海现在多少度?"}]
response = client.chat.completions.create(
model="MODEL_ID",
messages=messages,
tools=tools,
tool_choice="auto",
)
assistant_message = response.choices[0].message
messages.append(assistant_message)
for tool_call in assistant_message.tool_calls or []:
if tool_call.function.name != "get_weather":
raise ValueError("模型请求了未注册的工具")
arguments = json.loads(tool_call.function.arguments)
result = get_weather(**arguments)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False),
}
)
if assistant_message.tool_calls:
final_response = client.chat.completions.create(
model="MODEL_ID",
messages=messages,
tools=tools,
)
print(final_response.choices[0].message.content)
else:
print(assistant_message.content)正式应用应使用 JSON Schema 校验库验证 arguments,并为工具实现设置独立权限、超时、错误类型和审计记录。
不同协议怎样表示工具调用
| 协议 | 工具定义 | 模型调用 | 工具结果回传 |
|---|---|---|---|
| Chat Completions | tools[].function | Assistant Message 中的 tool_calls | role: tool 与 tool_call_id |
| Responses | tools 中的 Function Tool | function_call Output Item | function_call_output 与 call_id |
| Anthropic Messages | tools 与 input_schema | tool_use Content Block | tool_result Content Block |
| Gemini | functionDeclarations | Part 中的 functionCall | Part 中的 functionResponse |
字段名称和嵌套结构不能混用。完整结构分别查看 Chat Completions、Responses、Anthropic Messages 和 Gemini Generate Content Reference。
多个调用与 Streaming
- 不要假设每次只能调用一个工具;按调用 ID 分别校验、执行和回传。
- 只有相互独立且业务允许的只读调用才适合并行执行。
- 流式参数可能被拆成多个增量;必须拼接完成后再解析 JSON。
- 返回工具结果后,模型仍可能提出下一轮调用;应用需要最大轮数和总超时。
- 同一个写操作重试时使用业务幂等键,避免重复扣款、发送或创建资源。
失败处理
工具执行失败时,不要伪造成功结果。可以向模型回传经过脱敏的错误类别,例如参数无效、权限不足、上游暂时不可用,让模型向用户解释或选择允许的替代方案。
以下信息不应直接回传给模型或用户:内部堆栈、数据库结构、访问令牌、私有网络地址和未脱敏的第三方响应。
上线前检查
- 工具只能从注册表或白名单选择。
- 参数在执行前通过 Schema 和业务规则校验。
- 每个工具都有权限、超时、最大结果大小和错误处理。
- 高风险写操作经过用户确认,并支持幂等和审计。
- 调用循环有最大轮数,不能由模型无限继续。
- 流式工具参数在完整结束后才会执行。
需要边接收边拼接工具参数时,继续阅读 Streaming;需要让最终回答遵循 JSON Schema 时,阅读 Structured Output。