背景:工具升级为什么会让 Agent 翻车
在普通后端系统里,API 契约通常由调用方代码、OpenAPI 文档、类型定义和集成测试共同约束。只要字段没有删除、枚举没有破坏、响应格式仍能反序列化,很多变更就可以被视为兼容。
但 LLM Agent 的工具调用链路多了一层不稳定因素:模型会读取工具名称、描述、参数 schema、历史消息和工具返回值,然后自行决定是否调用、调用哪个工具、如何填参数。这意味着工具契约不仅是机器可解析的 JSON Schema,也是模型理解任务的提示词组成部分。
OpenAI 的 Function Calling 文档明确说明,函数工具由 JSON Schema 定义,模型会返回 tool_calls,应用侧需要执行函数并把结果再交还给模型。Anthropic 的 Tool Use 文档也采用类似模式:开发者传入带 input_schema 的工具,Claude 返回 tool_use block,应用执行后回传 tool_result。Google Gemini 的 Function Calling 文档则要求提交 function declarations,并支持通过模式控制模型是否自动、强制或禁止调用函数。
这些机制说明:工具调用不是一次普通 HTTP 调用,而是一个由 模型选择 → 结构化参数 → 业务执行 → 结果回灌 → 最终回答 组成的闭环。只测接口是否能跑通,远远不够。
核心原理:把 Tool Contract 拆成五层
一个可上线的 Tool Contract 不应该只等于 name + parameters。更稳妥的拆法是五层:
第一层:选择契约
它决定模型什么时候应该调用这个工具,什么时候不应该调用。这里包含工具名、description、命名空间、与其他工具的边界说明,以及禁止调用的条件。
第二层:参数契约
它包含字段类型、required、enum、默认值、nullable、格式约束、单位、时间区间、ID 格式和数组长度等。OpenAI 文档中函数参数使用 JSON Schema 定义,Google 文档中 function declarations 兼容 OpenAPI schema,这类结构化定义是参数契约的基础。
第三层:执行契约
它规定工具是否幂等、是否会写数据、是否需要审批、是否允许重试、超时如何处理、错误码如何返回。很多 Agent 事故并不是参数错,而是模型把”查询工具”当成”写入工具”,或者在重试时重复下单、重复发邮件、重复扣费。
第四层:返回契约
工具返回给模型的不只是程序可解析字段,也会影响模型下一步推理。返回值必须稳定、短、可解释,并避免把内部栈、隐私字段、无关大对象直接塞回上下文。
第五层:行为契约
它用黄金对话和回放测试描述”在这些真实场景里,模型应该怎么选工具、怎么填字段、怎么处理失败”。这部分无法完全靠静态 schema 推导,需要基于真实或合成轨迹验证。
Schema 兼容矩阵:先判断变更风险
上线前第一步不是跑模型,而是做工具契约 diff。建议把每次工具变更分成四类:
| 风险等级 | 典型变更 | 策略 |
|---|---|---|
| 低风险 | 补充 description、增加非 required 字段、增加只读返回字段、修正文档错字 | 静态 diff 即可,但描述变化仍需关注 |
| 中风险 | 新增 required 字段但提供默认值、新增 enum 值、调整字段格式说明、改变工具描述边界、增加相似工具 | 需要黄金对话回放 |
| 高风险 | 删除字段、重命名字段、改变字段类型、改变单位、改变幂等语义、把只读工具改成写入工具、改变错误码结构 | 引入新工具版本(如 create_invoice_v2) |
| 模型敏感变更 | 工具名变短、description 变得模糊、多个工具边界重叠、参数名从业务语义改成内部缩写 | API 层可能兼容,但直接影响模型选择 |
可以用一个简单的契约元数据文件把这些规则固定下来:
tool: billing.create_invoice
version: 2.1.0
owner: billing-platform
risk_level: high
side_effect: write
idempotency_key: required
compatibility:
removed_fields: forbidden
renamed_fields: requires_new_tool_version
new_optional_fields: allowed_with_replay
new_required_fields: requires_default_or_new_version
enum_expansion: requires_golden_replay
release_gate:
schema_diff: pass
golden_conversation_replay: pass
sandbox_execution: pass
rollback_plan: required
这份元数据不需要复杂,但必须进入 CI。否则工具升级会变成”谁改了谁知道”,Agent 运行时才暴露问题。
黄金对话:不要只保存 prompt,要保存完整轨迹
Tool Contract Testing 的核心资产是 golden conversation。它不是一组静态 prompt,而是一组完整可回放轨迹。
一条黄金轨迹至少应包含以下要素:
- 用户输入
- 系统提示
- 可见工具列表
- 工具 schema 版本
- 模型配置
- 期望调用工具
- 期望参数
- 模拟工具返回值
- 期望最终回答
- 禁止行为
- 业务断言
示例:
{
"case_id": "invoice_create_existing_customer_001",
"user_message": "给客户 C1024 生成 6 月份 SaaS 订阅发票,金额 299 美元",
"tool_catalog_version": "2026-07-09",
"expected_tool": "billing.create_invoice_v2",
"expected_arguments": {
"customer_id": "C1024",
"billing_period": "2026-06",
"currency": "USD",
"amount": 299
},
"forbidden_tools": [
"billing.refund_payment",
"billing.send_invoice_email"
],
"mock_tool_result": {
"invoice_id": "INV-202606-C1024",
"status": "draft"
},
"assertions": [
"must_not_send_email",
"must_create_draft_invoice_only",
"must_include_invoice_id_in_final_answer"
]
}
注意:这里不仅检查字段是否匹配,还检查 不应该调用什么工具。在生产 Agent 里,错误的工具选择往往比参数格式错误更危险。
回放沙箱:执行工具,但不触碰真实业务
黄金对话只能验证模型输出;回放沙箱要验证工具执行链路。建议将工具执行拆成三种环境:
| 环境类型 | 用途 | 适用场景 |
|---|---|---|
| Mock Executor | 只校验参数结构,返回固定结果 | 快速 CI |
| Stateful Sandbox | 使用隔离数据库、假邮箱、假支付、假工单系统,验证写入副作用和状态变化 | 每日回归和发布前验证 |
| Shadow Replay | 使用真实线上请求的脱敏副本,在不执行真实副作用的情况下重放模型选择与参数生成 | 发现 description 改动、工具列表变化或模型升级带来的行为漂移 |
沙箱结果不要只记录 pass/fail。更有用的是记录以下指标:
- Tool Selection Accuracy:是否选对工具
- Argument Exact Match:关键参数是否完全匹配
- Semantic Argument Match:时间、金额、ID、单位是否语义一致
- Forbidden Call Rate:是否调用了禁止工具
- Clarification Rate:是否在信息不足时追问
- Execution Success Rate:工具是否能在沙箱中完成
- Side Effect Violation:是否产生了不允许的写入行为
这些指标比”模型回答看起来正确”更接近生产风险。
工程落地:把 Tool Contract Testing 放进发布流水线
一个可操作的落地流程可以分为六步:
1. 工具注册中心
每个工具都必须有唯一 ID、版本、owner、side effect 等级、schema、description、返回结构、权限范围和弃用状态。不要让 Agent 代码散落维护工具定义。
建议对工具 ID 使用稳定命名:
crm.search_customer.v1
billing.create_invoice.v2
calendar.create_event.v1
support.open_ticket.v1
不要频繁重命名工具。对模型而言,工具名就是语义锚点。
2. Schema Diff 检查
每次修改工具 schema,都自动比较旧版本和新版本。对删除字段、改类型、改 required、改 enum、改 side effect 的变更直接打上风险等级。
3. 黄金对话选择
不要所有用例都全量回放。更好的做法是按风险选择:高频工具、写入工具、相似工具、近期出过事故的工具、description 变化的工具优先。
4. 沙箱执行
模型输出 tool call 后,不要直接打真实系统。先进入沙箱 executor,验证参数、权限、幂等键和业务状态。写入类工具必须检查是否存在重复执行风险。
5. 发布门禁
| 风险等级 | 最低门禁要求 |
|---|---|
| 高风险 | schema diff + 黄金对话回放 + 沙箱执行 + owner 审批 |
| 中风险 | schema diff + 黄金对话回放 |
| 低风险 | 静态 schema diff |
6. 灰度与回滚
工具升级应支持按租户、按流量比例、按工具版本灰度。发现 forbidden call rate 或 argument mismatch 上升时,优先回退工具 catalog,而不是等待模型侧修复。
适用场景
Tool Contract Testing 适合以下场景:企业内部 Agent 平台、客服 Agent、CRM/ERP 操作助手、代码 Agent、数据分析 Agent、支付/订单/日程类自动化、以及任何会调用外部系统的 LLM 应用。
如果你的工具只读、数量少、调用后果低,可以从轻量级 schema diff 和十几条黄金对话开始。如果你的工具会写数据、发邮件、改订单、改权限、触发财务动作,就应该把工具契约测试当成上线硬门禁。
常见误区
误区一:JSON Schema 合法就代表工具可用
JSON Schema 只能说明结构合法,不能保证模型会选对工具,也不能保证字段语义正确。customer_id 和 account_id 都是 string,但业务含义完全不同。
误区二:只测成功路径
真实 Agent 事故经常发生在信息不足、用户表达含糊、权限不足、工具超时、返回空结果等场景。黄金对话必须覆盖失败路径和追问路径。
误区三:工具越多越好
OpenAI 文档提醒,函数定义会进入模型上下文并消耗输入 token。工具过多不仅增加成本,也会增加模型选择错误的概率。工具目录应该按场景裁剪,而不是一次性暴露全部后台能力。
误区四:description 只是文档
对 LLM 来说,description 是行为提示。修改 description 可能改变模型选择,即使 schema 没变,也应触发回放测试。
误区五:把回放结果等同于线上安全
回放只能覆盖已知场景。线上仍需要监控工具调用分布、错误率、禁止工具调用、异常参数、用户撤销率和人工接管率。
上线检查清单
- 工具是否有唯一 ID、owner、版本和 side effect 等级
- 是否完成 schema diff,并标记破坏性变更
- 新增 required 字段是否有默认值或新版本工具名
- 工具 description 是否说明适用边界和禁止场景
- 是否存在相似工具导致选择混淆
- 黄金对话是否覆盖成功、失败、追问、权限不足和工具超时
- 写入工具是否有幂等键、审批策略和重复执行保护
- 沙箱是否隔离真实邮箱、支付、订单、CRM 和数据库
- 是否记录 tool call、arguments、tool result、final answer 和断言结果
- 是否有按版本回滚工具 catalog 的能力