☰
Agent失败排查:从粗错误码到失败链的完整拆解
2026/11/2 8:02:35 网站建设 项目流程

1. 从错误码到失败链:一次 Agent 排查的真实起点

做 Agent 开发的人应该都有过这种体验:用户那边丢过来一句"模型又抽风了",你顺着日志看过去,发现 Agent 内部抛了一个看似无关紧要的错误码,然后整个链路就断了。更麻烦的是,同一个错误码在不同场景下表现完全不一样,有时候重试几次就好了,有时候换个模型就好了,有时候改个 prompt 就莫名其妙正常了。于是大家习惯性把锅甩给模型,说"模型能力不行""模型理解错了""模型幻觉了"。

但我在实际排查过几个 Agent 项目之后发现,模型背的锅,其实有一大半都是冤枉的。真正的问题往往藏在 Agent 的编排逻辑、工具调用协议、上下文管理,甚至是错误码本身的设计里。这次分享的案例,就是一次从"粗错误码"到"失败链"的完整排查过程。所谓粗错误码,就是那种只能告诉你"出错了",但说不清楚哪里出错、为什么出错的返回值;而失败链,指的是从用户请求发起到最终失败,中间经过模型调用、工具执行、状态更新、重试机制等多个环节的完整因果链条。搞清楚这条链,才算真正找到根因。

这个内容适合谁看?正在做 AI Agent 开发、Agent 框架选型,或者被生产环境里各种"模型玄学问题"折磨的工程师。也适合那些刚接触 Agent 开发,想知道错误到底该怎么排查的新手。我会把这次排查的完整思路、用到的工具、踩过的坑、以及最后沉淀下来的排查方法都讲清楚。内容不依赖某个特定框架,但我会结合主流 Agent 框架(比如 LangGraph、AutoGen 这类编排层)来举例,方便你对号入座。

我个人比较反感那种"遇事不决就换模型"的解法。因为在大多数 Agent 架构里,模型只是整个系统里的一个组件,它前面有编排层,后面有工具层,旁边还有记忆模块和状态管理。任何一个环节出错,最终都可能表现为"模型输出不符合预期"或者"Agent 任务失败"。如果不把失败链拆开看,你换了再强的模型,大概率还是会在同一个地方翻车。

2. 为什么"粗错误码"是排查的第一大障碍

2.1 错误码粒度太粗,等于没报错

先说说这次案例里最典型的一个现象:Agent 在调用某个工具时,返回的错误码是ERR_TOOL_EXEC_FAILED,后面只附带了一句 "tool execution failed"。这个错误码覆盖了所有的工具执行失败情况——参数解析失败、工具内部异常、网络超时、权限不足、返回结果格式错误,全都归到这个码里。你在日志里看到这个错误码,能获得的有效信息约等于零。

你可能会说,这不就是"工具执行失败"吗,去看工具日志不就行了?问题是,在很多 Agent 架构里,工具调用是由编排层统一管理的一组函数,这个函数可能内部调用了多个子服务。比如一个search_and_summarize工具,它既要调搜索服务,又要调摘要模型,还要做结果后处理。任何一个环节挂了,编排层都会笼统地返回同一个错误码。如果你只盯着这个错误码,根本不知道是底层的搜索 API 超时了,还是摘要模型的 prompt 模板拼错了,还是后处理的正则表达式没匹配上。

这次项目里的实际情况就是这样:Agent 在完成一个多步骤任务时,第二步需要调用一个外部数据查询接口。接口偶尔会返回 5xx,但真正导致失败的不是 5xx 本身,而是 Agent 没有针对 5xx 做重试,直接把 5xx 当成最终结果传给了下一步的模型。模型拿到这个粗错误信息,自然无法理解上下文,于是开始"一本正经地胡说八道",告诉用户"数据不存在"或者"任务无法完成"。从用户视角看,这就是模型能力不行。但从链路上看,模型的输出是被错误信息误导的结果。

这个案例让我意识到一个问题:错误码的设计质量,直接决定了 Agent 系统的可观测性。如果错误码只做到"知道失败了"这个层级,那排查问题就只能靠猜,猜不中就换模型,换模型解决不了就重启,重启还不行就骂模型。这本质上不是技术问题,而是工程问题。

2.2 错误码丢失关键上下文,链路断了

