Function Calling 深度解析:Tool Use 参数设计、并行调用与错误处理
English Title: Function Calling Deep Dive — Tool Schema Design, Parallel Calls & Error Handling
MCP 把工具暴露成标准协议之后,模型侧如何「选中工具、填好参数、消化结果」仍是 Agent 落地的核心。Function Calling(也称 Tool Use)不是让 LLM 直接执行代码,而是让模型输出结构化调用意图,由你的运行时真正执行并回传结果。本文从闭环流程、JSON Schema 设计、错误重试、并行调用、结果回灌到 OpenAI / Claude / Gemini 差异,给出可上线的 Python 示例,衔接系列中的 MCP 与 API 集成专题。
After MCP standardizes tool exposure, the model still must select tools, fill parameters, and consume results. Function Calling lets the LLM emit structured call intents while your runtime executes them. This article covers the full loop, schema design, retries, parallelism, and provider differences.
1. Function Calling 如何工作 | The Agent Loop
一次完整的工具调用闭环可以概括为四步:
1 | Model → tool_call(s) → Execute → tool_result → Model → … |
| 阶段 | 谁负责 | 产出 |
|---|---|---|
| 1. 决策 | LLM | tool_calls:工具名 + JSON 参数 |
| 2. 执行 | 你的代码 | 调用 API、查库、跑脚本 |
| 3. 回灌 | 你的代码 | role: tool 消息,携带 tool_call_id 与结果 |
| 4. 续写 | LLM | 自然语言回答,或再次发起 tool_call |
关键认知: 模型是「调度员」,不是「执行器」。它根据 tools 定义里的 description 与 parameters(JSON Schema)推断该调哪个函数;你注册的真实 Python/HTTP 函数才接触生产数据。多轮 Agent 就是在 messages 数组末尾不断追加 assistant(含 tool_calls)与 tool(含 result),直到模型不再请求工具、只返回最终文本。
典型消息序列如下:
1 | messages = [ |
2. JSON Schema 参数设计 | Tool Parameter Design
tools[].function.parameters 遵循 JSON Schema 子集。设计质量直接决定模型能否一次填对参数。
推荐实践:
name— 动词 + 名词,如search_documents、create_ticket,避免do_stuffdescription— 写清「何时用、何时不用、边界」;这是模型选工具的第一信号- 必填字段 — 用
required: ["query"],减少漏填 - 枚举约束 — 对固定选项用
enum,比自由字符串更稳 - 控制粒度 — 宁可多个小工具,也不要一个「万能」工具塞满可选参数
1 | tools = [{ |
常见陷阱: arguments 在 API 里是字符串化的 JSON,必须先 json.loads 再校验;Schema 过于复杂(深层 oneOf)会降低填参成功率;字段名与业务代码不一致会导致静默失败——建议在执行前用 Pydantic 做二次校验。
3. 错误处理与重试 | Error Handling & Retries
工具层错误分三类,处理策略应不同:
| 类型 | 示例 | 策略 |
|---|---|---|
| 可恢复 | 429 限流、网络超时 | 指数退避重试(tenacity) |
| 参数错误 | 缺字段、类型不对 | 把错误信息回灌模型,让其修正参数 |
| 业务失败 | 无权限、资源不存在 | 结构化错误写入 tool content,让模型向用户解释 |
不要把堆栈直接丢给模型——用简短、可行动的 JSON:
1 | def run_tool(name: str, args: dict) -> str: |
重试层次:
- HTTP 层 — 对 LLM API 的 429/5xx 重试(与上一篇 API 指南一致)
- 工具层 — 幂等读操作可重试 2–3 次;写操作慎用自动重试
- Agent 层 — 同一
tool_call_id只回灌一次结果;若模型重复请求相同调用,可在运行时做去重或缓存
若连续多轮工具失败,应设置 max_tool_rounds 上限,避免无限循环烧 Token。
4. 并行工具调用 | Parallel Tool Calls
现代模型(如 GPT-4o、Claude 3.5+)常在一次 assistant 消息中返回多个 tool_call,且彼此无依赖——例如同时查天气与搜新闻。你的执行器应:
- 解析
message.tool_calls列表 - 并行执行(
asyncio.gather或线程池) - 按相同
tool_call_id逐条回灌role: tool消息 - 全部结果就绪后,再发起下一轮 LLM 请求
1 | import asyncio |
注意: 有依赖关系的调用(先查用户 ID 再查订单)不应依赖模型并行——应在 Schema 层拆成顺序工具,或用编排层(LangGraph 等)显式控制。并行只适用于「彼此独立」的子任务。
5. 结果解析与回灌 | Parsing & Feeding Back
执行结果回灌时需遵守各厂商约定,否则下一轮请求会 400:
- OpenAI 兼容 — 每条
tool消息必须带tool_call_id,与 assistant 里tool_calls[].id一一对应;content建议为字符串(JSON 文本即可) - 顺序 — 先 append 带
tool_calls的assistant,再 append 所有tool消息,不要穿插user - 体积 — 大段检索结果应截断或摘要后再回灌,避免撑爆上下文;可只保留
title + snippet前 N 条
1 | def agent_turn(messages, tools): |
解析技巧: 对模型返回的 arguments 做宽松解析(尾随逗号、单引号)可提升鲁棒性,但应在日志中记录原始字符串便于排错。若模型返回了未注册的工具名,回灌 {"error": "unknown_tool"} 比直接抛异常更能引导自修正。
6. 厂商差异 | OpenAI vs Claude vs Gemini
| 维度 | OpenAI / 兼容 API | Claude (Anthropic) | Gemini (Google) |
|---|---|---|---|
| 工具声明 | tools[].type=function |
tools[].name + input_schema |
function_declarations |
| 模型输出 | message.tool_calls |
content 块 type: tool_use |
functionCall parts |
| 结果回灌 | role: tool + tool_call_id |
role: user 块 tool_result |
functionResponse part |
| 并行 | 单条 assistant 多 call | 支持多 tool_use 块 |
支持多 function call |
| 强制调用 | tool_choice: required |
tool_choice: any |
mode: ANY |
Claude 把工具结果放在 user 角色里,且需 tool_use_id 关联;Gemini 则在同一次 generateContent 的 parts 数组里交替 functionCall 与 functionResponse。若你做统一 Provider 抽象,建议在内部归一化为:
1 |
|
上层 Agent 只处理 ToolInvocation / ToolResult,底层适配各 SDK 差异。DeepSeek、通义千问等 OpenAI 兼容端可直接复用 openai 客户端,仅改 base_url。
7. 完整 Python 示例 | Runnable Example
下面是一个最小可运行的「天气 + 计算」双工具 Agent(同步版,便于理解闭环):
1 | import json |
8. 实战要点 | Production Tips
- 工具幂等 — 写操作带
idempotency_key,防止模型重试导致重复下单 - 审计日志 — 记录每次
tool_call的 name、args、latency、success,便于回放与合规 - 人机确认 — 删数据、转账类工具在执行前插入 HITL 审批,不要全自动
- 与 MCP 的关系 — MCP Server 暴露能力,Function Calling 是模型侧的「遥控器」;二者常组合:MCP 提供工具清单,LLM 通过
tools数组选择调用(见上一篇 MCP 专题) - 测试 — 用固定
messagesfixture 测 Schema 校验与错误回灌,而非只测最终自然语言
9. 总结 | Conclusion
Function Calling 的本质是结构化意图 + 运行时执行 + 结果回灌的循环。Schema 写得越清晰,并行与错误处理越规范,Agent 就越稳定。厂商 API 表面不同,但心智模型一致:把工具当函数签名暴露给模型,把执行权握在自己手里。掌握本文后,你已能搭建「能选工具、能并行、能容错」的 Tool Use 层;下一步是把工具背后的 REST/OAuth/Webhook 接到真实业务系统。
系列导航 Series Navigation: