Skip to content

使用 Function Calling 调用工具

Function Calling 用于让模型生成外部工具的调用参数。模型负责生成 tool_calls;应用负责解析和校验参数,执行自己的函数、查询或工作流,再把工具结果返回给模型。

如果只需要模型返回一个可解析的 JSON 对象,且无需触发外部动作,请优先使用 Structured output。如果模型输出会驱动数据库查询、订单创建、任务队列、工作流或第三方 API 调用,再使用 Function Calling。

先决定接入路径

先判断任务是否真的需要工具调用,再选择模型。这样可以避免把简单 JSON 输出流程写成多轮工具调用流程。

决定是否使用 Function Calling

当模型输出会改变外部系统状态,或需要调用应用已有能力时,使用 Function Calling。典型场景包括查询库存、创建订单、调用搜索服务、执行运维动作、触发审批流程。

当目标只是得到一个 JSON 对象时,Structured output 通常更直接。Function Calling 的关键价值是把模型生成的参数接到应用自己的工具执行流程中;这些参数仍需要解析、校验和业务检查。

选择支持工具调用的模型

先在模型广场确认目标模型是否支持工具调用,再用目标工具 schema 发起一个小请求。模型能力和兼容行为可能随模型版本变化,以当前模型信息和实际请求结果为准。

准备工具调用请求

本节用于准备第一次请求:定义工具 schema,选择 tool_choice,再发送请求让模型生成工具参数。

定义工具参数 schema

工具 schema 应该只描述应用真正会执行的参数。字段越明确,应用越容易发现坏参数或越权动作。

以下示例定义一个 create_order 工具,用于创建订单:

language-json
{
  "type": "function",
  "function": {
    "name": "create_order",
    "description": "Create an order record.",
    "strict": true,
    "parameters": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "buyer": { "type": "string" },
        "item": { "type": "string" },
        "quantity": { "type": "integer" },
        "total": { "type": "number" },
        "currency": { "type": "string", "enum": ["CNY", "USD"] },
        "order_date": { "type": "string" }
      },
      "required": ["buyer", "item", "quantity", "total", "currency", "order_date"]
    }
  }
}

生产工具 schema 应尽量包含 requiredenumadditionalProperties 和清晰的字段说明。应用侧也要重新校验这些字段,因为模型生成的参数仍属于外部输入。

选择合适的 tool_choice

先决定当前请求是否必须调用工具,再编写请求体。tool_choice 会直接影响模型是否返回 tool_calls

  • 使用 "auto":模型可以自行决定回答文本或调用工具。
  • 使用 "none":当前请求明确关闭工具调用。
  • 使用 "required":当前请求必须返回至少一个工具调用。

如果只提供一个工具,"required" 通常足以要求模型调用该工具。如果提供多个工具,请在应用侧检查返回的函数名是否符合当前业务动作。

警告

deepseek-v4-pro,对象形式的强制 tool_choice 是已知问题,会触发 HTTP 500:

language-json
{
  "tool_choice": {
    "type": "function",
    "function": {
      "name": "create_order"
    }
  }
}

使用 deepseek-v4-pro 时,tool_choice 只使用字符串值:"none""auto""required"。如果必须指定某个函数名,请改用已验证支持该请求形态的模型,或在应用侧检查返回的 tool_calls 是否命中目标函数。

让模型生成工具参数

建议先用 curl 发送一次等价请求。这样可以确认目标模型是否接受当前 toolstool_choice 形状,也方便排查 SDK 是否改写了参数。

以下 curl 命令适用于 bash/zsh 等 POSIX 风格 Shell。请先设置 GENSTUDIO_API_KEY 环境变量;Windows 用户请按所用 Shell 语法调整换行和环境变量。

