当Agent互相“推锅”这件事开始在我负责的系统里反复出现时,我意识到问题不在某个Agent,而在责任追踪机制。那是某零售团队的多Agent订单交付系统上线后的第三周,客户投诉电话打爆了客服群。三笔订单在毫不知情的情况下被开了发票并发送了发货通知,追责时每个Agent看起来都有完整的调用记录,却没有任何一条链路能说清楚“到底是谁对这笔订单做出了错误决策”。执行纠偏也没法下手,因为不知道该让谁回退、回退到什么程度。
这篇文章想把整套方法梳理一遍:先说清多Agent系统里“推锅”是怎么发生的,再讲如何通过决策链做责任追踪,最后聊执行纠偏的具体打法和长期机制。适合正在构建Agent工作流、被“Agent不听话但不知道怎么定责”折磨过的工程师、架构师和技术负责人看。
1. 为什么会推锅:从一次订单事故看责任链怎么断掉的
1.1 事故现场还原:每个Agent都有日志,却没人能定责
先说那次事故本身,它特别典型。
系统里三个Agent协作完成一笔订单交付:订单状态Agent负责从订单库读取状态,开票Agent根据状态决定是否开票,通知Agent负责把开票结果发给用户。链路很简单,正常情况下没有任何人工干预。
那天上游数据库里有一条脏数据,导致三笔订单被读取成了“已支付”。订单状态Agent把这个结果发给了开票Agent,开票Agent生成了发票,通知Agent随即给用户发了发货通知。等到真实支付还未完成的用户找上门,系统已经产生了三张完全不该存在的发票。
开始追责的时候,三个Agent各有说法:
- 订单状态Agent说:我是从订单库读的,数据库当时就是显示已支付。
- 开票Agent说:我根据上游结论判断,这是流程设计好的规则。
- 通知Agent说:我只管发送,业务状态判断不归我负责。
每一句话单看都没毛病。更气人的是,每个Agent都留有日志,调用关系一清二楚,谁在什么时候调用了谁都能查出来。但当你问“最终是谁对这笔订单的业务判断负责”时,答案却是空的。
这就是责任追踪缺失的典型症状:调用链有,决策链没有。
所谓调用链,记录的是“谁调了谁、什么时候调、耗时多少、成功还是失败”。它只能回答操作层面的事。而决策链要回答的是“Agent是根据什么信息、断言了什么结论、这个断言靠不靠谱、谁对这个断言负责”。后者才是责任归属的核心。那次事故里,如果每个Agent都在输出里记录了“我根据订单状态Agent的返回值,断言该订单已支付,断言依据来源为xxx”,到了排查阶段根本不用猜。
1.2 四个技术诱因,把责任链逐环切断
多Agent系统的责任链不是天生就断的,是设计过程中被四个因素逐环切开的。
第一个因素:Agent的职责边界停留在文档层面。架构设计时大家都会写清楚“订单状态Agent负责状态读取,开票Agent负责开票”,但写进代码往往只有一句Prompt描述。Prompt描述天然有解释空间,“根据订单状态判断是否开票”这句话,不同版本模型可能理解出完全不同的执行策略。文档不是机器约束,当边界只存在于文档里,出了事谁也说不清边界到底是什么。
第二个因素:Agent之间传递的是自然语言消息,语义容易发生飘移。上游Agent说“订单状态:已支付”,下游Agent正常情况下会当作业务判断采信。问题是这个采信没有经过任何校验机制,下游不会提问“你这个已支付是基于什么数据得出的”。自然语言最糟糕的地方是它看起来很肯定,甚至带着一种权威感,但没有任何机器可验证的结构。
第三个因素:异步解耦导致失败不阻塞。很多Agent协作是队列或者事件的架构,上游Agent处理完了往队列里一丢,下游Agent从队列里拿消息继续处理。一旦出现异常,上游Agent完全感知不到下游发生的事。这种解耦设计天然让Agent“事不关己”。
第四个因素:只记录执行结果,不记录决策依据。日志里写了“生成发票成功”“发送通知成功”,却没写“为什么生成发票”。这是最要命的一条,也是责任追踪必须解决的核心。
记住这四个诱因,后面所有设计都是在跟它们对抗。
2. 决策链设计:让每个Agent的每个判断都有据可查
2.1 从调用链升级到决策链:记录“我是根据什么做出这个结论”
既然调用链不够用,就给Agent的输出加一层结构化信息。
我给内部的多Agent运行时加了一个字段叫assertions,专门承载决策依据。每个Agent在输出结论之前,必须列出自己做过哪些关键断言,每个断言都要注明来源、可信度和时间点。结构化之后,责任追踪就有了抓手。
设计决策链有两条核心原则,缺一不可。
原则一是断言必须可溯源。每个断言里要写清楚source和对应的原始数据标识。比如开票Agent断言“订单已支付”,source必须是订单状态Agent的某一次具体输出,对应着trace_id里的某个节点。如果这个信息来自数据库读取,就要写明数据库表名和记录ID。追责时看到的是一个可以追溯到具体源头的链条,而不是一句孤零零的“已支付”。
原则二是不确定性必须显式记录。模型Agent在给出结论时往往会遭遇信息不足的情况,问题在于很多Agent为了完成任务会把不确定性“脑补”掉,输出一句非常肯定的结论。决策链记录里必须要求Agent在拿不准时写明confidence,或者直接标识为“信息不完整,需要人工确认”。
实现角度,一个Agent输出消息可以设计成这样:
{ "trace_id": "order-20250618-0003", "agent": "billing_agent", "action": "generate_invoice", "input": { "order_id": "order_0088", "input_digest": "prev_node_hash_abc123" }, "assertions": [ { "key": "order_status", "value": "paid", "source": "order_status_agent", "source_trace_id": "order-20250618-0003-node02", "confidence": 0.97, "timestamp": "2025-06-18T10:22:31.000Z" } ], "output": { "invoice_id": "INV-2025-0012", "status": "created" } }这段结构最关键的是source_trace_id,它把当前Agent的决策和上游Agent的输出节点直接连起来。系统里任何一笔任务,按trace_id和source_trace_id就能从末端反推到最源头,形成一条完整的决策链。
2.2 唯一追踪ID与消息血统:把每一步挂在同一条绳上
决策链要真正可用,前提是整个系统从入口开始就有一个不变的身份标识。
我的做法是给每个任务生成一个全局唯一的trace_id,从任务第一次进入系统开始,无论经过多少个Agent,无论中间是否发生分支合并,这个ID始终贯穿。任何Agent在处理任务时,都必须把自己所在节点的关联信息挂到这条链上。
只有trace_id还不够,因为跨Agent传递时经常会有多个分支合并,如果下游同时收到多个上游结果,仅仅一个ID没法还原依赖关系。所以在消息传递时还要保留input_digest,也就是输入摘要。下游Agent需要把“我用了哪几个来源的信息”记录清楚。当两个Agent各执一词时,用input_digest可以直接还原当时Agent看到的信息到底是什么。
另外建议给消息加一个简单的版本号或者哈希校验。实践中我遇到过下游Agent使用的其实是缓存里的旧数据,但时间戳却显示为最新。加了摘要校验之后,这类“数据版本不一致”问题能在追责阶段直接被识别出来。
2.3 信任边界显式化:善意信任模型与验证模型的取舍
决策链能帮你在事后把责任算清楚,但真正能减少事故的,是事前设计信任边界。“信任边界”这个术语听着抽象,其实就一句话:下游Agent在多大程度上无条件采信上游Agent的输出。
实际系统里可以把采信模式分成两种。一种是善意信任模型,下游Agent拿到上游结果直接用,不做额外校验。走通一条链路最短,效率高,但上游一旦出错,错误会不带衰减地往下游传播。另一种是验证优先模型,下游Agent在执行业务动作前,会对上游结论做交叉验证,比如查业务库、查状态快照、或者调用独立的校验Agent。安全,但要付出额外的延迟和费用。
这两个模型的核心区别在下游Agent面对上游断言时的反应:直接执行,还是先查证再执行。在风险敏感的用例里(开票、支付、削库存),必须配置成验证优先。在普通信息整理类任务里,善意信任也能跑得不错。我的建议是按动作的风险等级分级配置,而不是一刀切选择某个模型。
2.4 信任边界在实际运行中的表现
这里放一个实测配置示例,技术人员可以直接抄作业。我在调度器里给每个Agent定义了诉求级别:
agents: order_status_agent: verify_policy: no_verify authority: read_order_db billing_agent: verify_policy: verify_before_act verify_source: order_status_db on_verify_fail: refuse notification_agent: verify_policy: no_verify requires_billing_receipt: truebilling_agent配置了verify_before_act,意味着它在开发票前必须自己确认订单状态,校验失败就拒绝。通知Agent没有业务判断能力,所以配置为必须有开票凭证才允许发送。这套配置上线后,前面那种三笔脏数据事故根本走不到开票那一步,因为billing_agent在校验阶段就会把订单拦下来。
切身体会是:责任追踪的事后账目当然要有,但把信任边界显式化、给Agent配置校验动作和拒绝权利,才是从源头压缩推锅空间的办法。
3. 执行纠偏的落地打法:重试、回退、换人还是人工
3.1 四种纠偏策略与适用边界
责任追踪抓到了“谁的问题”,接下来要紧的是让事情恢复。执行纠偏不是一种孤立的操作,它是一套分级策略。
| 策略 | 触发条件 | 适用场景 | 主要风险 |
|---|---|---|---|
| 局部重试 | 单个Agent处理失败,但状态未发生变化 | 临时性错误、模型输出解析失败 | 如果错误是系统性规则问题,重试只会重复犯错 |
| 状态回退 | 下游Agent尚未执行不可逆动作 | 数据状态错误但不涉及外部副作用 | 回退范围容易过界,可能误伤正常操作 |
| 动态换人 | 同一Agent连续多次失败或产出质量差 | 模型能力不足、指令理解偏差 | 新Agent可能因缺少上下文而再次犯错 |
| 人工介入 | 涉及不可逆动作或责任争议严重 | 开票、支付、删除等高风险操作 | 人工等待时间不可控,拖慢整体流程 |
这四招不是互斥关系。我实际处理过的情况是:先判断当前状态有没有不可逆副作用,如果没有,优先局部重试;如果发现是Agent对指令理解有偏差,直接换Agent;如果已经产生了副作用,进入回退流程,必要的时候人工介入。
关键原则是:先做影响评估,再做纠偏动作。影响评估的本质就是前面说的责任追踪的延伸——通过决策链看当前动作传播到了哪些下游节点,哪些状态可以回滚,哪些动作已经不可撤销。这一步做扎实了,后面才不会越纠越乱。
3.2 补偿操作优先:不要动不动就全量回滚
执行纠偏里最常见的误操作,是遇到问题就整条链路回退。实际上很多动作是不需要、也不应该回滚的。
举个例子。开票Agent已经生成了一张发票,但通知Agent还没来得及发出去。这时候正确的纠偏动作是作废这张发票,而不是把订单状态、库存状态全部一起回滚。因为订单本身可能没有任何问题,回滚一个没问题的状态反而是引入新故障。
业界有个现成的思想叫补偿事务,在多Agent系统里同样适用。每个带有副作用的Agent动作,都应该注册一个对应的补偿操作。开票的补偿是作废发票,扣减库存的补偿是回补库存,发送通知的补偿是再发一条更正通知。回退是最后手段而不是默认手段,日常纠偏能用补偿解决的,都别去动正常节点。
我用了大概一个月才踩明白这个坑。最开始遇到开票异常我就查所有下游节点,把整个任务重置,结果经常把已经正确处理的数据又给打回去,还会引起新的状态不一致。后来改成“动作级补偿优先,链路级回退兜底”,故障恢复时间明显缩短。
3.3 给Agent“可拒绝权”和“前置校验”,从源头减少推锅
纠偏做得再好,也只是事后补救。执行纠偏的上游手段,是在设计阶段就让Agent有能力拒绝错误请求。
第一道闸门是前置校验。下游Agent在执行动作前主动核验上游断言。前端效果前面已经说过了,开发票前必须确认订单状态,校验失败就拒绝。这套机制本质上是在给“推锅”物理制造阻挡点。
第二道闸门是可拒绝权。允许Agent在输出中显式声明“我拒绝”,并给出结构化原因,而不是不得不完成一件它认为有风险的事情。我见过一个Agent为了保证通过率,在信息不够时强行编了一个结论。可拒绝权解决的就是这个问题——当Agent不确定时,它可以选择“拒绝”,而不是“猜测”。在系统Prompt里加一句“如果信息不足以做出安全决策,必须输出action: request_human_confirmation”,效果立竿见影。
第三道闸门是要求Agent标注不确定性。即使Agent决定继续执行,它也要在决策链里写明哪些断言可信、哪些是推测。这样就算最终出了事,追责时至少能区分“决策依据不足导致的失败”和“完全没有依据就乱来的失败”。
这三道闸门配合在一起,多Agent系统的故障模式会发生变化:很大一部分风险在执行前就被拦截掉了。
4. 机制内建与复盘闭环:追责最终要变成系统本能
4.1 复盘会怎么开:从“吵归谁”变成“看决策链”
之前组里开故障复盘会,大家围着日志各执一词扯了几十分钟。后来我们把决策链完整拉出来以后,复盘会的画风完全变了:不用再争论“谁撒了谎”,只需要盯着链条看“哪一环的断言依据不足、哪个节点没有校验前置、哪个Agent该拒绝却执行了”。
复盘的标准路径大致是这样:
第一步,按trace_id拉出该任务的全部决策链节点,先把执行成功和失败的节点、断言和来源列出来形成一个可视图表。
第二步,找责任断裂点。重点是看每个Agent的断言来源是否可靠、是否经过校验、是否存在uncertainty却没有上报。
第三步,针对断裂点改系统。如果发现是某个Agent在校验失败后仍然继续执行,说明它的instruction没有把“拒绝”优先级写清楚,要改的是Prompt或配置。如果发现是上游数据源本身脏数据,要补的是上游校验逻辑。如果发现是Agent在信息不足时脑补结论,要补的是“强制标注不确定”的约束。
复盘的目的不是开除某个Agent,而是让责任追踪机制本身变强。
4.2 三个关键指标,衡量责任追踪与纠偏系统的健康度
机制做没做对,不能靠感觉,要盯指标。我目前固定统计三个指标。
第一个是平均定责时长,也就是从故障发现到定位责任人/责任节点花多长时间。这个指标之前我在没有决策链的时候,平均要花几个钟头,后来有了decision链,基本缩小到一段时间以内。定责时长越短,说明决策链的完整度越高。
第二个是纠偏成功率。发起执行纠偏后,系统恢复到正常状态的比例。这个指标反映了补偿操作和回退策略设计得好不好。如果纠偏成功率长期低于预期,就要查是不是回退范围定得不准,或者补偿操作本身有bug。
第三个是故障复发率。同一类责任断裂点在两周内是否再次出现。如果同一节点反复出问题,说明之前的修复只是在打补丁,没有真正改变Agent的决策逻辑。
这三个指标不用很复杂,Grafana或者任意追踪系统里都能算出来,关键是持续盯。
4.3 把责任追踪内建到框架层,而不是事后加日志
最后一个建议,也是我最想强调的:责任追踪必须内建到Agent运行时的框架层,而不是在各业务Agent里手工加日志。
内建的意思是框架负责给每个任务生成trace_id、维护决策链的挂载、提供统一的责任链持久化和查询接口,同时支持按动作风险注册前置校验策略。业务Agent只负责填写assertions相关字段,框架保证这些字段最终进入责任链存储。这样如果有100个Agent,责任追踪的能力也是一致的,不会出现一个Agent记录得很细致、另一个Agent完全不记录的情况。
反而是那种挨个Agent修改Prompt让它们“记得打日志”的做法,一开始看着省事,规模一上来就会脱节:“天然有记录”的Agent程序跑得很稳定,但那些没被约束的Agent又开始了自由发挥。
另外一个容易被忽略的内建点:校验策略最好也放在框架层配置,而不是散落在各个Agent的代码里。还是按动作风险等级统一管理,运营同学可以随时调整策略,不用去找每个Agent的开发者改代码。
结尾:一点个人体会
有一次事故,我花了半个多小时才定位到是某个Agent在“上游校验失败后仍选择继续执行”,原因只是它的系统Prompt里没有“拒绝优先级高于完成优先级”这句话。这之后我彻底改变了做Agent系统的方式:不是等Agent出了问题再去救火,而是把每个Agent的决策依据当成系统的一等公民来对待。给Agent配上责任链,它会回报你可观测、可定责、可纠偏的工作方式。
最后再分享一个小技巧:在Agent的system prompt末尾加一句“当你知道的信息不足以支撑决策时,直接告诉我,不允许猜测”。这句话看着简单,实际省掉了很多查日志的时间,因为它强制让Agent把不确定性暴露出来,而不是封装成肯定句。真正让责任追踪运转起来的是这个设计习惯,不是某个特别的工具或组件。