文章

LLM Tool Contract Testing 生产实战:用 Schema 兼容、黄金对话与回放沙箱降低 Agent 升级风险

Agent 工具升级最容易破坏函数选择和参数语义。本文介绍如何用 Schema 兼容矩阵、黄金对话、沙箱回放和版本门禁,构建可上线的 LLM Tool Contract Testing 流程,覆盖选择契约、参数契约、执行契约、返回契约与行为契约五层体系。

背景:工具升级为什么会让 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、命名空间、与其他工具的边界说明,以及禁止调用的条件。

第二层:参数契约

它包含字段类型、requiredenum、默认值、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_idaccount_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 的能力

参考资料

常见问题

Tool Contract Testing 和普通 API 测试有什么区别?
普通 API 测试主要验证调用方传参和服务端响应是否符合接口契约;Tool Contract Testing 还要验证模型是否能在真实上下文中选对工具、填对参数、处理返回值,并在工具升级后保持行为稳定。
工具 schema 做了兼容变更,为什么仍然需要回放测试?
因为 LLM 会读取工具名称、字段描述、枚举、required 字段和历史上下文。即使 JSON 层面兼容,描述变化、字段顺序变化或新增相似工具也可能改变模型的工具选择。
上线前最小化的门禁应该包含什么?
至少包含 schema diff、黄金对话回放、沙箱执行、参数语义校验、幂等检查、失败回退策略和灰度流量监控。
Tool Contract Testing 是否需要每次模型升级都跑?
需要。模型升级、系统提示变更、工具描述变更、工具列表变化都会影响工具选择。即使工具代码没变,只要模型或上下文变了,也应至少跑核心黄金对话。