# 工具调用

定义函数工具、校验模型参数并回传结果，完成受控的工具调用循环。

## 工具调用的职责 [#工具调用的职责]

工具调用让模型返回函数名与参数，由你的应用执行函数，再把结果提交给模型。网关负责协议转发，不执行你的函数。收到 `tool_calls` 不代表外部操作已经完成。

先选择支持工具调用的模型，再检查接入通道是否支持对应字段。本文使用 GFW 模型 ID `deepseek-v4-pro` 演示，并只定义一个本地无副作用函数。响应结构是示意；示例不是实际模型调用记录。

## 协议选择与交互步骤 [#协议选择与交互步骤]

推荐通过 `POST /v1/chat/completions` 实现完整循环：

1. 随消息提交 `tools` 中的函数名、描述和 JSON Schema。
2. 接收助手消息，检查 `tool_calls`，不要要求 `content` 一定有文本。
3. 将完整助手消息加入历史，校验每个工具名与参数，执行允许的函数。
4. 添加 `role=tool` 的结果消息，`tool_call_id` 必须与对应调用的 `id` 相同。
5. 再次提交历史，直到收到普通回复或达到应用设置的轮数上限。

Anthropic 非流式 Messages 会转换 `tools`、`tool_use` 和 `tool_result`，也可实现基本循环。当前 Responses 输出转换只构造文本消息，不完整返回 `function_call` 项，输入转换也不支持完整的 `function_call_output`，因此不要使用它完成本页流程。

`tool_choice=auto` 允许模型直接回答；`required` 或指定函数的行为由模型及通道决定。`parallel_tool_calls` 也不是所有模型都支持的通用开关。本页示例不依赖强制或并行调用。

## 完整调用示例 [#完整调用示例]

设置 `GFW_API_KEY`。cURL 展示第一轮声明工具；Python（依赖 `openai` 包）包含最多三轮的完整执行与回传流程。



**cURL**


```bash
curl --fail-with-body --silent --show-error \
  --max-time 120 \
  https://api.gfw.net/v1/chat/completions \
  -H "Authorization: Bearer ${GFW_API_KEY:?请先设置 GFW_API_KEY}" \
  -H 'Content-Type: application/json' \
  --data '{
    "model": "deepseek-v4-pro",
    "messages": [{"role": "user", "content": "调用 normalize_label，把 Example Label 转成统一标签。"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "normalize_label",
        "description": "清理标签首尾空格并转换为小写。",
        "parameters": {
          "type": "object",
          "properties": {"text": {"type": "string"}},
          "required": ["text"],
          "additionalProperties": false
        }
      }
    }],
    "tool_choice": "auto",
    "max_tokens": 512,
    "stream": false
  }'
```



**Python**


```python
import json
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["GFW_API_KEY"],
    base_url="https://api.gfw.net/v1",
    timeout=120.0,
    max_retries=0,
)
tools = [{
    "type": "function",
    "function": {
        "name": "normalize_label",
        "description": "清理标签首尾空格并转换为小写。",
        "parameters": {
            "type": "object",
            "properties": {"text": {"type": "string"}},
            "required": ["text"],
            "additionalProperties": False,
        },
    },
}]
messages = [{
    "role": "user",
    "content": "调用 normalize_label，把 Example Label 转成统一标签。",
}]

def execute_tool(call):
    if call.function.name != "normalize_label":
        raise ValueError("不允许的工具名称")
    args = json.loads(call.function.arguments)
    if not isinstance(args, dict) or set(args) != {"text"}:
        raise ValueError("工具参数字段不符合约定")
    text = args["text"]
    if not isinstance(text, str) or len(text) > 200:
        raise ValueError("text 必须是长度不超过 200 的字符串")
    return {"label": text.strip().lower()}

for _ in range(3):
    response = client.chat.completions.create(
        model="deepseek-v4-pro",
        messages=messages,
        tools=tools,
        tool_choice="auto",
        max_tokens=512,
        stream=False,
    )
    if not response.choices:
        raise RuntimeError("响应未包含 choices")
    choice = response.choices[0]
    if choice.finish_reason == "length":
        raise RuntimeError("输出截断，不能执行可能不完整的工具参数")
    message = choice.message
    if not message.tool_calls:
        print(message.content or "")
        break
    # 只保留下一轮对话需要的消息字段，避免回传未确认的扩展字段。
    messages.append({
        "role": "assistant",
        "content": message.content,
        "tool_calls": [call.model_dump(exclude_none=True) for call in message.tool_calls],
    })
    for call in message.tool_calls:
        result = execute_tool(call)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result, ensure_ascii=False),
        })
else:
    raise RuntimeError("工具交互超过三轮，请人工检查任务")
```




## 响应与回传格式 [#响应与回传格式]

以下助手消息为结构示意；`arguments` 在 Chat Completions 中是 JSON 字符串，而不是直接嵌入的对象：

```json
{
  "role": "assistant",
  "content": null,
  "tool_calls": [{
    "id": "call_example",
    "type": "function",
    "function": {"name": "normalize_label", "arguments": "{\"text\":\"Example Label\"}"}
  }]
}
```

对应的结果消息示意：

```json
{
  "role": "tool",
  "tool_call_id": "call_example",
  "content": "{\"label\":\"example label\"}"
}
```

保留所有助手工具请求和每个对应结果，不能仅把工具结果作为新的 user 文本提交。一个助手消息可能包含多个工具调用；应用应明确其执行顺序，不能把“收到多个请求”自动解释为可安全并行。

## 参数校验与流式工具 [#参数校验与流式工具]

JSON Schema 有助于模型组织参数，但应用仍必须校验工具名、字段类型、长度、权限与业务限制。不要使用 `eval` 执行模型返回的参数，也不要将模型给出的函数名直接映射成任意程序调用。

执行有副作用的函数时，记录工具调用 ID 与执行状态，避免网络重试导致重复操作；重复结果应可识别。工具结果只返回必要数据，错误也应形成明确结果或终止流程。

流式 Chat Completions 的工具名和 `arguments` 可能分多次增量返回，按 `delta.tool_calls[].index` 聚合，直到调用参数完整、结束原因合理后再执行。不要逐片段解析半个 JSON。Anthropic 工具流为基础转换实现，不应假定复杂并行场景与原生事件完全一致。

## 注意事项与相关文档 [#注意事项与相关文档]

多轮工具流程会发起多次模型请求，每轮的历史消息也参与输入。控制工具轮数、结果长度与请求总时限，避免不断回传大段数据。工具本身的成本与模型 Token 费用应分别记录。

相关文档：[OpenAI 兼容 API](/docs/openai-api)、[Anthropic 兼容 API](/docs/anthropic-api)、[流式输出](/docs/streaming)、[结构化输出](/docs/structured-output)、[计费与充值](/docs/billing)。
