Function Calling 原理
模型如何选择工具、填写参数、读取结果并继续判断。
一句话理解:模型从来不亲自执行工具。它只会输出一段结构化的“调用请求”(调哪个工具、填什么参数),由你的程序去执行,再把结果交回给模型继续判断。
很多人第一次看到 Agent 查订单、发邮件、改代码,会以为模型“连上了系统”。其实模型仍然只会生成文本——只不过这段文本被约定成了机器可读的 JSON。真正碰到数据库、API 和文件系统的,始终是你写的程序。
弄懂这一点很关键:权限应该加在哪、错误从哪来、成本花在哪、为什么 Agent 会“编造”一个不存在的参数,答案都藏在这个往返过程里。
先看全景:下图把本模块的核心概念放在一张图里——模型读工具定义做判断,程序负责执行与把关,工具设计、安全与编排是让它可靠运行的支撑体系。
直觉:模型是点菜的人,程序是后厨
把一次工具调用想象成在餐厅点菜:
- 菜单是工具定义:每道菜叫什么、是什么、可以选什么口味。模型只能从菜单上点。
- 点菜单是模型输出的调用请求:“我要
get_order,参数order_id = ORD2026090142”。 - 后厨是你的程序:检查点菜单合不合法、有没有这道菜、这位客人能不能点,然后真正去做。
- 上菜是工具结果:程序把 JSON 结果送回对话里,模型看到结果后决定继续点菜,还是直接回答。
所以,模型负责 判断(要不要调用、调用哪个、参数怎么填、结果说明了什么),程序负责 执行与把关。这就是 一个 Agent 由什么构成 里 Model 与 Tools 的分工。
一次完整的往返
以电商客服场景为例,用户问:“订单 ORD2026090142 为什么还没发货?”
整个往返过程如下图:
几个容易忽略的细节:
- 每次请求都要带上工具定义。 模型没有记忆,工具的名称、描述和参数 Schema 每次都会作为输入 Token 计费。工具越多、描述越长,成本越高(详见 Token 与上下文窗口)。
- 一轮可能有多个调用。 模型可以在同一轮里同时请求
get_order和get_payment,你要把每个结果都对应回去,见 并行调用与调用链。 - 循环由程序驱动。 只要模型还在请求工具,程序就执行并回填;模型给出普通回答时循环结束。这正是 Agent Loop 详解 的最小形态。
两家主流 API 的字段对照
概念一样,字段名不同。评审研发方案或读 Trace 时,认得这些字段就够了(以下对照 Claude Messages API 与 OpenAI Responses API 的官方文档):
| 环节 | Claude(Messages API) | OpenAI(Responses API) |
|---|---|---|
| 声明工具 | tools:name、description、input_schema |
tools:type: "function"、name、description、parameters |
| 模型发起调用 | 响应的 stop_reason 为 tool_use,content 里有 tool_use 块(id、name、input 对象) |
output 里有 function_call 项(call_id、name、arguments 为 JSON 字符串) |
| 回传结果 | 下一条 user 消息里放 tool_result 块(tool_use_id、content,出错时 is_error: true) |
输入里追加 function_call_output 项(call_id、output) |
| 控制是否调用 | tool_choice:auto / any / tool / none |
tool_choice:auto / required / 指定函数 / none,另有 allowed_tools |
| 严格匹配 Schema | 工具定义上加 strict: true |
工具定义上加 strict: true |
| 关闭并行调用 | tool_choice 里设 disable_parallel_tool_use: true |
parallel_tool_calls: false |
下面是 Claude 格式下的一轮真实结构(精简版):
{
"stop_reason": "tool_use",
"content": [
{ "type": "text", "text": "我先查一下这个订单的状态。" },
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_order",
"input": { "order_id": "ORD2026090142" }
}
]
}
程序执行后回传:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "{\"status\":\"pending_shipment\",\"reason\":\"payment_pending\",\"updated_at\":\"2026-09-16 18:02\"}"
}
]
}
Claude 的文档特别强调两条格式规则:tool_result 必须紧跟在包含 tool_use 的那条助手消息之后;同一条用户消息里,所有 tool_result 要排在文字前面。违反时会直接报 400 错误。
模型如何“选择”工具
模型能看到的只有三样东西:工具名称、描述、参数 Schema,加上对话上下文和你的系统指令。它不会读你的源码,也不知道接口背后连的是哪张表。因此:
- 选错工具,多半是 描述写得含糊,或两个工具职责重叠(
get_order与query_order_info并存)。 - 参数填错,多半是 Schema 没说清格式,比如没说订单号是
ORD开头加 10 位数字。 - 该调不调、不该调乱调,可以通过系统指令和
tool_choice调节。默认的auto让模型自己决定;强制调用适合“这一步必须查数据”的环节。
还要记住,参数是模型 生成 出来的,可能缺字段、类型不对,甚至凭空猜一个值。Claude 文档就提到,缺少必填信息时,有的模型会追问用户,有的会自己推断一个看似合理的值。所以两道防线必不可少:开启 strict 让参数严格符合 Schema,程序在执行前再做一次业务校验(订单是否存在、用户有没有权限查)。这两件事分别在 工具定义三要素 和 安全的工具设计 里展开。
常见误区
- “模型调用了我们的 API。” 调用 API 的是程序。模型只提出请求,程序可以拒绝、改写、要求人工确认。这是所有权限设计的前提。
- “开了 Function Calling,参数就一定对。” 不开
strict时,Schema 只是尽力遵循;开了strict也只保证 格式 对,不保证 业务上 对(格式正确的订单号可能根本不属于这个客户)。 - “工具结果是可信的。” 工具返回的网页、邮件、工单备注可能夹带恶意指令。结果要当作不可信数据处理,见 Prompt 注入。
产品经理检查清单
- 这个 Agent 需要哪些工具?每个工具对应用户 Job 里的哪一步?
- 模型每一轮能看到哪些工具?工具定义大约占多少 Token?
- 模型给出的参数,程序在执行前会做哪些校验?
- 工具失败时,返回给模型的错误信息能不能帮它修正下一步?
- 一次任务最多允许多少轮调用?超过之后怎么收尾(见 停止条件)?
- Trace 里能不能看到每次调用的名称、参数、结果和耗时?
练习
不写代码,只用 JSON 在纸上走完一次往返(约 30 分钟):
- 为电商订单场景写一个
get_order工具定义(名称、描述、参数 Schema)。 - 假设用户问“ORD2026090142 卡在哪一步了”,写出模型应该返回的
tool_use块。 - 编一个订单系统的返回 JSON,写出对应的
tool_result消息。 - 再写一个“订单号不存在”的
tool_result,设置is_error: true,并让错误信息告诉模型下一步该怎么做。
如果你已经完成 实操:调通第一个 API,可以把第 3 步换成真实接口的返回。
延伸阅读
- Tool use with Claude:Claude 工具调用总览,含完整往返示例。
- Handle tool calls:
tool_result格式规则与is_error用法。 - Function calling(OpenAI):Responses API 的函数调用流程、
tool_choice与 strict 模式。
下一步
模型只能通过名称、描述和 Schema 理解工具。下一课「工具定义三要素」讲怎么把这三样写好。