多代理系统有两种错误处理反模式,叠加后效果是毁灭性的。静默吞没把故障隐藏起来不让协调器知道。工作流终止在任何故障浮现时杀掉一切。合在一起,它们确保大多数查询要么产出看起来完整但实际有缺口的报告,要么什么都不产出。
反模式 #1:静默吞没
物流子代理尝试请求承运商 API 时超时了。它在内部捕获了超时并返回 {"tracking_info": [], "status": "success"}。协调器看到了一次成功查询,没有结果。客户被告知:“您的订单没有物流追踪信息。”
包裹昨天就发了,有追踪号。客户的追踪信息存在——只是子代理无法访问。但协调器分不清”不存在追踪”和”追踪查询失败”,因为子代理对结果撒了谎。
模式: 子代理捕获错误 → 返回标记为成功的空结果 → 协调器基于错误前提继续 → 客户收到不正确的信息。
这不是轻微的数据缺口。一个账单子代理静默吞没超时,意味着每天 25 个客户被告知”没有未解决的账单问题”,而他们实际上有 $500 的多收费。错误的保证比承认查询失败更糟。
反模式 #2:工作流终止
一个 4 代理的代码分析系统运行安全、性能、风格和文档检查。文档代理因 API 超时失败了。协调器终止了整个分析。
开发者丢失了 10 多分钟的安全、性能和风格结果——这些都成功完成了——因为一个文档检查失败了。遇到第一个故障就终止的模式把任何子代理故障当作整个系统的故障,丢弃所有有效工作。
它们如何叠加
一个每月 1,000 次查询、5 个子代理的系统:
- 8% 的单个代理故障率(每月 400 次故障)
- 60% 被静默吞没 → 报告看起来完整但有隐藏缺口(占所有报告的 35%)
- 40% 被传播 → 触发工作流终止(占所有查询的 25% 被杀掉)
- 结果: 约 40% 的查询产出完整、准确的报告
静默吞没和终止把故障分成两种有害结果。单独看都解释不了全部损害。
正确模式:优雅降级 + 覆盖标注
三层,每层修复一个具体问题:
第一层:来自子代理的结构化错误上下文。
替换静默吞没。返回 {"status": "error", "failure_type": "timeout", "partial_results": [...], "alternatives": [...]}。协调器收到诚实的、可操作的信息。
第二层:协调器收集所有结果——成功的和失败的。 替换工作流终止。5 个代理中 1 个失败时,收集 4 个成功结果,标注缺口,继续。不丢弃有效工作。
第三层:最终输出中的覆盖标注。 用户看到:“安全:✅ 无问题。性能:✅ 2 个警告。风格:✅ 整洁。文档:⚠️ 不可用(超时——将重试)。“对什么成功什么没成功保持透明。
访问失败 vs 有效的空结果
静默吞没最危险的后果:协调器分不清”数据不存在”和”我们无法访问数据”。两者看起来都是空结果。
修复:使用不同的状态码。status=error 带故障详情用于访问失败。status=success 带空数据用于真正的不存在。协调器就能说”未发现依赖”(有信心,可操作)versus”依赖检查不可用”(诚实,有后续跟进)。
先修静默吞没
当两种反模式同时存在时:先修静默吞没。没有来自子代理的准确错误报告,协调器无论终止逻辑如何都无法做出正确决策。结构化错误上下文是基础——协调器层面的优雅降级依赖于收到诚实的信息。
只替换通用错误是不够的
有人提议用 {"status": "error", "message": "analysis unavailable"} 替换空的成功,这是改进——至少协调器知道有东西失败了。但通用错误仍然阻碍智能恢复。协调器分不清超时(重试)、认证失败(刷新凭证)和永久故障(切换来源)。带 failure_type、partial_results 和 alternatives 的结构化错误上下文为每种故障类型提供了合适的恢复方式。
三重冗余是过度工程
每个代理跑 3 次然后多数投票,算力成本翻三倍。优雅降级以零额外成本处理同样的故障,通过呈现带覆盖标注的部分结果。在 CI 分析中,开发者更愿意立即看到 4 个分析中的 3 个,而不是等第 4 个的 3 次冗余运行。
一句话总结: 用结构化错误传播替换静默吞没,用优雅降级替换工作流终止——三层修复(诚实错误 + 故障时继续 + 覆盖标注)把一个只有 40% 查询成功的系统变成部分结果总是保留、缺口总是透明的系统。