Skip to content

Function Calling:让模型真的能调你的函数 ​

模型本身只会输出文本,读不了数据库、查不了天气、更下不了单。Function Calling(函数调用,现在也常叫 Tool Calling/工具调用)就是给它一份可调用的接口清单,让它用结构化 JSON 告诉你:我要调哪个函数、传什么参数。真正执行的人是你。这篇把这条链路完整走一遍。

模型只负责说,不负责做 ​

关键认知:它没有任何执行能力。模型见过海量函数签名,于是学会按 JSON Schema(一种描述 JSON 结构的规范)的约束输出调用意图。你可以把它理解成:把 TypeScript 函数的签名和注释改写成接口文档递给它,它回你一段能直接当参数用的 JSON 字符串。参数是字符串形式的 JSON,不是对象,必须自己解析,而且可能失败——官方文档明确说模型不保证输出合法 JSON,也可能编造你没定义的字段,执行前务必校验。

一次完整回合:五步走 ​

  1. 带着 tools 数组发请求,里面是允许它调用的函数;
  2. 模型返回 tool_calls,含函数名和参数;
  3. 你在本地真正执行这个函数;
  4. 把结果作为一条 tool 消息回填进对话历史;
  5. 再发一次请求,模型读完结果给出最终回答。

第 4 步最容易翻车:回填的消息必须带上 tool_call_id,和上一步的 id 对上号。注意各家叫法不同,OpenAI 风格里旧的 function_call 字段已标记废弃,官方建议用 tool_calls;Anthropic 风格差别更大,工具定义是扁平的 name/description/input_schema(不是 parameters),响应里是 content 中的 tool_use 块,回填则是带 tool_use_id 的 tool_result。字段名以各家官方文档为准,别混着抄。

json
{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "查询某个城市当前的天气。用户问天气、温度、是否下雨时使用。",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {
          "type": "string",
          "description": "城市名,中文或拼音,例如「杭州」"
        },
        "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
      },
      "required": ["city"]
    }
  }
}

描述写得清楚,模型才不乱填 ​

description 是给模型看的注释,写它就等于写函数文档。要点:说清「什么时候该用」,而不只是「这是干嘛的」;每个参数都补说明和例子,「城市名,中文或拼音」比干巴巴一个 string 有用得多;能枚举就用 enum 收窄取值;必填项老实标进 required。官方的验收标准可以借用:实习生只拿到这些信息能正确调用吗?不能就把缺的补进描述里。

并行调用与多轮循环 ​

模型一次可能返回多个 tool_calls,比如同时查北京和上海,这就是并行调用(parallel tool calling)——能并发就并发,再把多条结果一起回填。任务也可能需要好几轮:先查接口拿 id,再拿 id 查详情,直到模型不再返回工具调用、而是给出一段人话。所以外面通常要包一个循环,并设最大轮数上限,防止它绕不出来。

出错也要回填,别直接抛给用户 ​

接口超时、参数没过、权限不足都很常见。正确做法是把错误当成一种结果回填给模型,比如告知「调用失败:city 参数为空」,让它换参数重试或改用别的工具,必要时再向用户解释。直接把原始报错甩到用户脸上,等于浪费了它的纠错能力。Anthropic 的 tool_result 还支持 is_error 标记,可以显式标错。

ts
const result = await runTool(call);
history.push({
  role: "tool",
  tool_call_id: call.id,
  content: result.ok ? JSON.stringify(result.data) : `调用失败:${result.error}`,
});

危险操作必须人工确认 ​

读写要分开看。查天气、查列表这类只读工具可以放开自动执行;删数据、转账、发邮件、改线上配置这类有副作用的操作,执行前一定要人工确认:把模型想调的函数和参数摊开给人看,人点了同意再执行。别把「模型说的」当成「已授权的」。

常见坑

  1. 参数是字符串不是对象,直接当对象用会炸,记得 JSON.parse 并包 try/catch。
  2. 忘了回填 tool_call_id/tool_use_id,模型对不上号会直接报错。
  3. 工具描述含糊,模型就会乱调,或者该调的时候不调。
  4. 一次暴露几十个工具,选择准确率明显下降,官方建议单轮控制在 20 个以内(软建议)。
  5. 各家字段名不通用,复制代码前先看对应厂商的官方文档。
最近更新