language-shell
curl --request POST \
  --url "https://cloud.infini-ai.com/maas/v1/chat/completions" \
  --header "Authorization: Bearer $GENSTUDIO_API_KEY" \
  --header "Content-Type: application/json" \
  --data-raw '{
    "model": "deepseek-v4-flash",
    "messages": [
      {
        "role": "user",
        "content": "Create an order for Alice buying 3 notebooks for 12.50 CNY on 2026-05-14. Use the tool."
      }
    ],
    "temperature": 0,
    "max_tokens": 2048,
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "create_order",
          "description": "Create an order record.",
          "strict": true,
          "parameters": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "buyer": { "type": "string" },
              "item": { "type": "string" },
              "quantity": { "type": "integer" },
              "total": { "type": "number" },
              "currency": { "type": "string", "enum": ["CNY", "USD"] },
              "order_date": { "type": "string" }
            },
            "required": ["buyer", "item", "quantity", "total", "currency", "order_date"]
          }
        }
      }
    ],
    "tool_choice": "required"
  }'

预期响应中,工具调用位于:

language-text
choices[0].message.tool_calls[0]

工具参数通常是 JSON 字符串:

language-text
choices[0].message.tool_calls[0].function.arguments

解析后应类似:

language-json
{
  "buyer": "Alice",
  "item": "notebooks",
  "quantity": 3,
  "total": 12.5,
  "currency": "CNY",
  "order_date": "2026-05-14"
}

执行工具并返回结果

模型返回工具参数后,应用才进入真正的工具执行流程。执行前先校验参数;执行后按消息顺序把工具结果返回给模型。

在执行工具前校验参数

模型请求返回 200 后,也需要先检查工具参数。请从 tool_calls[0].function.arguments 读取 JSON 字符串,解析成对象后,再按工具 schema 和业务规则校验。

至少检查:

  1. JSON 能被解析。
  2. 必填字段存在。
  3. 字段类型、枚举值和数值范围正确。
  4. 用户有权限执行该动作。
  5. 金额、数量、日期、幂等键等业务规则通过。

参数校验失败时,停止工具执行。记录模型 ID、请求 ID、工具名、原始参数摘要和校验错误,再进入重试、降级或人工复核流程。

把工具结果返回给模型

工具执行后,需要把两类消息按顺序放回 messages:先追加模型刚才返回的 assistant 工具调用消息,再追加对应的 tool 结果消息。

顺序必须保持不变:

language-text
user message
assistant message with tool_calls
tool message with matching tool_call_id

如果一次响应包含多个工具调用,请为每个 tool_call_id 追加一个对应的 tool 消息。工具调用顺序和模型返回的 tool_call_id 都应保持原样。

使用 Python SDK 完成完整工具循环

Python 示例使用 OpenAI SDK。示例会让模型生成订单参数,应用校验并模拟执行工具,再把工具结果返回给模型获取最终回答。

代码按四段组织:第一次请求生成工具参数,校验参数,追加工具结果,第二次请求生成最终回答。

language-python
import json
import os

from openai import OpenAI


MODEL = "deepseek-v4-flash"

client = OpenAI(
    api_key=os.environ["GENSTUDIO_API_KEY"],
    base_url=os.environ.get("GENSTUDIO_BASE_URL", "https://cloud.infini-ai.com/maas/v1"),
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "create_order",
            "description": "Create an order record.",
            "strict": True,
            "parameters": {
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "buyer": {"type": "string"},
                    "item": {"type": "string"},
                    "quantity": {"type": "integer"},
                    "total": {"type": "number"},
                    "currency": {"type": "string", "enum": ["CNY", "USD"]},
                    "order_date": {"type": "string"},
                },
                "required": [
                    "buyer",
                    "item",
                    "quantity",
                    "total",
                    "currency",
                    "order_date",
                ],
            },
        },
    }
]

messages = [
    {
        "role": "user",
        "content": "Create an order for Alice buying 3 notebooks for 12.50 CNY on 2026-05-14. Use the tool.",
    }
]


