Function Calling 深度:从工具调用到可靠执行
Function Calling 是 Agent 影响真实世界的接口,但"模型说我要调工具"和"可靠地执行完"之间隔着好几道工程关。本文讲清它的工作机制、并行与多轮调用、参数校验与错误恢复,以及它和 MCP 的关系——把 function calling 从"能跑"用到"生产可靠"。
本文要点
- 主题聚焦:大模型、AI Agent
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Function Calling(工具调用)是 LLM 从"会聊天"变成"能干活"的那道门。模型不再只输出文本,而是能声明"我要调用这个函数,参数是这些",由你的代码去执行、把结果回灌给模型。整个 Agent 生态都建立在它之上。
但"模型说要调工具"和"可靠地把这件事执行完"之间,隔着好几道工程关。这篇把这些关讲透。
一、工作机制:一次完整的工具调用
一次 function calling 的完整往返:
1. 你把 tools 定义 + 用户问题发给模型
2. 模型决定调用 → 返回 tool_call: {name, arguments}
3. 你的代码执行这个函数,拿到 result
4. 把 result 作为 tool 消息回灌给模型
5. 模型基于 result 继续推理(给答案,或再调一个工具)
代码长这样:
const response = await client.messages.create({
model, system, messages, tools,
});
// 模型要调工具
if (response.stop_reason === "tool_use") {
const toolCall = response.content.find(c => c.type === "tool_use");
const result = await dispatch(toolCall.name, toolCall.input); // 你的执行器
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: [{ type: "tool_result",
tool_use_id: toolCall.id, content: JSON.stringify(result) }] });
// 第二轮:带结果再问模型
const final = await client.messages.create({ model, system, messages, tools });
}
关键认知:模型只是"声明"要调,真正执行的是你的代码。 这意味着安全、鉴权、副作用全部在你这一侧——这是 function calling 工程化的核心,也是 prompt injection 攻防的关键战场。
二、并行调用与多轮
现代模型支持一次返回多个 tool_call(并行调用)。比如用户问"查北京和上海的天气",模型可以一次返回两个 get_weather 调用,你的执行器并行执行,把两个结果一起回灌。这比串行(查完北京再查上海)快得多。
多轮调用则是另一回事:模型调用 A → 看结果 → 决定再调 B → 再看结果 → 给答案。这就是 Agent 的本质循环——它自主决定下一步调什么,直到任务完成。
工程上要注意:
- 并行调用要全部完成后一起回灌,别回灌一部分。
- 多轮循环必须有上限(最大轮数/最大 token/最大耗时),否则模型可能"调→失败→再调"死循环烧钱。
三、可靠执行的四道关
模型说"调 transfer_money(from, to, amount)",不代表你该照做。从声明到执行,四道关:
1. 参数校验。 模型可能给出非法参数(负数金额、不存在的账号、格式错的邮箱)。执行前用 schema validator(zod)校验,不通过就把错误回灌给模型让它修正,而不是直接报错给用户。
const schema = z.object({ from: z.string(), to: z.string(), amount: z.number().positive() });
const parsed = schema.safeParse(args);
if (!parsed.success) {
return { error: parsed.error.message }; // 回灌,模型会改参数重试
}
2. 权限与授权。 模型决定调用 ≠ 已授权。执行前校验:当前用户有没有权限对这个资源做这个操作?写操作要不要人工确认?"确认过一次"不等于永久授权,参数变了要重新确认。
3. 副作用与幂等。 工具调用可能失败、可能重试。create_order 如果不幂等,重试会下两单。尽量把工具设计成幂等(带 idempotency key),或区分"可重试"和"不可重试"的操作。
4. 错误回灌。 工具失败时,把可读的错误信息回灌给模型("余额不足""无权限"),而不是抛异常中断。Agent 往往能根据错误自行换方案——这是它比"硬编码流程"灵活的地方。只有真正的系统错误才降级到人工/规则。
四、工具定义的质量决定一切
模型选不选调、调得对不对,九成取决于工具定义的质量:
- description 写给模型看,要讲清"什么情况用、不用什么替代、参数怎么填"。模糊的描述是 Agent 跑偏的头号原因。
- 参数 schema 要严,required 标清,enum 列全,加描述。这是模型填参数的唯一依据。
- 工具粒度要对。太粗(一个
manage_users干所有事)模型填不对参数;太细(get_user_id→get_user_name→ … 要调十次)效率低。一个工具做一件清晰的事。
经验:为工具定义投入的时间,回报远高于调 prompt。工具描述清楚、schema 严格,模型表现立竿见影地稳。
五、Function Calling 和 MCP 的关系
很多人混淆这两者。简单说:
- Function Calling 是模型 API 的能力(模型能声明要调函数)。
- MCP 是能力暴露的协议标准(让"工具集"可复用、跨客户端)。
MCP 的底层往往就是 function calling——MCP Server 暴露的 tools,到了客户端(Claude Code、Cursor)会被转成模型的 function 定义,模型照样用 function calling 调。MCP 是"工具的分发和标准化"层,function calling 是"模型调用工具"的机制层。 两者是互补,不是替代。
理解这个关系,你就知道:自己写一个小 Agent,直接用 function calling 就够;要让你的工具被整个生态复用,就把它封装成 MCP Server。
小结
Function Calling 是 Agent 的执行引擎,但"能跑"和"可靠"之间隔着工程:
- 机制:模型声明 → 你的代码执行 → 结果回灌。
- 并行/多轮:并行要一起回灌,多轮必须有上限。
- 四道关:参数校验、权限授权、幂等副作用、错误回灌。
- 定义质量:description + schema 决定模型调用准确率。
- 和 MCP:function calling 是机制,MCP 是分发标准。
把这几件事做扎实,你的 Agent 才能从"demo 里调通一两次"变成"生产里稳定执行千万次"。记住核心那句话:模型只负责声明,执行的全部责任在你——这也是为什么 function calling 既是 Agent 的力量之源,也是它最大的工程责任所在。
Related