工具调用
OpenAI Chat & Responses Function Tools / Anthropic Tool Use / Gemini Function Calling 跨 SDK 对照,含内置工具的模型限制
让模型决定何时调用你的代码逻辑 —— 查天气 / 查数据库 / 调用其他 API / 控制软件 ...
本平台在 4 个聊天协议端点(OpenAI Chat / OpenAI Responses / Anthropic Messages / Gemini Native)上都支持 function / tool 调用,但字段名跟 schema 形状不同。本页给跨 SDK 对照与最佳实践。
概念对照#
| 概念 | OpenAI Chat | Anthropic | Gemini |
|---|---|---|---|
| 工具列表字段 | tools | tools | tools |
| 单工具 wrapper | {type:"function","function":{...}} | 直接 {...} (不包 wrapper) | {functionDeclarations:[...]} |
| 参数 schema 字段 | function.parameters | input_schema | parameters |
| 模型选择策略 | tool_choice | tool_choice | toolConfig.functionCallingConfig |
| 模型工具调用响应 | message.tool_calls[i] | content[i] type:tool_use | parts[i].functionCall |
| 工具结果回填 | role:"tool" + tool_call_id | content type:tool_result + tool_use_id | parts[i].functionResponse |
OpenAI Chat —— Function Tools#
1. 定义工具#
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["c", "f"], "default": "c"},
},
"required": ["city"],
},
},
}]
2. 发起调用#
from openai import OpenAI
client = OpenAI(api_key="sk-gpushare-xxx", base_url="https://api.dflop.top/v1")
messages = [{"role": "user", "content": "Weather in Tokyo?"}]
resp = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=messages,
tools=tools,
)
3. 处理模型工具调用#
import json
choice = resp.choices[0]
if choice.finish_reason == "tool_calls":
# assistant 消息(含全部 tool_calls)只 append 一次。
# 放进循环会在多工具调用时产生 [assistant, tool, assistant, tool] 非法序列,
# 协议要求所有 tool 结果紧跟在唯一一条 assistant(tool_calls) 消息之后。
messages.append(choice.message)
for tc in choice.message.tool_calls:
args = json.loads(tc.function.arguments)
result = get_weather(**args) # 你的真实函数
# 每个 tool_call 各回填一条 role:"tool" 结果
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
# 再发一轮拿最终回答
final = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
Anthropic Messages —— Tool Use#
1. 定义工具#
tools = [{
"name": "get_weather",
"description": "Get the current weather in a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["c", "f"]},
},
"required": ["city"],
},
}]
2. 发起调用#
from anthropic import Anthropic
client = Anthropic(api_key="sk-gpushare-xxx", base_url="https://api.dflop.top")
messages = [{"role": "user", "content": "Weather in Tokyo?"}]
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
3. 处理模型工具调用#
import json
if resp.stop_reason == "tool_use":
tool_blocks = [b for b in resp.content if b.type == "tool_use"]
tool_results = []
for tb in tool_blocks:
result = get_weather(**tb.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": tb.id,
"content": json.dumps(result),
})
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": tool_results})
final = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
Gemini Native —— Function Calling#
1. 定义工具#
from google.genai import types
weather_tool = types.Tool(function_declarations=[
types.FunctionDeclaration(
name="get_weather",
description="Get the current weather in a city",
parameters={
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["c", "f"]},
},
"required": ["city"],
},
)
])
2. 发起调用#
from google import genai
client = genai.Client(api_key="sk-gpushare-xxx", http_options={"base_url": "https://api.dflop.top"})
response = client.models.generate_content(
model="gemini-2.5-pro",
contents="Weather in Tokyo?",
config=types.GenerateContentConfig(tools=[weather_tool]),
)
3. 处理模型工具调用#
candidate = response.candidates[0]
function_calls = [p.function_call for p in candidate.content.parts if p.function_call]
if function_calls:
# 第二轮 contents: 原始提问 + 模型的 functionCall 轮(原样放回) + functionResponse parts
contents = [
types.Content(role="user", parts=[types.Part.from_text(text="Weather in Tokyo?")]),
candidate.content, # 模型上一轮(含 functionCall part),必须一并回传
]
response_parts = [
types.Part.from_function_response(
name=fc.name,
response={"result": get_weather(**dict(fc.args))},
)
for fc in function_calls
]
contents.append(types.Content(role="user", parts=response_parts))
final = client.models.generate_content(
model="gemini-2.5-pro",
contents=contents,
config=types.GenerateContentConfig(tools=[weather_tool]),
)
print(final.text)
Gemini 回填最容易写错的两点: (1)
functionResponse必须用types.Part.from_function_response包装,response字段是 dict; (2) 模型上一轮的functionCallcontent 必须原样放回contents,否则模型不知道自己调过什么。Gemini Native 通道是字节透传,回填形状完全由客户端负责。
OpenAI Responses (/v1/responses) —— Function Tools#
Codex CLI 等 Responses-API 客户端走 POST /v1/responses,function tools 同样可用,但字段形状与 Chat Completions 不同:
- 工具定义没有
functionwrapper ——name/description/parameters直接平铺 - 模型的调用以
function_calloutput item 返回(不是message.tool_calls) - 结果以
function_call_outputinput item 回填(不是role:"tool"消息)
import json
from openai import OpenAI
client = OpenAI(api_key="sk-gpushare-xxx", base_url="https://api.dflop.top/v1")
tools = [{
"type": "function",
"name": "get_weather", # 平铺,没有 function:{...} 包装
"description": "Get the current weather in a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}]
resp = client.responses.create(model="gpt-5.5", input="Weather in Tokyo?", tools=tools)
input_items = [{"role": "user", "content": "Weather in Tokyo?"}]
for item in resp.output:
if item.type == "function_call":
result = get_weather(**json.loads(item.arguments))
input_items.append(item) # 模型的 function_call item 原样放回
input_items.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(result),
})
final = client.responses.create(model="gpt-5.5", input=input_items, tools=tools)
print(final.output_text)
跨厂商 + 工具调用#
本平台协议翻译层让你用任意 SDK 调任意模型,工具同样跨厂商可用:
# 用 OpenAI SDK 调 Claude 做工具调用
from openai import OpenAI
client = OpenAI(api_key="sk-gpushare-xxx", base_url="https://api.dflop.top/v1")
resp = client.chat.completions.create(
model="claude-sonnet-4-6", # Claude 走 T1 翻译
messages=[{"role": "user", "content": "Weather in Tokyo?"}],
tools=tools, # 同上文「OpenAI Chat —— 1. 定义工具」的 tools 定义
)
# resp.choices[0].message.tool_calls 跟纯 OpenAI 一致
翻译路径的响应 header 会加 X-Protocol-Translation: openai_chat_to_anthropic_messages,你可以据此知晓走了哪条路径;若有能力在翻译中被降级或丢弃(例如 cache_control 受限),还会附 X-Protocol-Warning 列出具体说明 —— 调试「为什么我的工具没生效」时先看它。注意:路由选中原生协议渠道时这两个 header 都不出现,header 缺失是正常现象,不代表请求异常。
高级技巧#
强制工具调用#
# OpenAI: tool_choice 指定具体工具
tool_choice = {"type": "function", "function": {"name": "get_weather"}}
# Anthropic: tool_choice 限制必须用工具
tool_choice = {"type": "tool", "name": "get_weather"}
# Gemini: function_calling_config mode = ANY
config = types.GenerateContentConfig(
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(mode="ANY")
)
)
多个工具#
各协议 SDK 都支持单次请求多个工具,模型可能一次性返回多个调用:
# OpenAI 一次 response 可能有 multiple tool_calls
for tc in resp.choices[0].message.tool_calls:
handle(tc)
Stream 模式下的工具#
工具调用可以配合 stream=true。增量 chunk 里的 delta.tool_calls 是字段级累加的(function.arguments 一次发一片),客户端需要拼接所有 chunk 才能拿到完整 JSON。
注意事项#
- 工具 schema 校验严格 ——
parameters必须是合法 JSON Schema,模型按它生成 arguments - arguments 是字符串 (OpenAI) / 是对象 (Anthropic / Gemini) —— 客户端解析方式不同
- 不要在工具结果里返回长 base64 图像 —— 会被算进下一轮 input tokens,极易爆账
- 内置工具 (
web_search/image_generation) 不需要你定义 schema,但必须配stream=true(详见 流式响应),且仅部分模型可用 —— 见 模型列表 的能力列:web_search: GPT (gpt-5.4/gpt-5.5)、Claude(翻译为web_search_20250305)、Gemini(翻译为googleSearch)系列支持;GLM / DeepSeek / Grok 等其它模型默认不支持image_generation(对话内置出图): 仅gpt-5.4/gpt-5.5;独立出图请走按张计费的/v1/images/generations,见 图像 / 视频 / Embedding API
- 不支持的 tool type 一律 400 —— 在不支持的模型/渠道上带内置工具,返回 400
tool_not_supported;function/web_search/image_generation之外的 tool type(如从 OpenAI 官方迁移带来的file_search/code_interpreter)也会被 400 拒绝,错误信息形如Tool type 'file_search' is not supported。各错误码详见 错误码