Function Calling 与 Tool Calling:从认知到工程的全景深度解析
Function Calling 与 Tool Calling:从认知到工程的全景深度解析
定位:本文面向已具备 LLM 基础认知的工程师,目标是将你对工具调用的理解从"会用 API"提升到"能设计系统"的层级。全文覆盖概念本质、协议演进、架构原理、训练机制、生产实践与前沿趋势,共 7 个认知层级。
阅读时间:约 25 分钟 |适合人群:AI 应用开发者、Agent 架构师、大模型平台工程师
一、破除第一个认知误区:LLM 没有"调用"任何东西
在深入任何技术细节之前,必须先钉死一个事实:
LLM 不执行代码、不发起网络请求、不操作数据库。它唯一做的事情是——生成 token。
所谓的 “Function Calling”,本质是:
LLM 输出一段符合 JSON Schema 的结构化文本 ↓ 你的 Runtime(应用层)解析这段文本 ↓ 你的 Runtime 执行真正的函数/API 调用 ↓ 将执行结果拼回 Prompt,再次请求 LLM ↓ LLM 基于结果生成最终自然语言回复LLM 是决策者(Decider),不是执行者(Executor)。它做的是"选择调哪个工具、传什么参数"这一推理任务,而真正的 I/O 操作发生在你的服务器进程里。
这个区分不是咬文嚼字——它直接决定了:
- 安全边界在哪里(你控制执行,而非模型)
- 延迟瓶颈在哪里(网络 I/O 而非推理)
- 错误处理该放在哪一层(你的代码里,不是 prompt 里)
二、术语澄清:Function Calling vs Tool Calling
这两个术语在 99% 的语境下指同一件事,但理解它们的历史分野有助于读懂不同厂商的文档:
| 维度 | Function Calling | Tool Calling |
|---|---|---|
| 提出时间 | 2023.06(OpenAIfunctions参数) | 2023.11(OpenAItools参数) |
| 语义范围 | 单个函数(一对一) | 任意工具(函数、API、检索器、代码解释器…) |
| 并行能力 | 不支持(一次只能调一个) | 原生支持并行调用多个 |
| 现状 | 已废弃(legacy) | 当前标准 |
| 使用厂商 | 早期 OpenAI 文档 | OpenAI / Anthropic / Google / 国内厂商统一 |
一句话总结:Function 是 Tool 的子集。现代工程代码中统一使用tools/tool_choice/tool_calls,遇到functions/function_call视为历史遗留。
三、协议演进时间线(2023 → 2026)
理解演进脉络,才能理解当前架构为什么长这样:
2023.06 OpenAI 发布 functions/function_call(单函数、串行) │ 2023.11 OpenAI 升级为 tools/tool_choice/tool_calls(多工具、支持并行) │ 2023.11 GPT-4 Turbo 发布,原生 Parallel Function Calling │ 2024.03 Anthropic Claude 发布 Tool Use(computer_use 前身) │ 2024.04 Google Gemini 支持 Function Calling(tool_config) │ 2024.11 Anthropic 开源 MCP(Model Context Protocol)v1 │ 2025.05 OpenAI Responses API 上线,内置 web_search/code_interpreter/file_search │ 同时支持远程 MCP Server 接入 │ 2025.06 MCP 2025-06-18 版发布(安全增强、elicitation 机制) │ 2025.11 MCP 2025-11-25 版(主流稳定版) │ 2026.07 MCP 2026-07-28 版发布(第 5 版规范,从有状态转向无状态) │ 被定性为"问世以来最大更新" │ 2026.xx 各模型厂商 Function Calling 能力趋于同质化, 竞争焦点转向:工具编排、安全沙箱、多模态工具关键转折点:MCP 的出现将工具调用从"每个应用自己写胶水代码"推进到"标准化即插即用",类比 USB-C 对充电线的统一。
四、一次 Tool Call 的完整生命周期
以 OpenAI Chat Completions API 为例,拆解五步循环:
Step 1:工具声明(开发者 → API)
{"model":"gpt-4o","messages":[{"role":"user","content":"北京明天会下雨吗?"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"获取指定城市未来N天的天气预报","parameters":{"type":"object","properties":{"city":{"type":"string","description":"城市名称,如'北京'"},"days":{"type":"integer","description":"预报天数","default":1}},"required":["city"]}}}],"tool_choice":"auto"}Step 2:模型决策(API → 开发者)
模型不返回自然语言,而是返回结构化调用意图:
{"choices":[{"message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":"{\"city\": \"北京\", \"days\": 1}"}}]},"finish_reason":"tool_calls"}]}⚠️注意:此时
content为null,finish_reason为"tool_calls"而非"stop"。这是判断模型是否发起工具调用的唯一可靠信号。
Step 3:应用层执行(开发者本地)
importjson tool_call=response.choices[0].message.tool_calls[0]func_name=tool_call.function.name func_args=json.loads(tool_call.function.arguments)# 你的业务逻辑result=get_weather(**func_args)# → {"temp": "28°C", "rain": False, ...}Step 4:结果回灌(开发者 → API)
{"role":"tool","tool_call_id":"call_abc123","content":"{\"temp\": \"28°C\", \"condition\": \"晴\", \"rain_probability\": 0.05}"}Step 5:模型二次推理 → 最终回复
{"choices":[{"message":{"role":"assistant","content":"明天北京天气晴朗,气温28°C,降雨概率仅5%,不需要带伞。"},"finish_reason":"stop"}]}这五步构成一个不可分割的原子循环。在 Agent 场景中,Step 2-4 可能迭代多次(模型连续调用多个工具),直到模型判断信息充足,输出finish_reason: "stop"。
五、tool_choice的四种策略与适用场景
这是控制模型行为最关键的旋钮,但很多开发者只用过"auto":
| 值 | 行为 | 适用场景 |
|---|---|---|
"auto" | 模型自主决定是否调工具 | 通用对话、不确定用户意图 |
"none" | 强制不调用任何工具 | 安全兜底、纯闲聊场景 |
"required" | 必须调用至少一个工具(但不指定哪个) | 你确定需要外部数据,但让模型选工具 |
{"type":"function","function":{"name":"xxx"}} | 强制调用指定工具 | 流程编排、确定性 pipeline |
大厂实践原则:
- 用户侧入口用
"auto"(保持灵活性) - 内部 pipeline 用强制指定(保证确定性)
- 永远不要在生产环境省略
tool_choice,显式声明意图
六、并行工具调用(Parallel Function Calling)
从 GPT-4 Turbo 开始,模型可以在单次响应中返回多个tool_calls:
"tool_calls":[{"id":"call_1","function":{"name":"get_weather","arguments":"{\"city\":\"北京\"}"}},{"id":"call_2","function":{"name":"get_weather","arguments":"{\"city\":\"上海\"}"}},{"id":"call_3","function":{"name":"search_flights","arguments":"{\"from\":\"PEK\",\"to\":\"SHA\"}"}}]工程要点:
- 多个调用之间无依赖关系,可并发执行(
asyncio.gather/Promise.all) - 回灌结果时,每个
toolmessage 必须携带对应的tool_call_id - 如果工具间有依赖(先查 ID 再查详情),模型会自动拆成多轮串行调用
性能收益:在"查多个城市天气 + 搜航班"这类场景下,并行调用可将延迟从串行的 3×RTT 降至 1×RTT + max(单次执行时间)。
七、多厂商协议差异对照表
这是面试高频考点,也是实际跨平台开发必须掌握的:
| 维度 | OpenAI | Anthropic (Claude) | Google (Gemini) |
|---|---|---|---|
| 参数名 | tools/tool_choice | tools/tool_choice | tools/tool_config |
| 工具类型 | type: "function" | type: "custom"/"computer_20250124" | function_declarations |
| 调用返回 | message.tool_calls[] | content[].type == "tool_use" | candidates[].content.parts[].functionCall |
| 结果回传 | role: "tool" | role: "user"+type: "tool_result" | role: "function"/functionResponse |
| 强制调用 | tool_choice: {function: {name}} | tool_choice: {type: "tool", name} | tool_config.function_calling_config |
| 并行调用 | ✅ 原生支持 | ✅ 支持(2025 补齐) | ✅ 支持 |
| 流式调用 | stream: true+ delta 拼接 | SSE +content_block_delta | SSE |
核心启示:底层机制完全同构,差异仅在字段命名和嵌套结构。如果你在做多模型适配,抽象层应该定义统一的ToolSpec和ToolResult接口,在 adapter 层做格式转换。
八、深层原理:模型是如何"学会"调用工具的?
8.1 训练层面
工具调用能力不是通过 prompt 临时注入的,而是经过专门训练:
SFT(监督微调)阶段:使用大量
(query, tool_schema) → tool_call_json的配对数据训练,让模型学会"看到工具描述 → 生成合规 JSON"的映射。格式约束:训练数据中严格约束输出格式——以
{开头、}结尾,key 只能是name/arguments等固定字段,参数值做类型校验。RLHF / RLAIF:通过人类反馈强化"该调就调、不该调别硬调"的决策能力,减少过度调用(over-calling)和遗漏调用(under-calling)。
拒识训练:明确训练模型在没有匹配工具时输出自然语言而非强行凑一个调用。
8.2 推理层面(Inference Time)
在推理时,工具定义的 JSON Schema 会被序列化后拼入 System Prompt(或等效的上下文位置)。模型本质上是在做一个受限生成任务:
[System] 你有以下工具可用:{tool_schema_json} [User] 用户问题 [Assistant] → 生成 token,但被 constrained decoding 限制在合法 JSON 空间内部分推理引擎(如 vLLM)使用Guided Decoding(基于 grammar / regex 的 token 级约束),确保输出 100% 符合 JSON Schema,而非仅靠模型"自觉"。
8.3 为什么 Schema 描述质量如此关键?
模型选择工具的依据完全来自你写的 description。这不是"文档写得好不好"的问题,而是"模型能不能正确理解你的工具"的问题:
// ❌ 差的描述{"name":"process","description":"处理数据"}// ✅ 好的描述{"name":"query_order_status","description":"根据订单号查询电商订单的当前物流状态。仅支持2024年之后的订单。返回字段包括:状态枚举(shipped/in_transit/delivered)、预计到达时间、快递公司。"}大厂规范:
- 描述中写明能做什么、不能做什么、返回什么
- 参数描述写明格式、范围、枚举值
- 避免歧义词(“处理”、“操作”、“相关”)
九、MCP:工具调用的"USB-C 时刻"
9.1 解决什么问题
在 MCP 之前,每个 AI 应用要对接 N 个外部系统,就需要写 N 套集成代码,且与具体框架/模型绑死。M 个应用 × N 个工具 = M×N 个适配器。
MCP 将其简化为M + N:
- 每个工具只需实现一次 MCP Server
- 每个 AI 应用只需实现一次 MCP Client
- 中间通过标准协议通信
9.2 架构三要素
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ MCP Host │◄───────►│ MCP Client │◄───────►│ MCP Server │ │ (AI 应用) │ 内部 │ (协议客户端) │ JSON-RPC │ (工具提供方) │ │ e.g. Claude │ │ │ / SSE │ e.g. GitHub │ │ Desktop │ │ │ │ DB, API... │ └──────────────┘ └──────────────┘ └──────────────┘MCP Server 暴露三类原语:
- Tools:可执行的操作(对应 Function Calling)
- Resources:可读取的数据(文件、数据库记录)
- Prompts:预定义的提示模板
9.3 2026-07-28 版核心变化
最新第 5 版 MCP 规范的最大变化:从有状态(Stateful)连接转向无状态(Stateless)设计。
- 之前:Client 与 Server 维持长连接,服务端保存会话状态
- 现在:每次请求自包含(self-contained),服务端不依赖连接上下文
为什么?有状态设计在 Serverless / 分布式部署中极其痛苦(连接漂移、状态同步、扩缩容)。无状态化让 MCP Server 可以像普通 REST API 一样水平扩展。
9.4 MCP 与传统 Function Calling 的关系
传统 Function Calling:工具定义硬编码在 API 请求中 ↓ 演进 MCP:工具定义由 Server 动态暴露,Client 运行时发现(Discovery)MCP 不是替代 Function Calling,而是在其之上加了一层标准化的服务发现与通信协议。模型侧的调用机制不变,变的是"工具从哪来、怎么注册、怎么鉴权"。
十、Agent 循环中的工具调用:ReAct 模式
在 Agent 架构中,工具调用不是一次性的,而是嵌入Think → Act → Observe循环:
defagent_loop(user_query:str,tools:list,max_steps:int=10):messages=[{"role":"user","content":user_query}]forstepinrange(max_steps):# Think + Act: 模型决定是否调工具response=llm.chat(messages,tools=tools,tool_choice="auto")ifresponse.finish_reason=="stop":returnresponse.content# 最终答案# Observe: 执行工具并回灌fortool_callinresponse.tool_calls:result=execute_tool(tool_call)messages.append({"role":"tool","tool_call_id":tool_call.id,"content":result})messages.append(response.message)# assistant message with tool_callsreturn"达到最大步数限制,任务未完成"关键设计决策:
max_steps防止无限循环(生产必须设置)- 每步的
messages会持续增长 → 需要 context window 管理策略 - 工具执行失败时,将错误信息作为
toolmessage 回传,让模型自行决策重试或换路径
十一、生产环境的 8 条铁律
这些是从真实事故中提炼的,不是教科书上的"最佳实践":
1. 永远做 Schema 校验
模型返回的arguments是字符串,json.loads可能失败。必须在执行前校验:
try:args=json.loads(tool_call.function.arguments)validate_against_schema(args,tool_schema)except(json.JSONDecodeError,ValidationError)ase:# 回传错误让模型重试,而非静默失败returnerror_message_to_model(str(e))2. 工具描述是 Prompt 的一部分
修改工具 description 等于修改 prompt。任何变更必须回归测试。一个enum值从"ASC"改成"asc"就可能导致批量任务全线崩溃。
3. 超时与熔断
工具执行必须有超时限制。一个慢 API 不能拖死整个 Agent 循环:
result=awaitasyncio.wait_for(call_tool(args),timeout=10.0)4. 幂等性设计
模型可能重试。create_order这类非幂等操作必须有去重机制(如 idempotency key)。
5. 最小权限原则
不要给模型"万能工具"。每个工具只做一件事,权限粒度到操作级别。
6. 日志与可观测性
每次 tool_call 记录:tool_name、arguments、result、latency、token_cost。这是调试 Agent 行为的唯一手段。
7. 优雅降级
工具不可用时,返回结构化错误而非让模型"猜":
{"error":"SERVICE_UNAVAILABLE","message":"天气API暂时不可用,请稍后重试"}8. 版本化工具 Schema
工具定义必须有版本号。模型缓存了旧 Schema 而你的 API 已升级,是最隐蔽的生产 Bug。
十二、Structured Output:工具调用的"硬保证"进化
2024 年后,各厂商推出 Structured Output / JSON Mode 的增强版:
| 特性 | 普通 Function Calling | Structured Output |
|---|---|---|
| 格式保证 | 大概率合规,偶有格式错误 | 100% 符合 Schema(grammar-constrained) |
| 实现方式 | 靠训练 + 后处理 | 推理时 token 级约束(FSA / CFG) |
| 适用场景 | 通用 | 对格式零容忍的场景(金融、医疗) |
| 性能代价 | 无 | 约 5-15% 推理延迟增加 |
OpenAI 的response_format: {type: "json_schema", json_schema: {...}}和 Anthropic 的tool_use+ strict mode 都属于此类。
选型建议:如果你的下游系统对格式有硬依赖(直接 parse 不做人审),用 Structured Output;如果允许后处理兜底,普通模式延迟更低。
十三、性能优化:减少 Token 消耗与延迟
工具调用的隐性成本常被忽略:
13.1 工具定义的 Token 开销
每个工具 Schema 约占 100-300 tokens。如果你注册了 20 个工具,每次请求光工具定义就消耗 3000-6000 tokens。
优化策略:
- 动态工具注入:根据用户意图分类,只注入相关工具(Router → Subset)
- 工具分组:将 20 个工具分为 4 组,第一轮让模型选组,第二轮注入该组工具
- 精简描述:去掉冗余修饰词,保留语义关键信息
13.2 多轮调用的 Context 膨胀
每次 tool result 都会追加到 messages 中。10 轮工具调用后,context 可能已消耗 50%+ 的窗口。
优化策略:
- 工具结果做摘要压缩(只保留关键字段)
- 使用 sliding window 或 summarization 策略管理历史
- 对已完成的工具调用,将详细结果替换为摘要
13.3 流式工具调用
在stream: true模式下,tool_calls的arguments是逐 token 流式返回的:
delta.tool_calls[0].function.arguments = '{"ci' delta.tool_calls[0].function.arguments = 'ty": "北' delta.tool_calls[0].function.arguments = '京"}'你需要在客户端做拼接,且不能在拼接完成前尝试json.loads。
十四、安全模型:工具调用的攻击面
工具调用让 LLM 从"只读"变为"可写",攻击面急剧扩大:
| 攻击类型 | 描述 | 防御 |
|---|---|---|
| Prompt Injection → 工具滥用 | 恶意输入诱导模型调用危险工具 | 工具执行前做意图审核;危险操作需人工确认 |
| 参数注入 | 模型被诱导传入恶意参数(如 SQL 注入) | 参数白名单校验;参数化查询 |
| 过度调用(DoS) | 诱导模型无限循环调用 | max_steps限制;调用频率限制 |
| 数据泄露 | 通过工具将敏感数据外传 | 工具返回结果做脱敏;出站白名单 |
| 工具投毒 | MCP Server 返回恶意结果影响后续推理 | 结果校验;多源交叉验证 |
大厂安全架构:
用户输入 → 输入过滤 → LLM 决策 → 工具调用意图 ↓ ┌─────────────────┐ │ Policy Engine │ ← 权限校验、频率限制 │ (OPA / 自研) │ ← 参数合规检查 └─────────────────┘ ↓ 工具沙箱执行(隔离环境) ↓ 结果审计 → 脱敏 → 回传模型十五、评估体系:怎么量化模型的工具调用能力?
如果你在做模型选型或自研模型评估,以下是核心 Benchmark:
| Benchmark | 评估维度 | 特点 |
|---|---|---|
| BFCL(Berkeley Function Calling Leaderboard) | 单/多/并行/相关性检测 | 最权威,覆盖 AST 匹配 |
| ToolBench | 多步推理、工具选择 | 16000+ 真实 API |
| API-Bank | API 调用准确率 | 侧重参数填充 |
| TaskBench | 端到端任务完成率 | 评估 Agent 全链路 |
| τ-bench | 工具调用 + 对话交互 | 模拟真实客服场景 |
关键指标:
- Tool Selection Accuracy:选对工具的比例
- Parameter Filling Accuracy:参数填对的比例(AST 级匹配)
- Relevance Detection:不需要调用时不调用的能力(避免 over-calling)
- Multi-step Success Rate:多步任务的端到端成功率
十六、前沿趋势(2026 下半年视角)
16.1 从"调用"到"编排"
单纯的工具调用已是标配。竞争焦点转向:
- 多 Agent 协作中的工具共享与冲突解决
- 动态工具创建:模型在运行时自己写代码注册新工具
- 工具调用链的自动优化(类似查询计划优化器)
16.2 多模态工具
工具不再限于文本 API:
- Computer Use(操控 GUI)
- 代码执行沙箱(Code Interpreter)
- 浏览器操作(Browser Tool)
- 机器人控制指令
16.3 MCP 生态爆发
截至 2026 年中,MCP 官方 SDK 已覆盖 10+ 语言,GitHub 48K+ followers。OpenAI、Google、国内主流厂商均已原生支持。MCP 正在成为事实标准。
16.4 推理模型 × 工具调用
o 系列 / 深度思考模型在工具调用中的特殊行为:
- 先进行长链推理(thinking),再决定调什么工具
- 可能在 thinking 中"模拟"工具结果,减少实际调用次数
- 对工具描述的语义理解更深,但也更"有主见"(可能拒绝调用它认为不合适的工具)
十七、一张图总结:工具调用的技术栈全景
┌─────────────────────────────────────────────────────────────────┐ │ 应用层 (Application) │ │ Agent Framework (LangGraph / CrewAI / AutoGen / 自研) │ ├─────────────────────────────────────────────────────────────────┤ │ 编排层 (Orchestration) │ │ ReAct Loop / Plan-and-Execute / Multi-Agent Router │ ├─────────────────────────────────────────────────────────────────┤ │ 协议层 (Protocol) │ │ OpenAI tools API / Anthropic Tool Use / MCP / A2A │ ├─────────────────────────────────────────────────────────────────┤ │ 模型层 (Model) │ │ 工具选择推理 / 参数生成 / Structured Output / Constrained Decode │ ├─────────────────────────────────────────────────────────────────┤ │ 执行层 (Runtime) │ │ 函数执行 / API Gateway / 沙箱 / 权限控制 / 熔断限流 │ ├─────────────────────────────────────────────────────────────────┤ │ 工具层 (Tools) │ │ REST API / DB / 文件系统 / 搜索引擎 / 代码解释器 / 外部服务 │ └─────────────────────────────────────────────────────────────────┘十八、面试 / 技术评审高频问题速答
Q1:Function Calling 和 RAG 的关系?
互补而非替代。RAG 是"给模型喂知识",Function Calling 是"让模型做动作"。实际系统中常组合使用:先用 RAG 检索上下文,再用 Tool Calling 执行操作。
Q2:为什么模型有时会"幻觉"一个不存在的工具名?
训练数据中见过类似函数名,但当前 tools 列表中没有。防御方式:在应用层做工具名白名单校验,不匹配则返回错误让模型重选。
Q3:tool_choice=“auto” 时模型不调工具怎么办?
检查:① 工具描述是否足够清晰 ② 用户 query 是否真的需要工具 ③ 尝试改为
"required"强制触发 ④ 检查模型版本是否支持。
Q4:并行调用中一个失败了怎么处理?
将失败的工具结果以 error 形式回传(
role: "tool"+ 错误信息),让模型决定是重试、换工具还是基于已有结果作答。不要静默丢弃。
Q5:MCP 会取代 Function Calling 吗?
不会。MCP 是通信协议(解决"怎么连接"),Function Calling 是模型能力(解决"怎么决策")。MCP 让工具的注册/发现/鉴权标准化,但模型侧的调用机制不变。
结语:从"会调 API"到"能设计系统"
工具调用的技术门槛在降低——每个模型都支持、每个框架都封装。但工程能力的门槛在升高:
- 如何在 50+ 工具中让模型精准选择?
- 如何在多步调用中管理 context 不爆炸?
- 如何在保证灵活性的同时确保安全性?
- 如何在 MCP 生态中设计可复用、可组合的工具服务?
这些问题的答案不在任何一篇"入门教程"里,而在你对系统设计、分布式架构、安全模型的综合理解中。
工具调用是 Agent 的手。手有多灵巧,取决于大脑(模型)和神经系统(你的架构)的协同设计。
最后更新:2026 年 8 月 | 基于 MCP 2026-07-28 规范、OpenAI Responses API、多厂商最新文档整理
参考资源:
- OpenAI Function Calling Guide
- Anthropic Tool Use Documentation
- MCP Specification (2026-07-28)
- Berkeley Function Calling Leaderboard (BFCL)
- Google Gemini Function Calling Docs