首页 / 资讯中心 / 文章详情

AI Agent执行链路可靠性设计:从异常处理到自愈机制

AI Agent执行链路可靠性设计:从异常处理到自愈机制 ★ FEATURED ARTICLE
做AI Agent落地有一段时间了。说实话最让我崩溃的不是Prompt调不好而是链路跑着跑着就挂了。我手底下有个客服智能体项目上线第一周就碰到过一次典型事故用户问了一句带歧义的话模型先是错误地调用了查订单接口接口返回超时后整个执行链路既没有重试也没有降级状态还丢了最后用户被晾在那里一分多钟系统日志里只有一行孤立的时间戳。那一次让我彻底意识到Agent能不能在真实业务里跑起来拼的不是某一个环节的聪明程度而是整条执行链路的可靠性设计。这篇文章想聊的就是这件事怎么从异常处理出发一步步把AI Agent的执行链路做成一个“出了错还能自己缓过来”的系统以及我实际踩坑之后总结下来的设计框架。适合所有正在做Agent应用、智能体平台或者打算把大模型能力接进生产系统的同学参考。1. 执行链路可靠性先看清问题出在哪1.1 一个故障案例引出的真实教训上面提到的客服Agent案例很有代表性。正常流程是用户提问 - 意图识别 - 调用订单查询接口 - 基于结果生成回复。听起来很简单但实际跑起来有太多“中间状态”可能出错。那次事故的完整过程是这样的模型先输出了一个工具调用指令代码按指令请求了接口但接口服务那会儿刚好在发布响应时间从平均200毫秒拉长到了8秒触发了我们设置的超时。超时之后代码抛了个异常直接结束了这次执行既没有告诉模型“你换个思路”也没有尝试重新调用更糟的是这一步的状态压根没落库。用户再追问时整个会话又从头开始但模型已经不知道之前查过什么了。结果就是用户困惑业务投诉我们复盘时发现连“卡在哪一步”都无法定位。这个案例暴露了三个层面的缺失第一对工具调用的异常没有分类和兜底策略第二整条链路缺乏自愈机制超时就是终点而不是一个可恢复的状态第三执行状态没有持久化链路一断就彻底失忆。这三个缺失恰好对应了Agent执行链路可靠性设计里最核心的三个议题异常处理、自愈机制、状态管理。1.2 Agent执行链路和传统系统链路的本质区别很多后端工程师第一次看Agent执行链路会觉得眼熟这不就是个分布式调用链吗无非多了一步调大模型。但真正做进去会发现两者的不确定性完全不是一个量级。传统接口调用是确定性的请求过去要么成功返回要么返回明确的错误码超时、幂等、副本机制都有成熟方案。Agent执行链路里最核心的执行者是大模型而大模型的输出天然带有概率性。同一个Prompt同一批上下文这次输出“调用查询接口”下次输出“直接自己猜一个答案”。模型甚至会在工具返回错误信息之后编造一个看起来合理的假结果来完成任务这在业界有个词叫“幻觉”在执行链路里就表现为“看似成功、实则失败”的脏结果。再加上工具返回的数据格式千奇百怪有的接口返回JSON有的返回XML有的干脆返回一段人类可读文本上下文窗口一长模型还可能把早期的关键指令忘了。这些不确定因素叠加在一起让Agent执行链路的可靠性和传统后端服务完全是两种挑战。你不能用“try-catch包一层”的思路解决必须从架构层面把每一个不稳定环节都看成“故障模式”提前想好对策。1.3 可靠性目标不崩只是及格线我把Agent执行链路的可靠性目标拆成三层分别对应不同的工程投入目标含义工程投入不崩任何单点异常都不会导致链路整体挂掉异常捕获、超时控制、拦截非法输出能自愈异常发生后系统能自动重试、降级、换路径最终完成任务或给出明确反馈重试策略、降级方案、错误回灌、状态恢复可诊断出问题时能快速定位是哪个环节、哪次调用、哪段上下文导致的Trace追踪、结构化日志、会话回放大多数团队做第一层就以为够了但我实际做下来的体会是第一层只能保证“系统不崩溃”保证不了“任务能完成”。用户在意的不是你系统有没有崩而是他的问题有没有被解决。所以从设计第一天起就要以“能自愈”和“可诊断”为目标哪怕第一版实现得粗糙一点架构上也要把这两层留出位置。2. 异常处理设计先把故障模式彻底枚举2.1 给异常分级不是所有异常都值得重试我见过很多Agent项目对异常的处理就是一把梭捕获所有异常统一重试三次不行就放弃。这种做法的坏处很明显有些异常重试一百次也没用比如模型服务返回400参数错误这是代码或Prompt的问题重试只会浪费时间和Token有些异常却必须立即处理比如权限凭证过期拖着不处理会导致所有后续步骤连环失败。我的做法是先给异常分级不同级别对应不同策略级别典型场景处理策略致命异常上下文超长、凭证失效、模型服务不可用、检测到注入攻击立即终止链路保存现场走人工兜底严重异常工具超时、返回格式错误、单次模型调用异常重试或降级把错误信息回灌给模型重新规划可忽略异常某条检索结果缺失、解析字段不完整但可用默认值记录日志继续执行不打断主流程分级有一个额外好处它能帮你在复盘时抓到真正的根因。如果发现“可忽略异常”大量出现说明上游数据质量有问题如果“严重异常”集中在某个工具说明该工具的稳定性需要优化。这些信号如果混在同一个日志级别里很难提出来做针对性处理。2.2 全链路故障模式清单工具层、模型层、上下文层光分级还不够你得知道链路里哪些位置可能出错。以下是我每次设计Agent执行链路必查的故障模式清单它基本覆盖了我在多个项目里遇到过的所有坑工具层故障工具调用超时响应迟迟不返回接口返回限流错误提示配额用尽返回的数据结构不符合预期字段缺失或类型错误接口返回空数据但HTTP状态码是200工具的副作用副作用已发生但因网络问题客户端收到超时最棘手的一种模型层故障模型返回空内容或只有结束标记输出不是合法的JSON/结构化格式模型没有按“先想后做”的格式输出直接跳过了工具调用模型幻觉编造了工具返回结果模型“过度自信”在缺少关键参数时仍尝试完成任务上下文层故障上下文超长被截断早期关键信息丢失多轮对话中工具返回结果与用户原始意图错位系统Prompt与用户内容发生角色混淆敏感信息被带进Prompt传给模型违反数据合规状态层故障链路执行到一半崩溃状态未持久化恢复后从头重跑同一请求被并发重放外部操作重复执行异步回调与主动轮询两种模式相互覆盖导致状态冲突把这份清单打印出来对照你自己的链路逐项检查你会发现很多之前没注意到的缝隙。比如“工具副作用已发生但客户端报超时”这一点我就是在一个支付类项目里踩了坑才补进去的重试扣费接口结果用户被扣了两次钱这种事故轻则投诉重则直接导致项目下马。2.3 结构化输出的“软约束”与“硬校验”Agent执行链路里模型输出格式是整套机制的信号来源所以这块的可靠性必须花功夫。我的方案是“软约束硬校验”双管齐下。软约束是指在Prompt里明确要求模型必须输出一个固定JSON结构比如工具调用指令包含“工具名”和“参数”两个字段。这靠Prompt工程解决但千万记得模型是概率系统哪怕Prompt里写了“必须”该不遵守还是可以不遵守你必须假设所有约束都可能被违反。硬校验是代码层面的兜底。模型输出后先做JSON解析和Schema校验比如用TypeScript的Zod或Python的Pydantic。校验失败时不直接放弃而是进入一个“输出修复循环”把校验错误信息拼进Prompt让模型重新输出。import json from pydantic import BaseModel, ValidationError class ToolCall(BaseModel): name: str arguments: dict def repair_output(raw: str, max_retries2): for i in range(max_retries): try: data json.loads(raw) tool_call ToolCall(**data) return tool_call except (json.JSONDecodeError, ValidationError) as e: # 把错误原因回灌给模型重新生成 raw ask_model( f你上一次的输出无法解析具体错误是{e}。 f请严格按以下JSON格式重新输出{{\name\:\工具名\,\arguments\:{{}}}} ) raise AgentChainFatalError(模型输出多次修复失败)这个修复循环是我最推荐的自愈手段之一它直接利用了模型“看到错误后自我纠正”的能力。实测下来大部分格式错误一轮就能修好两轮内成功率基本能到99%以上。2.4 超时策略别只设一个总超时模型调用和工具调用的延迟都极不稳定同一个工具平峰期可能200毫秒返回高峰期可能十几秒。如果只设一个总超时要么设短了导致正常慢请求被误杀要么设长了导致故障时用户要等很久。正确做法是分阶段设置超时模型单次调用超时一般建议10到30秒取决于模型规格。工具单次调用超时根据工具历史P99耗时来定通常是P99的两倍左右给足缓冲但兜住异常。全链路超时所有步骤的总预算建议60到120秒超过则终止并降级。超时触发后也不直接结束而是进入重试或降级分支这条我放到下一节详细讲。这里想强调的原则是超时是“开始处理异常的信号”不是“放弃任务的判决”。3. 自愈机制让链路自己缓过来3.1 重试策略指数退避加抖动才是正确的打开方式重试是最朴素的自愈手段但很多项目在重试上栽了跟头。最常见的问题有两个一是有异常就立刻重试工具还在故障期重试一万次也是失败二是不加限制地重试外部副作用被重复执行或者整个链路阻塞在重试循环里。正确的重试姿势是指数退避加抖动。指数退避的意思是第一次失败后等1秒第二次等2秒第三次等4秒成倍拉长间隔给下游恢复留出时间。抖动则是在等待时间上加上随机偏移防止大量请求在同一时刻集中重试制造二次峰值。import random import time def retry_with_backoff(attempt: int, max_retries: int 4): if attempt max_retries: return False # 2^attempt 秒加0~1秒抖动 wait min(2 ** attempt, 10) random.random() time.sleep(wait) return True这里想说一个容易漏掉的重点重试前要判断异常是否“值得重试”。凭证失效、参数校验类错误重试没有意义这类异常应该直接抛给上层处理。只有超时、限流、网络抖动、服务端5xx这类临时性错误才适合重试。这个判断逻辑一定要在重试入口做好否则你会在日志里看到大量无意义重试把费用和日志存储都打爆。3.2 降级路径有Plan B的Agent才是合格的Agent降级思路在传统系统里很常见主库挂了读从库CDN挂了回源站。但Agent执行链路里降级往往被忽略——很多团队设计的Agent只有一个“标准路径”一旦某个环节失败整个任务就失败了。我建议在设计阶段就为每条核心链路定义降级路径。场景不同降级方案完全不同完整版链路意图识别 - 多工具并行检索 - 融合排序 - 生成回复降级路径A意图识别失败时直接进入追问澄清流程不硬猜。降级路径B检索工具全部失败时放弃检索提示模型只用自己的训练知识回答但明确标注“当前回答未经外部资料验证”。降级路径C所有保底方案失败时回复“当前系统繁忙请稍后再试”并把上下文保存到待处理队列转人工处理。这里的关键是降级方案要在链路设计时就写好不能等到故障发生时现场拍脑袋。我习惯在配置中心里维护一张“能力降级表”每种能力有优先级列表运行时动态读取。能力优先级1优先级2兜底知识检索向量数据库搜索服务模型内建知识订单查询核心订单接口备用只读库提示用户稍等天气查询气象API主节点备用节点拒绝回答并引导查官网3.3 信息反馈型自愈把错误信息“喂回”给模型这是Agent自愈最核心、也最区别于传统系统的一个技巧。传统系统重试是重复同一个操作但如果模型第一次选错了工具你重复调同一个工具结果只会是同一个错误。Agent的优势在于错误信息本身可以成为下文让模型自己换一条路线。具体做法是工具调用失败后不机械重试而是把错误信息“翻译”成一段模型能理解的上下文拼接回对话记录然后让模型重新决策。你刚才调用的工具query_order执行失败。 工具返回的错误信息HTTP timeout after 8000ms, upstream service unavailable。 可能的原因查询服务正在发布或负载过高。 请换一种方式完成任务。你可以 1. 重试同一个工具如果错误是暂时性的 2. 尝试其他可用工具例如缓存查询 3. 如果确实无法完成明确告知用户当前服务异常。这个技巧我实测效果非常好。比如在客服场景里订单接口挂了模型看到这段错误信息后会自动转去查缓存数据或者主动向用户解释“订单系统正在升级建议您稍后再查”。用户得到的是一个有温度的解释而不是一句冷冰冰的“系统错误”。值得提醒的是错误信息喂给模型前要做“翻译”别把原始堆栈直接拼接进Prompt。大模型看不惯堆栈而且堆栈里可能包含敏感信息。翻译这一步相当于把技术错误转成业务语义既帮助模型理解也做了一层安全过滤。3.4 熔断机制保护下游也保护你自己重试和降级解决的是单次失败但如果某个工具已经连续失败几百次再让它重试就不合理了。这时候需要熔断机制。熔断器的经典三态在这里依然适用闭合态正常情况下请求正常放行。断开态连续失败次数达到阈值直接拒绝调用该工具。半开态经过一段冷却时间后放一个请求试探如果成功则恢复闭合态否则继续断开。Agent执行链路里的熔断不只是保护下游接口更是防止模型在一条错误路径上反复打转。想象一个场景模型连续三次选择调用一个已经熔断的工具每次调用都在空等。这既浪费用户时间又浪费Token。有了熔断调用在毫秒级就被拒绝错误信息会立刻回灌给模型逼它换一条路自愈效率会高很多。4. 状态管理与幂等性自愈的地基4.1 Checkpoint设计把每一步状态落到持久化存储里自愈最怕的是“不知道刚才干到哪了”。一个Agent链路动辄四五步意图解析、多轮工具调用、中间计算、答案生成、结果写入。每一步都可能失败如果状态只存在内存里进程一重启就全部丢失用户必须从头再来。我的设计是给每个Agent执行实例建一张状态表核心字段包括执行实例ID、当前步骤、步骤状态、工具调用记录、关键中间结果、错误摘要。每完成一个步骤就更新一次状态这样即使链路崩溃恢复时也能从最后成功的步骤继续而不是从头开始。一种实用的状态机设计from enum import Enum class StepState(str, Enum): PENDING pending # 等待执行 RUNNING running # 执行中 SUCCEEDED succeeded # 完成 FAILED failed # 失败可重试 COMPENSATING compensating # 正在补偿 COMPENSATED compensated # 已补偿 TERMINAL terminal # 终态链路结束状态迁移要注意一个原则任何非终态都允许在恢复后重新进入RUNNING比如PENDING或FAILED状态都可以恢复继续跑。SUCCEEDED的状态不能重复执行因为副作用已经发生了。每次状态变化都要记一条带时间戳的记录这就是你出事时回放的基础依据。4.2 幂等键重试再多也不会造成重复副作用状态管理里最容易翻车的是重试机制和副作用一起作用。客服Agent调用工单系统创建工单第一次调用超时代码触发重试结果工单创建了两次用户收到两条重复工单。这就是没有幂等设计的结果。解决办法是给所有“会对外产生副作用”的调用带一个幂等键。幂等键可以是执行实例ID加步骤序号比如“agent_12345_step_order”确保同一个执行实例的同一个步骤无论被重试多少次下游都只会执行一次。POST /api/ticket/create idempotency_key: agent_20250101_0001_create_ticket下游收到同样幂等键的重复请求时直接返回第一次执行的结果不重复创建。这样的话超时重试就是安全的因为我们知道副作用最多发生一次后面的调用只是在找回那个结果。我建议幂等键的生成规则在链路设计阶段就定下来并且做成框架级的能力而不是每个业务步骤自己实现。审计时你会发现这一步投入最值钱它能在你引入异步重试、消息队列、多副本执行这些高级机制时不额外增加风险。4.3 补偿与回滚有些步骤没法撤销只能“找补”传统后端有事务回滚Agent执行链路上很难做到严格的回滚因为工具调用很多是外部服务你调用的那一刻外部世界已经变了。比如下了一单你没法把对方的订单删除只能申请退款或者标记异常。这就是补偿和回滚的区别。我的经验是给每个会产生业务影响的步骤定义补偿方案发送通知出错时的补偿重新发送或者记录到“待通知队列”转人工处理。创建工单失败时的补偿不重试标记任务异常走人工创建流程。扣款成功但后续步骤失败时的补偿自动触发退款接口退款接口同样需要幂等键。信息查询类操作不需要补偿它们是幂等的、无副作用的。补偿逻辑的触发条件、执行顺序、失败后的处理策略都要提前写在链路配置里。尤其要注意补偿操作本身也可能失败所以补偿也要有重试和终态确认机制。链路状态机里的COMPENSATING和COMPENSATED不是摆设它们保证你能区分“这事已经过去了”和“这事还需要人工介入”。5. 可观测性把黑盒链路变成透明玻璃5.1 Trace追踪每个执行步骤必须能说清楚Agent执行链路最让人头疼的是调试困难问题到底出在模型理解错意图还是工具返回了坏数据还是上下文被截断不看内部细节根本不知道。因此从第一天起Trace追踪就是必须项不是优化项。我习惯每一个Agent执行步骤都开一个SpanSpan里记录四类关键信息步骤基本信息步骤名、执行顺序、耗时。语义信息当前用户的完整输入、模型输出的工具调用指令、模型对该步骤的“思考过程”。工具调用信息调用的工具名、输入参数、原始返回值、返回状态。状态迁移信息进入该步骤时的状态、退出时的状态、错误摘要。with tracer.start_as_current_span(tool_call_query_order) as span: span.set_attribute(agent.instance_id, instance_id) span.set_attribute(agent.step_name, query_order) span.set_attribute(agent.tool.name, query_order) span.set_attribute(agent.tool.args, json.dumps(args, ensure_asciiFalse)[:200]) span.set_attribute(agent.tool.response, response_text[:500]) span.set_attribute(agent.state.init, init_state) span.set_attribute(agent.state.final, final_state)这些信息在正常运行时不会全部打印它们存在追踪系统里只有在Trace详情页才展示。建议Saas方案用现成的开源追踪系统比如兼容OpenTelemetry协议的追踪后端自研的话重点把Span的字段规范做好别把大段内容塞进日志导致日志系统爆炸。5.2 指标监控别等到用户投诉才发现链路坏了日志告诉你某一个执行实例发生了什么指标则告诉你整个系统的健康状况。每个Agent项目至少要盯这组核心指标指标名含义告警阈值链路成功率完成全流程的对话占比低于95%告警工具调用失败率工具层错误占比超过5%告警重试率触发重试的执行占比超过10%排查熔断触发次数熔断器进入断开态的累计次数大于0就告警平均/最长链路耗时用户等待时长平均超30秒告警Token消耗单链路模型消耗费用超预算告警意图分布用户请求的意图类型占比无明显阈值用于发现异常意图激增有一类特别值得关注的指标是“静默失败率”执行链路没有报错但最终给用户的回复质量明显不达标比如回复只有一句“我无法回答”。这类失败最隐蔽光看日志查不出来。解决办法是加一道语义评估环节链路结束后对输出做一次简单的规则或模型判断看看是否有“明确回答、拒答、幻觉、空泛”这几类标记。5.3 会话回放能复现的问题才修得快日志和指标告诉你系统出了什么事但定位根因往往需要“把现场重演一遍”。会话回放是Agent执行链路最强大的调试手段它的思路是把某次执行实例的完整输入用户的每句话、每一轮的中间结果录制下来在一个隔离环境里重新执行观察每一步的差异对比生产与回放路径的偏差。实操的时候有两件事要注意第一录制要完整。不只是用户输入还包括当时的上下文窗口快照、模型版本、Prompt版本、工具返回结果。模型是概率系统同样的输入两次输出可能不同所以回放主要不是找“一模一样的复现”而是验证“如果换一种Prompt排序或者换一个工具调用顺序链路是否能成功”。第二回放要分级。线上全局回放太消耗资源我建议把回放分成三层冒烟回放只跑典型链路故障回放只跑历史上出问题的那一批输入压测回放则随机生成变体输入看系统在扰动下的稳定性。定期跑一轮故障回放回归效果很好。6. 验证与演练可靠性不是设计出来的是压出来的6.1 故障注入测试故意把链路搞坏自愈机制设计得再完善不验证就等于没有。我见过太多项目重试策略写得很漂亮但测试环境永远跑幸福路径直到线上出故障才发现降级代码压根没被执行过。故障注入是解决这个问题的标准手段。具体做法是在测试环境里故意制造各种故障看链路能不能扛住。常用的故障注入点包括注入故障操作方式预期响应工具调用超时把工具接口响应时间人为拉到20秒触发重试重试成功后继续工具返回格式错误篡改下游返回的JSON触发输出修复循环模型输出非法格式在模型网关层注入一个非JSON字符串触发硬校验修复循环模型服务不可用终止模型服务端进程触发熔断和降级路径A凭证过期手动撤销服务账号密钥触发致命异常走人工兜底上下文超长构造一个超过窗口限制的输入触发了摘要压缩逻辑并继续故障注入要自动化做成平台的日常测试任务不要只在发版前手动跑一次。连续跑一段时间你就能积累一份“链路弱点清单”哪个工具最脆弱、哪种故障模式最常触发降级这些数据反过来指导你对工具选型和架构做优化。6.2 回归基线让每一次修改都可验证Agent链路迭代频率很高Prompt一点小改动、模型版本一次升级都可能让原本稳定运行的链路突然变差。回归基线是防患于未然的关键。我的做法是维护一份固定的评估集按难度分层正常路径10到20个标准任务比如“查我的订单状态”、“帮我预订明天到某地的车票”。边界路径包含异常输入比如用户骂人、问法极其绕、一次带多个意图。故障路径触发特定故障比如“查询服务不可用时看Agent如何回复用户”。每次改Prompt、换模型或改工具都把这套评估集完整跑一遍用规则或模型打分来对比结果。重点是记录“关键步骤是否完成”比如是否成功调用了某个工具、是否提供了补救信息而不仅是看最终回复好不好看。这样能迅速发现某次修改导致的能力回退。6.3 版本管理与灰度发布别让Prompt改崩全链路最后想说一个很多团队容易忽视的点Agent系统的版本管理比传统后端更复杂。传统后端代码有版本Agent系统里不仅要管代码版本还要管Prompt版本、模型版本、工具接口版本和配置版本。这四者任何一个变了链路的可靠性都可能天翻地覆。我在实践中坚持三条规则一条链路只允许一个变量变化要么改Prompt要么换模型版本不要同时改好几个否则出问题没法定位。Prompt和配置必须有版本号运行时可以追溯到线上正在跑的是什么版本。模型升级或Prompt修改必须走灰度发布先放5%流量观察指标再逐步放量。有一次我更新了一个系统Prompt把“用户意图识别”的措辞改得更简洁了结果线上工具调用率从90%掉到60%模型开始跳过检索直接回答。因为只改了一个变量半小时内就定位回滚了。如果同时换了模型版本那个下午可能就得通宵排查了。写在最后的一些经验项目做多了以后我越来越觉得Agent执行链路的可靠性设计跟“堆防御工事”很像异常处理是围墙自愈机制是预备队状态管理是补给线可观测性是侦察兵压测演练是战前演习。每一样单独看不惊艳但组合起来才敢说这个Agent能拿到生产环境里跑。最后分享三个我实际项目中体会最深的点第一幂等设计是最容易被低估的但它是在你上重试、上并发、上异步之后唯一能兜住你的东西第二把错误信息回灌给模型重新规划是Agent自愈里性价比最高的手段没有之一第三状态持久化不是为了应付宕机而是为了让每一次重试和恢复都基于真实上下文而不是让用户在“从头再来”和“这不对”之间反复受罪。做可靠性设计本质上是逼自己把系统里每一个会坏的地方提前想一遍这个过程很烦但它决定了你晚上能不能睡得着觉。
阅读完成 · 觉得有帮助?
咨询建站