第4章 工具返回格式异常:现象、schema校验、JSON解析错误、兼容性修复
现象识别
Agent调用外部工具后,工具返回的响应体不符合约定的格式规范。典型表现包括:
- Agent陷入循环重试:反复调用工具但每次返回均被解析失败。
- JSON解析异常日志:看板上出现
json.decoder.JSONDecodeError或SyntaxError: Unexpected token。 - 字段缺失/类型错误:schema校验报出
required field missing或type mismatch。 - 上游接口版本更替:工具返回了新的JSON结构,Agent代码未同步适配。
快速判断口诀:
返回若能读,schema不匹配 → 字段级异常;
返回读不了,json抛异常 → 语法级异常;
返回读得通,逻辑走不通 → 语义级异常。
返回若能读,schema不匹配 → 字段级异常;
返回读不了,json抛异常 → 语法级异常;
返回读得通,逻辑走不通 → 语义级异常。
数据采集
当触发格式异常时,优先收集以下三类数据,用于准确定位故障节点:
| 数据源 | 关键字段 | 采集方法 |
|---|---|---|
| Agent 原始日志 | raw_response、status_code、content_type |
开启 Agent 的 debug 级别日志;记录 HTTP 响应的 text 原始内容 |
| 工具侧访问日志 | request_id、response_body、response_headers |
在工具网关或 API 管理平台中按 request_id 检索 |
| Agent schema 定义 | openapi.json 中该端点的 response 模型 |
从 Agent 配置仓库或注册中心获取最新 schema 文件 |
注意:切勿只收集 Agent 侧聚合后的结构化数据,必须保留 原始响应字符串。部分 JSON 解析错误在结构化打印时会被静默吞掉,只有原始字符串才能暴露非法字符或截断。
原因定位
按照“最易→最难”的排查顺序,使用以下判断规则表快速定位根因:
| 判定条件 | 根因分类 | 置信度 |
|---|---|---|
日志中 json.loads() 抛出 JSONDecodeError,且原始响应首字符不是 { 或 [ |
非 JSON 响应(如 HTML 5xx 错误页) | 高 |
响应有效 JSON,但 jsonschema.validate() 报 ValidationError,缺少必填字段 |
Schema 不匹配(字段级变更) | 高 |
| 响应有效 JSON,字段名或类型与 schema 定义一致,但 Agent 仍无法正常处理后续逻辑 | 兼容性异常(如枚举值新增、null 处理缺失) | 中 |
响应体包含 \ufeff BOM 头或控制字符 |
编码问题导致 JSON 解析失败 | 高 |
| 响应内容被截断一半,结尾不完整 | 超时/缓冲区限制导致的截断 | 中 |
推荐使用二分法做初步隔离:
- 将原始响应通过独立 parser 测试(如 Python
json.loads)→ 区分“能不能解析”。 - 若可解析,再将 parsed object 与 schema 做单独校验 → 区分“字段/类型是否匹配”。
- 若 schema 校验通过但逻辑异常,则检查兼容性适配层。
实际案例:某天气工具返回的
temperature 字段从整数 23 变为字符串 "23"。Agent 侧 schema 类型定义为 number,导致校验失败。修复前需确认工具侧是否已向后兼容,或是要求工具回滚。
修复动作
根据定位得到的根因类别,执行对应的修复策略:
- JSON 解析错误修复:
- 若为非法字符(BOM、控制字符),在 Agent 接收处增加
strip BOM和clean control chars预处理。 - 若为截断响应,增大
max_response_size或重试时要求工具分页返回。 - 若为完全非 JSON,则改为解析
Content-Type头,若为text/plain则尝试json.loads前先过滤异常字符。
- 若为非法字符(BOM、控制字符),在 Agent 接收处增加
- Schema 校验失败修复:
- 与工具团队协商对齐 API 版本,更新 Agent 侧的 schema 文件。
- 短期内采用“宽容模式”:在 Agent 内使用
allow_extra或skip_missing_optional策略。 - 对必填字段缺失,配置默认值或触发降级提示。
- 兼容性异常修复:
- 增加适配层:将新字段映射到旧字段(如
temperature_celsius→temperature)。 - 对新增枚举值做白名单更新,避免 Agent 因未知值而拒绝处理。
- 若工具同时返回多个版本响应,在 Agent 内按版本号分流处理逻辑。
- 增加适配层:将新字段映射到旧字段(如
# 示例:兼容性适配层代码片段(Python)
def normalize_weather_response(raw: dict) -> dict:
# 兼容工具返回字符串类型的 temperature
if isinstance(raw.get("temperature"), str):
raw["temperature"] = float(raw["temperature"])
# 填充缺失的必填字段
raw.setdefault("wind_speed", 0.0)
return raw
复测
完成修复后,按以下三步验证修复有效性:
- 单次调用测试:使用该工具的历史异常响应(从数据采集阶段保存的原始日志中回放),确认不再报 JSON 解析或 schema 校验错误。
- 回归批量测试:运行 Agent 对该工具的 100 次随机参数调用,检查返回成功率和正确率。
- 端到端场景测试:构造包含格式异常和正常响应的混合用例,确保 Agent 不会因一次异常导致整个会话崩溃。
复测通过标准:JSON 解析成功率 ≥ 99.5%,schema 校验通过率 ≥ 99.5%,无未捕获异常导致 Agent 终止。
预防
为避免工具返回格式异常反复出现,建议从以下维度建立防御体系:
| 维度 | 措施 | 优先级 |
|---|---|---|
| 契约测试 | 在 CI/CD 中为每个工具接口编写响应 schema 契约测试(如 Pact、JSON Schema Test Suite) | P0 |
| Agent 护网 | 所有外部工具响应必须经过 ResponseValidator 中间件,包含预处理和宽容解析 |
P0 |
| 版本协商 | Agent 调用时携带 Accept-Version 头,工具侧明确版本号;Agent 支持多版本响应适配 |
P1 |
| 监控告警 | 实时监控 json_parse_error_count 和 schema_validation_error_count,异常时自动降级 |
P1 |
| 灰度发布 | 工具侧 API 升级先在小流量 Agent 实例上灰度,观察格式兼容性后再全量发布 | P2 |
最后,建议在 Agent 的代码仓库中维护一份“工具响应异常处理手册”,每发生一次新的格式异常,就将原始响应、根因、修复代码记录到该手册中,形成组织级的知识沉淀。