Function Calling 与 MCP(工具调用)
工具调用是 Agent 的地基。面试不只考「流程是什么」,更考工程细节:schema 怎么设计、并行调用什么时候开、失败怎么兜底、模型为什么会用工具。本文从机制讲到最佳实践,MCP 协议的完整深挖见 MCP 协议深入。
Function Calling 完整机制
Function Calling 是让模型「调用外部函数/工具」的能力。核心认知:模型本身不执行函数,它只输出「我想调用哪个函数、用什么参数」这一结构化意图,真正执行由你的代码完成。
开发者 模型 你的程序
│ ① 注册工具 schema │ │
│ ───────────────────► │ │
│ ② 用户提问 │ │
│ ───────────────────► │ ③ 返回 tool_calls │
│ │ (函数名 + JSON 参数) │
│ │ ───────────────────────► │ ④ 校验并真正执行
│ │ ⑤ 结果作为 tool 消息回传 │
│ │ ◄─────────────────────── │
│ ⑥ 最终自然语言回答 │ (或继续发起下一次调用 → 循环)
│ ◄─────────────────── │第 ③~⑤ 步可以循环多轮——这个「模型决策 → 执行 → 观察结果 → 再决策」的循环就是 Agent 的最小骨架(ReAct 范式,见 Agent 基础)。
模型为什么会用工具?
工具调用能力是训练出来的,不是 prompt 魔法:
- 后训练阶段用大量「对话 + 工具调用轨迹」数据做 SFT/RL,模型学会输出特定格式(通常由特殊 token 或固定模板包裹的 JSON)。
- 推理时,工具 schema 被注入到上下文(相当于 system prompt 的一部分),模型「看着说明书点菜」。
- 因此 description 是模型决定「用不用、怎么用」的唯一依据——工具描述写得好不好,直接决定调用准确率。
- 很多推理框架还会配合约束解码(限制输出必须符合 JSON Schema),从机制上保证参数格式合法,见 结构化输出详解。
工具 Schema 设计最佳实践
{
"name": "search_orders",
"description": "按条件查询用户订单。适用于用户询问订单状态、物流、历史购买记录。不适用于退款操作(用 refund_order)。",
"parameters": {
"type": "object",
"properties": {
"user_id": { "type": "string", "description": "用户唯一 ID" },
"status": {
"type": "string",
"enum": ["pending", "shipped", "completed"],
"description": "订单状态筛选,不传则查全部"
},
"limit": { "type": "integer", "description": "返回条数,默认 10,最大 50" }
},
"required": ["user_id"]
}
}| 原则 | 说明 |
|---|---|
| 描述写「何时用 + 何时不用」 | 模型靠 description 路由,边界写清楚能显著减少误调用 |
| 能用枚举就用枚举 | enum 比自由字符串可靠得多,约束解码可直接强制 |
| 参数少而扁平 | 深层嵌套对象出错率高;超过 5~7 个参数考虑拆工具 |
| 命名动宾结构 | search_orders 优于 orders / tool1 |
| 返回结果也要设计 | 给模型看的结果要精简、结构化、含单位和字段说明,垃圾进垃圾出 |
| 幂等与只读标注 | 区分读操作与写操作,写操作考虑加确认环节 |
进阶机制
tool_choice(调用控制):auto(模型自行决定,默认)/ required(必须调用某个工具)/ 指定具体函数 / none。强制调用适合「这一步必然要查库」的工作流节点;但滥用 required 会让模型在无合适工具时硬编参数。
并行工具调用(Parallel Tool Calls):模型一次返回多个相互独立的 tool_calls(如同时查天气和汇率),程序并发执行后一起回传。能省多轮往返、降延迟;但有依赖关系的调用(先查 ID 再用 ID 查详情)不能并行,模型偶尔会误并行,需要在描述中写明依赖。
多轮工具循环的工程要点:设最大轮数上限防死循环;每轮把工具结果追加进消息历史(注意上下文膨胀,长结果要截断/摘要,见 上下文工程);流式场景下 tool_calls 的参数是分片到达的,要拼完整再解析。
错误处理与鲁棒性
- 参数校验前置:执行前用 JSON Schema 校验,非法参数不执行。
- 错误信息回传给模型:把「参数 user_id 缺失」「API 超时」作为 tool 结果返回,模型通常能自我修正重试——这比直接报错给用户体验好得多。
- 重试与超时:工具侧设超时与重试上限;连续失败 N 次则降级(告知用户/换工具)。
- 安全边界:工具返回的内容是不可信数据——网页、邮件等外部内容可能包含注入指令(间接 Prompt 注入),不要让模型把工具结果当作指令执行;高危操作(转账、删除)必须人工确认。详见 大模型安全。
工具一多就乱:规模化管理
- 控制单次可见工具数:几十上百个工具全塞上下文,路由准确率和成本都会恶化;经验上单次 10~20 个以内为宜。
- 工具检索(Tool RAG):把工具描述向量化,按用户意图先检索相关工具子集再注入。
- 分层/分组:按域拆分 Agent(订单 Agent、客服 Agent),或用「先选组、再选工具」的两级路由。
- 评估:工具调用准确率有专门基准(如 BFCL,伯克利函数调用排行榜),业务上应建自己的调用评估集(该调时调了吗、函数选对了吗、参数填对了吗)。
MCP(Model Context Protocol)
MCP 是 Anthropic 于 2024 年提出的开放标准协议,规范「LLM 应用 ↔ 外部工具/数据源」的连接,被称为「AI 应用的 USB-C」。
解决的问题:此前每个应用接每个工具都要定制集成,是 M×N 组合爆炸;MCP 统一标准后,工具方实现一个 MCP Server、应用方实现一个 MCP Client,变成 M+N。
核心架构:Host(AI 应用)— Client(连接管理)— Server(能力提供方),Server 暴露三大原语:
- Tools:可被模型调用的函数(对应 Function Calling 的工具)
- Resources:可读取的数据/文件
- Prompts:预定义提示模板
与 Function Calling 的关系(高频考点):
| Function Calling | MCP | |
|---|---|---|
| 层面 | 模型能力:如何表达调用意图 | 生态协议:工具如何标准化提供与发现 |
| 定义方 | 各模型厂商 API | 开放标准(跨厂商) |
| 关系 | MCP Client 拿到工具清单后,仍通过 FC 让模型发起调用 | MCP 在 FC 之上解决集成与复用 |
一句话:FC 是「模型会点菜」,MCP 是「统一的菜单格式和上菜通道」。MCP 的传输方式(stdio/Streamable HTTP)、生命周期、鉴权与安全风险见 MCP 协议深入。
高频追问
Q:模型会真的执行函数吗? 不会。模型只输出调用意图(函数名+参数 JSON),执行由外部代码完成,结果再回传给模型。这是最常见的概念辨析题,务必答清楚。
Q:为什么要专门的 Function Calling 接口,而不是在 prompt 里说「请输出 JSON」? 三个原因:① 模型在后训练中专门学过 FC 的特殊格式,可靠性远高于自由文本模仿;② 接口层可配合约束解码,从机制上保证 JSON 合法;③ 结构化的 tool_calls 字段便于程序解析与多轮管理,不用从自然语言里抠 JSON。
Q:Function Calling 失败/参数错误怎么办? 校验前置(JSON Schema)、把错误信息作为工具结果回传让模型自修正、设重试与轮数上限、降级路径(换工具或转人工)、记录失败 case 回流评估集。
Q:并行工具调用什么时候开、有什么坑? 相互独立的查询类调用开并行能显著降延迟;坑在于模型可能把有依赖的调用误判为可并行(用了还没拿到的结果),以及写操作并行的副作用顺序问题。实践:读操作放开并行,写操作串行+确认。
Q:工具太多模型选错怎么办? 控制单次可见数量、Tool RAG 动态检索、清晰命名与「何时用/不用」描述、分层路由、用调用评估集持续度量。
Q:怎么评估一个 Agent 的工具调用能力? 拆三层:是否该调(触发判断)、调哪个(函数选择)、参数对不对(槽位填充);公开基准可参考 BFCL,业务上用自建评估集 + 线上 bad case 回流,见 模型评估。