比粗错误码更坑的是,有些框架在传递错误时会把上下文丢掉。比如工具内部抛了一个异常,包含具体的 HTTP 状态码、响应体片段、超时时间,但框架统一封装后,只保留了一条 message 字段,其他全部吞掉。等到了模型那边,模型看到的错误信息变成了 "Internal error, please retry later"。这种信息对模型来说完全不可用,它没法区分是暂时性故障还是永久性失败,也没法决定自己是该重试、该换工具,还是该终止任务。

我在项目里给 Agent 加过这样一个测试:故意让一个工具返回带详细 body 的错误响应,看模型会怎么处理。结果发现,如果把所有上下文都传给模型,模型大概率能做出正确判断——它会说"这个错误是参数问题,我需要调整参数后重试";但如果只传一个简化后的错误码,模型会表现得非常迟钝,要么直接放弃,要么反复尝试同样的操作。这说明模型本身没有"变笨",是错误信息剥夺了模型进行推理所需的关键输入。

所以后来我在设计 Agent 的失败处理机制时,有一条铁律:错误传播时,必须保留原因链。也就是从底层异常到上层错误码,每一层的原始信息都要带上,并且格式化成一个结构化对象,比如{code, message, cause, retryable, context}。这样无论后面接的是日志系统还是模型 prompt,都能拿到足够的信息去决策。

2.3 模型侧的错误码理解偏差

还有一种情况是,错误码本身没啥问题,但模型对错误码的解释有问题。比如错误码明明写着RATE_LIMITED,模型却可能理解成"服务不可用",然后给出一个错误的应对策略——不是等待重试,而是直接宣告任务失败。这是因为大多数通用模型并没有接受过"如何阅读系统错误码"的专门训练,它默认以为错误码是人类之间沟通用的,而实际上错误码是给程序和系统看的。

这个问题的解法其实很简单:在 Agent 的 system prompt 里加入错误码的说明文档,把常见错误码的含义、可重试性、推荐处理策略都列清楚。好比给新员工发了一本《内部错误处理手册》。我在项目里做过 A/B 测试,加入错误码文档之后,Agent 对RATE_LIMITED的处理从"直接放弃"变成了"等待 30 秒后重试",任务成功率提升了接近两成。这说明很多时候不是模型蠢,是你没把“该怎么处理错误”的规则喂给它。

3. 失败链拆解:找出真正的"元凶"环节

3.1 把 Agent 任务拆成一条完整的事件链

排查 Agent 问题,我建议先别急着看模型输出,而是先把整个任务流程画成一条事件链。拿这次项目里的任务举例:用户输入一个复杂查询,Agent 需要先理解意图,然后决定调用哪个工具,拿到工具结果后进一步推理,最后给出回答。这里面至少有这几个环节:

  • 意图识别环节:模型判断用户想干什么,可能要输出结构化指令
  • 规划环节:模型决定执行哪些步骤,顺序是什么
  • 工具调度环节:编排层根据规划结果调用对应的工具
  • 工具执行环节:工具内部实现具体的业务逻辑,可能涉及外部 API
  • 结果处理环节:将工具返回的原始结果解析、转换、截断
  • 最终生成环节:模型结合所有中间结果生成最终回复

任何一个环节出问题,后续所有环节都会被污染。比如规划环节把步骤顺序排错了,那工具执行结果就会是错的,但你直接看到的是"模型最终回答错误",以为是模型逻辑能力弱。其实模型是拿着错误的中间输入在推理,巧妇难为无米之炊。

这次排查的转折点,就是我把日志按照事件链顺序排列之后,发现了一个关键线索:工具层返回的ERR_TOOL_EXEC_FAILED发生在模型第二次调用工具之后,但第一次调用是正常的。这说明问题不是模型不会规划,而是第二次调用时发生了什么特殊状况。我顺着这条线索往前查,发现第二次调用传入的参数里包含了一个特殊字符,而外部接口对特殊字符处理异常,导致接口直接返回 5xx。问题根源根本不是模型,也不是 Agent 编排逻辑,而是工具层的一个边界 case 没处理好。

3.2 用时间线和状态机记录失败路径

拆失败链的时候,光看事件顺序还不够,最好能把每个环节的输入、输出和状态变化都记录下来。我常用的做法是给 Agent 的每一步都打上 traceID,然后记录每个节点的状态:等待、执行中、成功、失败、重试、终止。这样从 traceID 下手,就能还原出一条完整的失败路径。