def parse_order_args(raw_args: str) -> dict:
    args = json.loads(raw_args)
    required = {"buyer", "item", "quantity", "total", "currency", "order_date"}
    missing = sorted(required - args.keys())
    if missing:
        raise ValueError(f"Missing required fields: {missing}")

    if not isinstance(args["buyer"], str) or not args["buyer"].strip():
        raise ValueError("buyer must be a non-empty string")
    if not isinstance(args["item"], str) or not args["item"].strip():
        raise ValueError("item must be a non-empty string")
    if not isinstance(args["quantity"], int) or args["quantity"] < 1:
        raise ValueError("quantity must be a positive integer")
    if not isinstance(args["total"], (int, float)) or args["total"] < 0:
        raise ValueError("total must be a non-negative number")
    if args["currency"] not in {"CNY", "USD"}:
        raise ValueError("currency is not supported")

    return args


def create_order(buyer: str, item: str, quantity: int, total: float, currency: str, order_date: str) -> dict:
    return {
        "success": True,
        "order_id": "SO-20260514-001",
        "buyer": buyer,
        "item": item,
        "quantity": quantity,
        "total": total,
        "currency": currency,
        "order_date": order_date,
    }


# 1. 请求模型生成工具参数。
first_response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    temperature=0,
    max_tokens=2048,
    tools=tools,
    tool_choice="required",
)

# 2. 按原样保留 assistant 工具调用消息。
assistant_message = first_response.choices[0].message
tool_calls = assistant_message.tool_calls or []
if not tool_calls:
    raise RuntimeError("The model did not return tool_calls.")

messages.append(
    {
        "role": "assistant",
        "content": assistant_message.content or "",
        "tool_calls": [
            {
                "id": tool_call.id,
                "type": tool_call.type,
                "function": {
                    "name": tool_call.function.name,
                    "arguments": tool_call.function.arguments,
                },
            }
            for tool_call in tool_calls
        ],
    }
)

# 3. 校验参数、执行工具并追加工具结果。
for tool_call in tool_calls:
    if tool_call.function.name != "create_order":
        raise RuntimeError(f"Unexpected tool: {tool_call.function.name}")

    order_args = parse_order_args(tool_call.function.arguments)
    tool_result = create_order(**order_args)

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

# 4. 请求模型生成最终回答。
final_response = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    temperature=0,
    max_tokens=1024,
    tools=tools,
)

print(final_response.choices[0].message.content)

这段代码的重点是消息顺序和参数校验:应用先保存 assistant 的 tool_calls,再追加 tool 结果,然后再请求模型生成最终回答。create_order 只是用于演示工具执行结果。

处理流式和推理模型

流式响应和推理模型会改变解析方式或历史回传要求。先跑通普通非流式工具循环,再进入本节处理这些扩展场景。

解析普通流式工具调用

流式响应中,工具参数可能分多段到达。请先累积完整 function.arguments,等流结束后再解析 JSON。

解析时还要处理空 choices。部分响应会在最后一个 chunk 中只返回 usage,此时 choices 可能为空。

language-python
stream = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    temperature=0,
    max_tokens=2048,
    tools=tools,
    tool_choice="required",
    stream=True,
)

content_parts = []
reasoning_parts = []
final_tool_calls = {}
active_index = None
next_index = 0

for chunk in stream:
    if not chunk.choices:
        continue

    delta = chunk.choices[0].delta

    if getattr(delta, "content", None):
        content_parts.append(delta.content)

    if getattr(delta, "reasoning_content", None):
        reasoning_parts.append(delta.reasoning_content)

    for tool_call in getattr(delta, "tool_calls", None) or []:
        index = getattr(tool_call, "index", None)
        if index is None:
            if getattr(tool_call, "id", None) or active_index is None:
                active_index = next_index
                next_index += 1
            index = active_index
        else:
            active_index = index

        current = final_tool_calls.setdefault(
            index,
            {"id": "", "type": "function", "function": {"name": "", "arguments": ""}},
        )

        if getattr(tool_call, "id", None):
            current["id"] = tool_call.id
        if getattr(tool_call, "type", None):
            current["type"] = tool_call.type

        function = getattr(tool_call, "function", None)
        if function:
            if getattr(function, "name", None):
                current["function"]["name"] = function.name
            if getattr(function, "arguments", None):
                current["function"]["arguments"] += function.arguments

