跳到主要内容

工具调用与结构化输出

工具调用和结构化输出解决的是两类不同问题:

能力用途典型场景
工具调用让模型请求应用执行某个函数查询天气、读取订单、搜索数据库、调用业务接口
结构化输出让模型的最终回答符合指定 JSON 结构信息提取、表单生成、分类和程序间数据交换

这两项能力都依赖具体模型和上游渠道。使用前,请在模型广场查看模型能力;如果请求被拒绝,应先移除相关高级参数验证基础对话是否正常。

本页示例使用 Python 的 requests 库:

python -m pip install requests

工具调用的工作流程

模型不会替应用真正执行函数。完整流程由应用和模型共同完成:

  1. 应用把可用工具及其参数结构发送给模型。
  2. 模型决定是否调用工具,并返回工具名称和参数。
  3. 应用校验参数并执行本地函数或业务接口。
  4. 应用把执行结果和对应的工具调用 ID 发回模型。
  5. 模型根据工具结果生成最终回答。
用户问题 → 模型返回 tool_calls → 应用执行函数
← 模型生成最终回答 ← 应用回传工具结果
工具调用只是执行请求

模型返回的工具名称和参数不能直接视为可信结果。是否执行、如何执行以及执行权限都由你的应用决定。

定义工具

下面定义一个 get_weather 函数工具。parameters 使用 JSON Schema 描述模型应生成的参数:

tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如杭州",
}
},
"required": ["city"],
"additionalProperties": False,
},
"strict": True,
},
}
]

主要字段:

字段说明
type函数工具固定为 function
function.name应用中用于识别工具的稳定名称
function.description说明何时以及如何使用该工具
function.parameters函数参数的 JSON Schema
function.strict请求模型严格遵循参数结构;需要模型和渠道支持

工具描述应明确、简短,并避免一次提供大量功能相近的工具。工具定义也会占用模型上下文和输入 Token。

完整工具调用示例

下面的脚本演示一次完整循环。示例中的天气数据由本地函数模拟,实际项目可以替换为自己的业务接口。

import json

import requests

API_URL = "https://www.bita-api.com/v1/chat/completions"
HEADERS = {"Authorization": "Bearer YOUR_BITA_API_KEY"}

tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如杭州",
}
},
"required": ["city"],
"additionalProperties": False,
},
"strict": True,
},
}
]


def get_weather(city):
return {
"city": city,
"condition": "晴",
"temperature": 26,
"unit": "celsius",
}


messages = [
{
"role": "user",
"content": "杭州现在的天气怎么样?",
}
]

response = requests.post(
API_URL,
headers=HEADERS,
json={
"model": "YOUR_MODEL_ID",
"messages": messages,
"tools": tools,
"tool_choice": "auto",
},
timeout=120,
)
response.raise_for_status()

assistant_message = response.json()["choices"][0]["message"]
messages.append(assistant_message)

tool_calls = assistant_message.get("tool_calls", [])
if not tool_calls:
print(assistant_message.get("content", ""))
raise SystemExit

for tool_call in tool_calls:
function = tool_call["function"]
arguments = json.loads(function["arguments"])

if function["name"] == "get_weather":
tool_result = get_weather(arguments["city"])
else:
tool_result = {"error": "unknown_tool"}

messages.append(
{
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(tool_result, ensure_ascii=False),
}
)

final_response = requests.post(
API_URL,
headers=HEADERS,
json={
"model": "YOUR_MODEL_ID",
"messages": messages,
"tools": tools,
},
timeout=120,
)
final_response.raise_for_status()

result = final_response.json()
print(result["choices"][0]["message"]["content"])

模型可能返回零个、一个或多个 tool_calls,因此示例使用循环处理。每个工具结果必须通过原始 tool_call_id 与对应调用关联。

Claude / Anthropic Messages 示例

Claude 原生格式同样支持完整的工具调用闭环,但字段结构与 OpenAI 不同:

  • 工具参数结构使用 input_schema
  • 模型通过 content 中的 tool_use 内容块请求调用工具。
  • 工具参数位于 tool_use.input,已经是 JSON 对象。
  • 应用使用 tool_result 内容块回传结果,并通过 tool_use_id 关联调用。
import json

import requests

API_URL = "https://www.bita-api.com/v1/messages"
HEADERS = {
"x-api-key": "YOUR_BITA_API_KEY",
"anthropic-version": "2023-06-01",
}

tools = [
{
"name": "get_weather",
"description": "查询指定城市的当前天气",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如杭州",
}
},
"required": ["city"],
},
}
]


def get_weather(city):
return {
"city": city,
"condition": "晴",
"temperature": 26,
"unit": "celsius",
}


messages = [
{
"role": "user",
"content": "杭州现在的天气怎么样?",
}
]

response = requests.post(
API_URL,
headers=HEADERS,
json={
"model": "YOUR_MODEL_ID",
"max_tokens": 1024,
"messages": messages,
"tools": tools,
},
timeout=120,
)
response.raise_for_status()

assistant_message = response.json()
messages.append(
{
"role": "assistant",
"content": assistant_message["content"],
}
)

tool_uses = [
block
for block in assistant_message["content"]
if block.get("type") == "tool_use"
]

if not tool_uses:
for block in assistant_message["content"]:
if block.get("type") == "text":
print(block.get("text", ""))
raise SystemExit