比如这次案例里,我用 traceID 还原出来的路径是这样的:

  1. 用户请求进入编排层,意图识别成功,模型输出规划步骤
  2. 第一步工具调用成功,返回结果 A
  3. 第二步工具调用返回 5xx,错误码被封装为ERR_TOOL_EXEC_FAILED
  4. 编排层未对该错误码做重试处理,直接标记该步骤失败
  5. 模型拿到失败状态,尝试生成回复,但因为系统 prompt 里没有错误处理策略,模型生成了一个"任务无法完成"的回答
  6. 用户看到回答,反馈"模型不行"

这条路径里,真正可以先修复的节点其实是 3 和 4。如果你在第 3 步识别出 5xx 是暂时性错误,加入重试机制,那第 4、5、6 步根本不会发生。而第 5 步即使发生了,如果你在 prompt 里告诉模型"遇到可重试错误时,主动重试一次",模型也不至于立刻放弃。所以模型在这里只是"背锅者",真正的 bug 是编排层缺少重试机制。

3.3 失败链的三种典型形态

根据我做的 Agent 项目经验,失败链大致可以分为三种形态。第一种是上游污染型,即上游某个环节返回了错误或错误格式的数据,下游模型被迫基于坏数据推理。这种最常见,解决办法是在每个环节的接口处加校验,发现异常立刻终止或纠正,别把脏数据往下传。第二种是中游断链型,即编排逻辑在某一步失败后终止了整个流程,没有提供降级或分支方案,导致模型本来可以继续完成任务,但被"前置失败"卡住了。第三种是下游误判型,即下游模型对错误的解读有偏差,明明是可重试错误却做了不可逆的决策。这种需要靠 prompt 约束和结构化的错误信息来改善。

拿这次案例来对照,它属于典型的上游污染型 + 中游断链型的组合:外部接口返回 5xx 是上游污染,编排层直接终止是断链。所以修复策略也得很明确:在上游错误发生时先判断是否可重试,在编排层设计退避重试机制,同时给模型提供足够的错误上下文。三个修复点都做完,这个任务的成功率才真正上来了。

4. 实操全过程:从复现到修复的一次完整记录

4.1 复现问题:稳定复现是排查的前提

排查这种"偶尔失败"的问题,最大的难点是不好复现。这次项目里的外部接口 5xx 是间歇性的,可能跑十次任务才出一两次。为了高频复现,我用了故障注入的办法:在测试环境里用一个 mock 工具模拟外部接口,让它按照某种概率返回 5xx。比如设定第二个调用周期内有 30% 的概率返回 5xx,跑完几十次任务后,就稳定复现了失败链。

复现过程中,我发现一个容易忽略的点:Agent 的决策是带随机性的(模型采样温度、概率分布都会影响),所以同样的任务,不同次运行可能走不同的路径。为了尽量保证复现的一致性,我在测试时把采样温度调成 0,并固定随机种子。如果项目里用的是 OpenAI 这类模型 API,可以在参数里加seed字段(部分模型支持);如果是本地模型,也要在推理配置里固定相关参数。这一步能让复现效率大大提高。

4.2 关键一环:改造错误处理中间件

复现成功之后,我并没有直接去改工具代码,而是先给编排层加了一个错误处理中间件。这个中间件的作用有两个:一是统一捕获底层工具抛出的异常,并提取结构化信息;二是根据错误类型决定重试策略。中间件改造后的错误对象,长这样:

@dataclass class AgentError: code: str # 粗错误码,如 ERR_TOOL_EXEC_FAILED message: str # 人类可读的消息 cause: str # 原始异常信息,如 HTTP 500 retryable: bool # 是否可重试 retry_after: int # 建议等待时间(秒) context: dict # 调用上下文,如工具名、参数摘要、traceID

有了这个结构,编排层在捕获到错误后,就能根据retryable字段决定是否重试。同时,这个错误对象还会序列化成 JSON,追加到传给模型的上下文消息里。这样模型下次生成回复时,就能看到"错误码是什么、原因是什么、是否重试过、接下来该怎么办",推理依据就充足了。

4.3 优化重试策略:指数退避 + 最大重试次数

这次案例中,外部接口偶尔返回 5xx,属于典型的暂时性故障,适合用指数退避重试。我在编排层实现了这样一个策略:

