工具调用

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 ChatAnthropicGemini
工具列表字段toolstoolstools
单工具 wrapper{type:"function","function":{...}}直接 {...} (不包 wrapper){functionDeclarations:[...]}
参数 schema 字段function.parametersinput_schemaparameters
模型选择策略tool_choicetool_choicetoolConfig.functionCallingConfig
模型工具调用响应message.tool_calls[i]content[i] type:tool_useparts[i].functionCall
工具结果回填role:"tool" + tool_call_idcontent type:tool_result + tool_use_idparts[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) 模型上一轮的 functionCall content 必须原样放回 contents,否则模型不知道自己调过什么。Gemini Native 通道是字节透传,回填形状完全由客户端负责。

OpenAI Responses (/v1/responses) —— Function Tools#

Codex CLI 等 Responses-API 客户端走 POST /v1/responses,function tools 同样可用,但字段形状与 Chat Completions 不同:

  • 工具定义没有 function wrapper —— name / description / parameters 直接平铺
  • 模型的调用以 function_call output item 返回(不是 message.tool_calls)
  • 结果以 function_call_output input 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。

注意事项#

  1. 工具 schema 校验严格 —— parameters 必须是合法 JSON Schema,模型按它生成 arguments
  2. arguments 是字符串 (OpenAI) / 是对象 (Anthropic / Gemini) —— 客户端解析方式不同
  3. 不要在工具结果里返回长 base64 图像 —— 会被算进下一轮 input tokens,极易爆账
  4. 内置工具 (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
  5. 不支持的 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。各错误码详见 错误码