Tool Calling 工程 2026:schema 到回退的统一架构
把 LLM 应用的 tool calling 视作一次微服务调用,用 schema 设计、执行引擎、五层失败回退、plan-and-execute/ReAct 编排与工具治理五个子工程,给出生产级 tool call 栈的统一架构。
约 31 分钟阅读9,010 字6 次阅读博主

把 LLM 应用的 tool calling 视作一次微服务调用,用 schema 设计、执行引擎、五层失败回退、plan-and-execute/ReAct 编排与工具治理五个子工程,给出生产级 tool call 栈的统一架构。

当 LLM 在第 23 个 token 处决定调用
search(query)而非ask_user(question)时,schema 校验、工具路由、失败回退、超时治理、成本归因被压缩成同一个微服务调用栈——本文把这套微服务化的工程真相一次拆开。
LLM 应用在 2026 年已经从"聊得不错"演化到"能做实事"。所谓的"做实事",核心不是模型本身的智能跃迁,而是 tool calling 这一层契约的成熟:模型不再是单纯输出自然语言,而是被允许输出结构化的 tool_calls 字段、调用真实的 API、读写真实的数据库、触发真实的副作用。每一次 tool call 都是一次跨越 LLM 边界的微服务调用——而微服务调用的所有经典工程难题(超时、重试、幂等、回退、链路追踪、权限审计、成本归因),全部被原封不动地搬进了 LLM 应用栈。
但是,绝大多数 LLM 应用开发者在第一周就会遇到一连串让人措手不及的失败:模型决定调用 search,给出的 query 字段是字符串却塞了一个完整 JSON 对象;模型决定调用 cancel_order,参数合法但订单已发货;模型在一次响应里同时调用了 transfer_money(from=A, to=B) 和 transfer_money(from=B, to=A);模型调用了一个不存在的工具 delete_user;模型调用的工具成功了但客户端提前断开 SSE;模型在工具还没返回时已经生成了"我已经查到了……"的自然语言结果。每一个失败都看似个案,但它们背后共享同一套未被显式建模的工程契约:tool call 不是"加一个 if 分支"那么简单,它是一套完整的分布式协议。
本文不打算重复介绍 OpenAI function calling、Anthropic tool use、Google Gemini function calling 的语法差异(这些文档各自完备),而是把视角抬升一层:一旦 LLM 应用生产化,tool calling 就是一次微服务调用。用这个视角重新审视 2026 年 LLM 应用的 tool 工程,可以把它拆解为五个相互独立又紧密耦合的子工程:schema 设计、工具执行、失败回退、多步编排、工具治理。下文按这个顺序展开,并在最后一节给出给架构师的工程清单。
把一次 tool call 形式化为四元组 ,其中 是工具名(tool name), 是参数对象(arguments), 是返回值(result,可能是结构化数据或错误信号), 是会话上下文(session context,包括 message history、user identity、policy 决策上下文)。一次完整的 LLM 应用调用栈可以视为 LLM 推理引擎在每一步生成 ,由执行引擎 完成实际副作用,再把 回填到消息历史,下一轮推理基于新的 history 继续。这个循环的工程复杂度并不比一个简单的状态机低,而状态机在分布式系统里已经够让团队头疼十年。
评价一次 tool call 的质量,传统 ML 视角只看"模型是否选对了工具"——但在生产 LLM 应用里,这个指标远不足以反映真实体验。我们提出五维质量指标 ,分别对应:
真实工程里这五维经常相互制约:为了 不得不加 idempotency key 影响 ,为了 不得不限制工具 timeout 又会让 下降。一个成熟的 tool calling 工程体系必须同时优化这五个维度,而不是优化其中一个而让其他维度塌缩。下文所有具体技术取舍都对应到这五维中的一项或多项。
schema 是 tool call 的契约。契约的质量决定了 参数合规率的天花板,也间接决定了 LLM 选工具的稳定性。一个常见误区是把 schema 当作"JSON Schema 的练习题"——能写、能校验、就完了。但在生产 LLM 应用里,schema 同时承担三个角色:
这三个角色相互冲突:静态校验要严格、机器可读;LLM 提示要自然语言丰富、举例子;人类阅读要简洁、突出核心约束。一个被工程验证的 schema 模板应当同时满足三者,结构如下:
{
"name": "search_documents",
"description": "在企业知识库中检索与 query 最相关的文档片段。返回 top-k 个 chunk 及元数据。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "自然语言查询字符串,建议 5-100 字。例如:'Q3 营收同比增长多少'"
},
"top_k": {
"type": "integer",
"description": "返回的 chunk 数量。默认 5,最大 20。",
"minimum": 1,
"maximum": 20,
"default": 5
},
"filter": {
"type": "object",
"description": "元数据过滤条件。可选字段:department (string), date_range (object with start/end ISO date)。示例:{\"department\": \"财务\", \"date_range\": {\"start\": \"2026-01-01\", \"end\": \"2026-09-29\"}}"
}
},
"required": ["query"],
"additionalProperties": false
},
"returns": {
"type": "object",
"properties": {
"chunks": {"type": "array", "items": {"$ref": "#/definitions/Chunk"}},
"total_found": {"type": "integer"}
}
},
"side_effects": "none",
"idempotent": true,
"cost_hint": "embedding_search, 约 0.001 USD per call"
}
注意几个细节:description 字段包含自然语言示例("Q3 营收同比增长多少"),而不仅是类型描述——LLM 在选工具时高度依赖这种带例子的描述;filter 是可选 object 嵌套,每个字段都给了示例;additionalProperties: false 防止 LLM 自由发挥塞进未声明字段;side_effects 和 idempotent 是自定义扩展字段,给执行引擎做策略决策(不能对幂等工具加重试退避?不能给有副作用的工具开"自动重试");cost_hint 让 LLM 在多工具竞争时偏向低成本选项。这种 schema 风格在 2026 年已经被 LangChain、LlamaIndex、Vercel AI SDK 等主流框架采纳为推荐实践。
schema 还有一个隐藏工程维度:版本管理。当工具后端 API 演进时,schema 不能 silent breaking change——必须显式标记 version: "v2" 并在 description 里说明 v1 vs v2 的差异,否则 LLM 应用在升级当天会同时收到新旧两个 schema,行为不可预测。
工具执行引擎 的核心职责是把一次结构化调用落地为真实副作用。生产环境的执行引擎必须解决五个子问题:超时控制、并发编排、状态隔离、结果标准化、上下文注入。
超时控制:每个工具调用都必须有显式 timeout,区别于 LLM 推理的 timeout。常见做法是分级 timeout:本地工具(数据库查询、本地文件读取)500ms-2s,HTTP API 调用 5-10s,第三方 SaaS 调用 15-30s,慢批处理(向量检索、ETL)60-300s。timeout 失败的处理是 timeout-aware retry 还是直接回退到下一步,取决于工具幂等性()。
并发编排:当 LLM 在一次响应里返回多个独立 tool_calls 时,执行引擎必须决定是并行还是串行。OpenAI/Anthropic 都支持 parallel tool_calls,但执行引擎仍要按依赖图重排:比如 search_documents + get_user_profile 之间没有依赖可并行,但 search_documents 的结果要喂给 summarize_documents 时必须串行。一个成熟的执行引擎内置依赖分析(DAG 推断),而不是把所有 tool_calls 当作可并行的。
状态隔离:单次 tool call 不应污染全局状态;多次 tool call 之间共享 session context,但工具实现不应依赖可变共享内存。常见反模式:在工具内部维护 let lastCall = ... 这种 instance 字段,导致水平扩展时行为不一致。
结果标准化:工具返回值必须收敛到一个统一的 schema:成功 → { ok: true, data: ... },失败 → { ok: false, error: { code, message, retryable } }。所有错误必须带 retryable 标志,区分 transient(timeout、429)和 permanent(400、404、422),retry 逻辑据此分流。
上下文注入:工具返回值会回填到 message history,太大的返回值会撑爆 LLM 上下文窗口(典型 200K tokens)。执行引擎必须在回填前做截断或摘要——这是 id=736(长上下文压缩)讨论的子集,但在 tool calling 上下文里更紧迫,因为一次响应可能触发多个 tool call,叠加截断风险。截断策略的取舍有三种:硬截断(保留前 N 字符)、结构化截断(保留 JSON 的关键字段、丢弃冗余字段)、LLM 摘要截断(用更便宜的模型把大段文本压缩成 200 字摘要)。生产环境推荐结构化截断 + 摘要压缩组合,因为纯硬截断会切断关键上下文,纯摘要截断引入额外的 LLM 调用增加延迟与成本。截断后还应在消息历史里加一个 truncated: true 标记,让 LLM 在后续推理中感知到信息不完整,避免幻觉式补全。
可靠性工程的具体决策见 §5 失败回退。
tool call 失败是常态。LLM 应用每天面对的失败谱系包括:JSON 解析失败(模型输出不符合 schema)、工具不存在、权限拒绝、timeout、429 限流、5xx 服务方错误、参数校验失败、副作用完成但客户端断开。健康的执行引擎必须构建五层防御:
图表加载中…
Layer 1 — schema 校验:在执行前拦截所有参数不合规的调用。这是 的最后一道闸门,也是 LLM 自我修正的关键输入——把校验错误回传给模型,模型在下一轮通常能纠正。失败的 tool call 不应直接展示给用户,但应回写到 message history 让模型知道。
Layer 2 — 权限与 policy 检查:即使 schema 合法,工具可能仍不可用(用户未订阅该功能、跨租户访问、敏感操作需二次确认)。policy engine 应在执行前拦截,并把可操作的错误(如"需要 admin 权限")回传给模型。
Layer 3 — 重试与退避:仅对 transient 错误(timeout、5xx、429)开启重试。退避策略包括指数退避(1s, 2s, 4s)+ 抖动;最大重试次数通常 3 次;带 idempotency-key 的 HTTP 调用可以重试,副作用工具(transfer_money)不能盲目重试。
Layer 4 — 回退到 fallback 工具:当主工具 retry exhausted,回退到降级方案:search_documents 失败 → keyword_search 替代;gpt-4 限流 → gpt-4-mini 替代;transfer_money 失败 → 提示用户"暂时无法转账,是否稍后重试?"。fallback 链应在工具注册时声明。
Layer 5 — 把错误回传 LLM 自决策:当所有 fallback 都失败,把结构化错误返回给 LLM,让模型决定是放弃、提示用户、还是换路径。LLM 通常能根据错误类型给出更自然的应对("我尝试调用银行接口查询余额失败,请稍后再试或联系客服"),而不是死板展示技术错误。这里有一个被低估的工程细节:错误消息的措辞会显著影响 LLM 的下一步决策——"查询失败" 会让模型重试同一工具,"工具 X 当前不可用,请尝试工具 Y" 会让模型主动换路径。所以 Layer 5 的错误消息应同时包含"人类可读"和"LLM 可决策"两种结构,前者给用户展示,后者写回 message history。
五层防御的工程实现需要在执行引擎内置一个错误分类器,把任意错误归类到五个 layer 之一的入口。常见分类规则:HTTP 4xx → layer 1 或 5;HTTP 5xx → layer 3;超时 → layer 3;JSON parse 错 → layer 1;权限拒绝 → layer 2;自定义业务错误 → 视 retryable 字段决定。一个被验证的分类器用决策表而非 ML 模型,因为错误分类需要可解释、可调试、可热更新。决策表的常见字段包括:error_code(来自工具实现的结构化错误码)、http_status、retryable_hint、fallback_available、user_visible(决定是否要把错误展示给终端用户)。决策表的可热更新很重要——新的工具上线时分类规则可能调整,不重启服务就能 patch 规则是上线速度的关键。
当 LLM 应用需要连续调用多个工具完成任务时,编排策略成为 端到端时延的核心决定因素。2026 年主流的两种范式:plan-and-execute(先规划后执行)和 ReAct(边推理边执行)。两者各有取舍。
plan-and-execute 范式:LLM 在第一轮只输出 plan(工具调用序列),不执行;执行引擎按 plan 串行调用,每步结果回传到下一轮 plan。优势是规划稳定、可观测、单步可恢复;劣势是延迟高(多一轮 LLM 推理)、不灵活(plan 一旦错误需要重新规划)。适合"任务明确、工具少、调用链短"的场景,比如"查用户最近 10 条订单并按金额排序"。
ReAct 范式:LLM 与执行引擎交错,每轮输出 reasoning + tool_call,结果回填后立即推理下一步。优势是延迟低(无需单独规划轮)、灵活(每步基于最新结果调整);劣势是不可预测(调用链长度不固定)、难调试(中间状态难快照)。适合"任务开放、工具多、需要人机交互"的场景,比如"帮我安排下周去东京的行程"。
生产环境的 LLM 应用通常采用混合策略:长任务用 plan-and-execute(在前 N 步规划后开始执行,遇到错误重新规划),短任务用 ReAct(单次推理直接调度)。判断"长 vs 短"的启发式:调用链预期长度 ≤ 3 用 ReAct,> 3 用 plan-and-execute。
编排层还需要解决partial failure recovery问题:调用链中途失败时,如何恢复到之前状态?常见做法是引入 checkpoint——每完成一步 tool call 都把当前 session state 序列化到持久存储,失败时从最近 checkpoint 重试。这与微服务架构里的 saga 模式一脉相承。
编排的可靠性可以形式化为:,即整条调用链的质量是每步质量的乘积乘以(1 - 人为中止概率)。这个公式揭示了一个反直觉的事实:单步 99% 的可靠性在 10 步调用链里会跌到 90%。要达到端到端 99.9%,每步必须 ≥ 99.97%——这就是为什么 tool execution 的内部可靠性( + )必须远高于通用 SaaS 的 SLO 目标。
编排层的另一个隐性成本是LLM 推理 token 的累计消耗。每次 tool call 都会让 message history 变长,下一轮 LLM 推理的 prompt token 也会变长(虽然 LLM 厂商通常对 cache 命中部分打折,但首次推理仍按全量计费)。一个 5 步调用链的后期 LLM 推理可能比第一步贵 3-5 倍,因为 message history 累积了前几步的 tool call 请求与返回。生产环境的优化策略包括:把工具返回值在回填前压缩(见 §4 上下文注入)、用更便宜的模型做中后期推理(model cascading)、用 prompt cache 把工具描述部分长缓存。
把上面六节抽象为可直接落地的工程动作:
additionalProperties: false 是默认。version、side_effects、idempotent、cost_hint 四个强制字段。idempotent: false,执行引擎跳过自动重试。cost_hint 字段让 LLM 在多工具竞争时偏向低成本选项。tool calling 工程的成熟度在 2026 年仍处于"早期成熟"阶段。已解决的:schema 标准(JSON Schema 2020-12 + 自定义扩展)、执行引擎抽象(LangChain Tools、Vercel AI SDK tools)、回退策略(retry + fallback pattern)。未解决的:长任务编排的 checkpoint 序列化标准、跨模型的 tool schema 兼容性(OpenAI / Anthropic / Gemini 各自扩展)、tool call 的成本归因与 LLM token 成本联合优化、tool call 的 A/B 实验框架(不同 tool schema 对模型选工具的影响)。
一个被低估的风险是 tool call 的安全护栏:tool 本身可能是高副作用(删除数据、转账、发邮件),但模型在选工具时只看 prompt 上下文,不感知真实权限边界。本文 §5 Layer 2 提出的 policy check 是必要的,但需要和整个应用的权限系统(RBAC / ABAC)深度集成,而不是孤立的 policy engine——这是 2026 年仍需攻克的工程债。
另一个被低估的是tool 调用对推理时延的影响:一次 round-trip 包括 LLM 推理(200ms-2s)+ tool 执行(10ms-30s)+ 结果回填 + 下一轮推理。在端到端 P95 时延要求 2s 的应用里,单个 tool call 不能超过 500ms。这意味着高延迟工具(数据库查询、第三方 API)必须有结果缓存层(见 id=732 语义缓存),否则无法满足延迟 SLO。
如果你是 LLM 应用的架构师,下一周可以立刻做的三件事:
tool calling 的工程复杂度并不低于任何已有的分布式系统,只是它把"自然语言"作为一个新的输入维度引入契约层。把自然语言视为"带噪的、高熵的、不稳定的结构化输入"——这是 LLM 应用工程化的核心心智模型。schema 是契约,执行引擎是运行时,回退是韧性,编排是流程,治理是边界。这五件事做好了,LLM 应用的生产化就从"demo 跑得通"跨到"用户用得稳"。
Conversation
0 条