tool_results = []
for tool_use in tool_uses:
if tool_use["name"] == "get_weather":
result = get_weather(tool_use["input"]["city"])
else:
result = {"error": "unknown_tool"}

tool_results.append(
{
"type": "tool_result",
"tool_use_id": tool_use["id"],
"content": json.dumps(result, ensure_ascii=False),
}
)

messages.append(
{
"role": "user",
"content": tool_results,
}
)

final_response = requests.post(
API_URL,
headers=HEADERS,
json={
"model": "YOUR_MODEL_ID",
"max_tokens": 1024,
"messages": messages,
"tools": tools,
},
timeout=120,
)
final_response.raise_for_status()

for block in final_response.json()["content"]:
if block.get("type") == "text":
print(block.get("text", ""))

Claude 请求工具时,响应的 stop_reason 通常为 tool_use。一条响应也可能包含多个 tool_use 内容块,因此示例会收集并回传全部结果。

保持 Claude 消息顺序

包含 tool_result 的用户消息必须紧跟在产生 tool_use 的助手消息之后。不要在两者之间插入其他消息,并确保每个 tool_result 都使用对应的 tool_use_id

tool_choice(OpenAI Chat Completions)

tool_choice 用于控制模型如何选择工具:

作用
"auto"由模型决定直接回答还是调用工具
"none"禁止调用工具
指定函数强制或限制使用某个函数;具体结构以接口兼容能力为准

默认情况下可以省略 tool_choice。如果模型频繁误调用工具,应先改善函数名称、描述和系统指令,而不是始终强制调用。

执行工具前的校验

应用收到工具调用后,至少应完成以下检查:

  • 工具名称必须存在于应用允许的工具列表中。
  • 使用 JSON Schema 或业务代码再次验证参数类型和取值范围。
  • 从登录会话或服务端上下文获取用户身份,不要让模型自行填写权限信息。
  • 删除、付款、发送消息等有外部影响的操作应在执行前获得明确确认。
  • 为可能重复提交的操作设置幂等机制。
  • 不要把模型生成的内容直接拼接为 Shell 命令、SQL 或代码执行。

使用 JSON Schema 约束输出

如果只需要模型返回固定结构的数据,不需要执行外部函数,可以使用 Structured Outputs。

下面要求 Chat Completions 返回符合指定结构的城市信息:

import json

import requests

response = requests.post(
"https://www.bita-api.com/v1/chat/completions",
headers={"Authorization": "Bearer YOUR_BITA_API_KEY"},
json={
"model": "YOUR_MODEL_ID",
"messages": [
{
"role": "user",
"content": "提取城市和国家:中国浙江省杭州市。",
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "location",
"strict": True,
"schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"country": {"type": "string"},
},
"required": ["city", "country"],
"additionalProperties": False,
},
},
},
},
timeout=120,
)
response.raise_for_status()

content = response.json()["choices"][0]["message"]["content"]
result = json.loads(content)
print(result["city"])
print(result["country"])

严格模式下,对象通常需要:

  • required 中列出需要生成的字段。
  • 设置 additionalProperties: false,避免产生未定义字段。
  • 只使用当前模型支持的 JSON Schema 子集。

即使启用了结构化输出,应用仍应处理拒绝回答、内容截断、网络失败和上游异常等情况。

Chat Completions 与 Responses 的字段区别

两种接口都可以使用结构化输出,但配置位置不同:

Chat Completions

{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "result",
"strict": true,
"schema": {}
}
}
}

Responses

{
"text": {
"format": {
"type": "json_schema",
"name": "result",
"strict": true,
"schema": {}
}
}
}

不要把 Chat Completions 的 response_format 直接放入 Responses 请求,也不要把 Responses 的 text.format 用于 Chat Completions。

JSON Mode

模型不支持 JSON Schema 时,可以尝试较基础的 JSON Mode:

{
"response_format": {
"type": "json_object"
}
}

JSON Mode 只保证返回内容是有效 JSON,不保证字段符合特定结构。使用时还需要在提示词中明确要求输出 JSON,并由应用自行校验字段和重试。

其他兼容协议

不同协议的工具调用字段并不相同:

接口格式工具定义模型请求调用回传工具结果
OpenAI Chat Completionstools[].functionmessage.tool_calls[]role: "tool"tool_call_id
OpenAI Responsestools[]output[] 中的 function_callfunction_call_outputcall_id
Anthropic Messagestools[]content[] 中的 tool_usetool_result 内容块
Geminitools[].functionDeclarationsfunctionCallfunctionResponse

第三方工具指定协议时,应使用对应协议的完整调用闭环,不能只修改接口地址后继续使用另一种协议的消息结构。

常见问题

现象建议检查
模型直接回答,没有调用工具工具描述是否清晰,问题是否确实需要工具,tool_choice 是否允许调用
function.arguments 无法解析模型是否支持工具调用或严格模式;应用是否捕获 JSON 解析错误
回传结果后接口报错是否保留原始助手消息,并使用正确的 tool_call_id
一次返回多个工具调用遍历全部 tool_calls,分别执行并回传结果
response_format 不受支持模型或渠道可能不支持 Structured Outputs,尝试 JSON Mode 或由应用校验普通 JSON
JSON 符合语法但字段错误JSON Mode 不保证 Schema;应使用 JSON Schema 或业务校验

下一步:图片、音频与多模态