def call_with_retry(func, max_retries=3, base_delay=1.0): for attempt in range(max_retries): try: return func() except AgentRetryableError as e: delay = base_delay * (2 ** attempt) logging.warning(f"attempt {attempt+1} failed, retry in {delay}s, cause={e.cause}") time.sleep(delay) raise AgentRetryableError("max retries exceeded")

指数退避的延迟序列是 1 秒、2 秒、4 秒。对大多数外部接口来说,前三次重试通常足够绕过短暂的瞬时故障。但要注意,不是所有错误都适合重试。如果错误码是INVALID_ARGUMENT或PERMISSION_DENIED,重试再多次也是白搭,反而会拖慢整个链路。所以retryable这个字段必须由底层工具根据自己的业务逻辑来判断,编排层不该一刀切。

4.4 给模型补上"错误处理手册"

重试机制加入之后,还有一个关键的 prompt 优化要做,就是给模型一份"错误处理手册"。我的做法是在 system prompt 里加一段描述:

当你调用工具失败时,会收到一个错误对象,格式如下: { "code": "错误码", "message": "错误概述", "cause": "原始异常原因", "retryable": true/false, "retry_after": 秒数, "context": 相关上下文 } 你需要根据以下规则处理: 1. 如果 retryable 为 true,说明工具层会自动重试;你可以继续等待重试结果,不要直接向用户宣告失败。 2. 如果 retryable 为 false,说明是永久性错误;请阅读 cause 和 context,调整你的策略后重新尝试,或者向用户说明具体原因。 3. 不要将内部错误码直接展示给用户,请用通俗的语言解释发生了什么。

这段 prompt 的实际效果相当明显。在没有这份手册时,模型遇到RATE_LIMITED错误可能会说"抱歉,我暂时无法完成这个任务"。加了手册之后,模型会说"服务暂时繁忙,请稍等,我稍后重试",并且自动等待retry_after时间。这里的核心逻辑是:模型需要知道系统期望它怎么处理错误,你告诉它,它就做得对;你不告诉它,它就自由发挥。

4.5 修复后的效果验证

修复完成后,我在同一批测试集上跑了 50 次任务,对比修复前后两个数据指标:任务成功率和失败后的正确处置率。修复前,遇到瞬时 5xx 的任务基本全部失败,用户看到的都是"模型无法完成任务";修复后,90% 以上的瞬时 5xx 都能通过重试绕过,剩余的也能给出合理的降级处理说明。整体任务成功率从 71% 提升到了 92%。这个提升里,模型本身没换,参数没变,只改了三处:错误码结构化、编排层重试机制、system prompt 错误处理说明。这足以说明问题根本不在模型。

5. 排查方法论沉淀:一套可复用的 Agent 问题定位流程

5.1 流程总览:五步定位法

经过这次排查,我把整套方法沉淀成了一个五步定位流程,现在已经在我负责的 Agent 项目里稳定使用了。第一步,还原链路:从用户请求开始,记录每个环节的输入、输出、状态、耗时,形成一条带 traceID 的完整事件链。第二步,错误码分类:把项目里所有错误码梳理一遍,标记出哪些是"粗错误码"(信息量低),哪些是"精确错误码"(信息量高),对粗错误码进行结构化改造。第三步,判断重试性:针对每一个错误类型,明确它是否可重试,以及重试的策略(指数退避、固定间隔、立即重试)。第四步,检查上下文传递:确认错误信息在传播过程中是否保留了原始原因,模型能否看到足够的决策信息。第五步,验证修复合规性:修完之后不仅看成功率,还要看错误处理路径是否符合预期,比如模型有没有按照错误手册行动。

这套流程听起来简单,但执行起来容易偷懒的点在于第一步。很多团队根本没有给 Agent 的每个环节加 traceID,导致链路还原只能靠日志时序猜。所以在项目初期就引入 trace 体系非常值得,比后面出了问题再补要省钱得多。

5.2 结构化错误码的字段设计建议

关于错误码结构,我建议至少包含下面这些字段,这也是我改了多个项目后认为比较稳的一套方案:

字段类型含义示例
codestring稳定的机器可读错误码TOOL_HTTP_5XX
messagestring人类可读的简要消息"调用外部接口时返回 5xx"
causestring原始异常信息,尽量详细"HTTP 503, body: service unavailable"
retryableboolean是否可安全重试true
retry_afterint建议等待秒数,0 表示立即重试5
contextobject调用上下文,如工具名、输入参数摘要、traceID{ "tool": "search_api", "trace_id": "abc123" }
timestampstring错误发生时间"2025-01-05T12:00:00Z"