tool_calls = [final_tool_calls[index] for index in sorted(final_tool_calls)]
for tool_call in tool_calls:
    args = json.loads(tool_call["function"]["arguments"])
    print(tool_call["function"]["name"], args)

每个 arguments 片段只是同一次工具调用的一部分。拼接完成后的 function.arguments 才适合解析和校验。

为 GLM 启用流式工具参数输出

tool_stream 不是所有模型都支持。该参数仅限 GLM-5.1、GLM-5、GLM-5-Turbo、GLM-4.7 和 GLM-4.6 系列使用。需要实时展示推理过程、回复内容和工具参数时,可以在这些模型上启用。

请求需要同时满足:

  1. streamtrue
  2. 传入 GLM 的 tool_stream: true
  3. 使用支持工具调用和流式工具输出的 GLM 模型。

使用 OpenAI SDK 时,可通过 extra_body 传入 GLM 特有参数:

language-python
stream = client.chat.completions.create(
    model="glm-5.1",
    messages=messages,
    tools=tools,
    tool_choice="required",
    stream=True,
    extra_body={"tool_stream": True},
)

使用 curl 时,tool_stream 是请求体中的顶层字段。下面只展示关键字段;完整请求仍需要包含 messagestools

language-json
{
  "model": "glm-5.1",
  "tool_choice": "required",
  "stream": true,
  "tool_stream": true
}

GLM 流式响应中的 delta 可能同时包含 reasoning_contentcontenttool_calls。解析方式仍然是:跳过空 choices,累积内容片段,按 index 合并工具调用,等流结束后再解析工具参数。

如果目标模型返回 400,或没有返回流式工具参数,请去掉 tool_stream,先按普通流式工具调用路径验证。

在推理模型中保留必要历史字段

部分推理模型会在工具调用旁边返回推理字段。是否需要在后续请求中回传这些字段,取决于模型系列和参数。

不同模型系列使用不同的推理字段和历史回传规则。接入 GLM、Kimi、MiMo、Qwen 或 DeepSeek 等推理模型时,先按 Reasoning 中对应模型系列的规则处理历史推理内容。

如果某个模型要求回传推理内容,请完整保留模型返回的 assistant 消息字段,包含推理字段和 tool_calls

排查工具调用失败

遇到工具调用失败时,先定位失败发生在哪一步。

  1. 请求被拒绝:记录 HTTP 状态码、响应体、模型 ID、tool_choice、工具 schema。
  2. 没有 tool_calls:检查模型是否支持工具调用、tool_choice 是否为 "none"、prompt 是否明确需要工具。
  3. 工具名不符合预期:减少可用工具数量,或在应用侧拒绝不符合当前业务动作的工具名。
  4. arguments 无法解析为合法 JSON:降低 schema 复杂度,重试一次,并记录原始参数摘要。
  5. 参数校验失败:停止工具执行;按业务风险选择重试、降级或人工复核。
  6. 流式解析结果异常:确认没有把空 choices 当成错误,也没有把参数片段当成独立工具调用。
  7. 推理模型多轮工具调用报错:检查是否按对应模型系列要求回传历史推理字段。

每次失败都应记录响应头中的 traceresponse、响应体中的 idrequest_id、模型 ID、工具名和 schema 版本。关于请求标识,参见 请求标识

相关阅读

  • Structured output — 当只需要稳定 JSON 结果、不需要执行外部工具时使用。
  • Reasoning — 按模型系列选择推理参数,并在工具调用历史中正确回传推理内容。