---
url: /agents/function-calling.md
description: >-
  面向前端工程师讲清 Function
  Calling（函数调用）的完整链路：工具定义怎么写、参数描述为什么最关键、并行调用与多轮循环怎么跑、错误结果为什么要回填，以及哪些危险操作必须人工确认。
---

# 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}`,
});
```

## 危险操作必须人工确认

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

::: warning 常见坑

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