值得强调的是,code字段一定要细分。比如ERR_TOOL_EXEC_FAILED这种太粗了,至少要拆成TOOL_PARSE_ERROR、TOOL_NETWORK_ERROR、TOOL_BUSINESS_ERROR、TOOL_TIMEOUT、TOOL_RATE_LIMITED这五类。每类再带上自己的特定参数,比如超时时间、限流窗口大小。错误码一旦细分,编排层才能针对性地决定重试、降级、终止或换路,而不会真假不分地莽一把。

5.3 别忽视 Agent 的"自我修复"能力

很多 Agent 开发者遇到失败就第一时间想用更强大的模型来兜底,但往往忽略了 Agent 本身的自我修复能力。所谓自我修复,是指 Agent 在失败后能通过重新推理、修正计划、重试工具等方式自行解决问题。这背后依赖的不是模型有多聪明,而是系统给了模型多少"修正错误的机会"。

比如,当模型发现第一次工具调用返回的结果不符合预期时,它是否可以反馈"这个结果有问题,我需要换一种查询参数",然后再次调用工具?在传统流水线设计里,这一步直接结束,流程失败;在良好的 Agent 设计里,模型可以主动发起二次尝试。这种能力可以通过在 prompt 中明确"发现结果不符时,允许修正计划重新尝试"来实现。我试过几次之后发现,这种"允许模型纠错"的设定,比任何微调模型都管用,而且几乎零成本。

5.4 建立失败链的回归测试集

修复完一个失败链,如果不把复现路径保存下来,下次改个代码可能又回归了。我在这次项目里特意维护了一个"失败链回归测试集",里面存的就是各种失败场景的追踪路径。每个用例包含四块:触发条件(比如模拟工具返回 5xx)、预期行为(编排层重试 3 次后成功)、预期最终状态(任务完成且模型回复合理)、预期错误日志(错误码为TOOL_HTTP_5XX,traceID 完整)。每次代码变更后,我都跑一遍这个测试集,确保之前修好的失败链没有被破坏。

测试集的构造方式其实不难。你可以从线上日志里随机抽取一些失败链路,然后把其中的参数泛化一下,作为回归用例。比如线上是查某支股票失败的,测试用例就改成查另一支股票,保持同样的错误触发逻辑。只要失败链机制是通用的,测试集就能稳定覆盖各种变体。

6. 经验总结:给 Agent 开发者的五个避坑建议

这次排查给我最深的教训就是:遇到 Agent 失败,先别急着换模型,先检查错误链路。为了帮大家少走弯路,我把这几年积累的几条避坑建议整理出来:

第一,错误码一定要结构化。粗错误码会害死排查效率,尽早把错误码拆细,并带上 retryable、cause、context 这些关键字段。哪怕前期工作量大一点,后面省下来的排查时间远超投入。

第二,编排层一定要有重试机制。很多 Agent 框架默认没有重试,或者只重试固定次数。实际业务里,瞬时故障远比你想的多,指数退避 + 最大次数限制是最基本的配置。

第三,prompt 里一定要写错误处理规则。不要指望模型"天生会处理错误",它需要一份清晰的手册。手册里写清楚每种错误怎么应对,模型就能给你惊喜;不写,模型就给你惊吓。

第四,排查时一定要用 traceID 串起整个失败链。没有链路追踪,全靠猜,猜中概率太低。哪怕你只是在小项目里,也可以在编排层手写一个简单的日志跟踪器,记录每个节点的输入输出和时间。

第五,修复后一定要做回归测试。失败链问题很容易因为改动某个无关参数而复发。维护一套自动化的失败场景测试集,把最容易出问题的几条路径固定下来,改代码前先跑一遍,心里才有底。

我个人在实际操作中还有一个特别深的体会:Agent 项目里,模型能力更像是"下限",而系统的容错设计才是"上限"。你把系统的错误处理做扎实了,哪怕用稍弱一点的模型,任务成功率都能维持在比较高的水平;反之,系统到处是坑,用再强的模型也是白搭。这也是为什么我一直强调,别再把 Agent 失败都算给模型了。排查一次失败链,往往比你换一个昂贵的新模型,带来的收益更大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询