news 2026/8/31 22:36:45

Agent异常处理指南:重试、降级与护栏校验的容错设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent异常处理指南:重试、降级与护栏校验的容错设计

很多开发者在第一次做 Agent 项目时,都会有这样一种错觉:只要把 Prompt 写得足够清楚、把工具函数接得足够完整,Agent 就能稳定工作。真正跑起来之后才发现,问题大多不在“脑子不够聪明”,而在“时好时坏”——模型偶尔超时、偶尔输出乱 JSON、偶尔调工具时参数错位、偶尔在某个分支上兜圈子。这个现象背后有一个非常关键的差异:传统程序的异常是确定的、可复现的,而 Agent 的异常是概率性的、依赖外部环境的。如果你沿用传统 try-catch 的思维来做 Agent 异常处理,很快就会发现自己陷入两难。

所以我先给出一个贯穿全文的判断:Agent 的异常处理,目标不是“让系统永不失败”,而是“让失败可控、可恢复、可观测”。判断一个 Agent 容错方案是否合格,标准只有一个——当异常发生时,系统是按照预设路径优雅降级,还是直接崩溃、卡死,甚至把错误结果当正常结果用。很多 Agent 项目从 Demo 走向生产环境时,最大的阻碍往往不是模型效果,而是异常处理做得不够扎实。

这篇文章会从三类最常见的异常处理方式展开:重试机制、降级与回退、护栏校验与人工确认。这三者不是层层替代关系,而是分别对应不同的失败类型。读完你会清楚,什么情况下该重试、什么情况下该降级、什么情况下该把问题交给人工,以及它们如何组合成一个完整的 Agent 容错体系。文章还会给出完整的 Python 示例代码,帮助你直接在自己的项目里落地。

补充一点:这篇文章面向正在做 AI Agent 项目的开发者,无论你用 LangChain、LlamaIndex 这类框架,还是自己封装模型调用,核心思想都适用。示例代码以 Python 为主,但重试、降级、校验的思路完全可以平移到 Java、TypeScript 等其他技术栈。接下来我们进入正题。

1. 为什么 Agent 的异常处理比普通编程更棘手

1.1 传统异常是“可枚举的”,Agent 异常是“概率性的”

传统程序中,try-catch 能生效的前提是:异常类型有限、异常发生条件可预期。网络异常、空指针、非法参数,这些都是编译器或运行时能明确告诉你的失败分支。你甚至在代码编译阶段就能判断哪些地方可能抛错,然后针对性地处理。Agent 程序不是这样。Agent 程序里最大的不确定来源是模型输出。即使你把 Prompt 写得再严格,模型也可能返回残缺 JSON,也可能胡编一个不存在的工具名,也可能对同一个问题给出质量完全不同的答案。这不是代码 bug,而是模型推理本身的概率分布。

这就导致了一个非常尴尬的局面:你很难穷举所有失败模式,因为模型输出的变化空间太大。比如你要求模型返回{"action": "query_order", "params": {"order_id": "123"}},它可能返回{"action": "query_order", "params": {"order_id": "123"}, "reasoning": "用户想查询订单"},也可能返回一大段解释文字后带上 JSON,甚至可能因为安全策略拦截返回一段道歉说明。这些情况在写代码时往往无法提前预料,只能设计一个通用的校验和修复机制去兜底。

1.2 Agent 的失败链路比普通接口长得多

一个普通接口从请求到响应可能只有一跳,一次 HTTP 请求、一次数据库查询,失败模式非常集中。一个 Agent 任务往往长得多:任务拆解、规划、调用工具、观察结果、再规划、最终回复。链路很长,任何一环出问题,都有可能被后续环节放大。

举一个具体的例子。假设 Agent 需要调用一个订单查询工具,工具返回了一个空列表。这里有可能是真的没有订单,也有可能是工具调用失败后返回了空对象,还有可能是参数传错了导致查询条件不匹配。如果是真人程序员,看到空列表会结合上下文判断到底哪个环节出了问题。但 Agent 很可能直接认为“查询无结果”,然后给用户输出一个错误的结论,比如“您没有订单”。这种“错误被当成正常输入继续传递”的现象,是 Agent 异常处理中最容易被忽视、也最危险的一种失败。

1.3 外部依赖的失败模式更加复杂

Agent 几乎必然依赖外部组件:大模型 API、工具接口、数据库、向量库。这些依赖各有各的失败模式:限流、超时、网络抖动、上下文超长、内容安全策略拦截,而且往往是间歇性的,复现难度很高。你测试的时候一切正常,上线后某个第三方服务突然慢了两秒,整个 Agent 的响应时间就超标了。如果用传统“出错了就报错”的方式处理,整个系统会频繁地暴露错误,用户体感极差。

更深一层的问题是,这些外部依赖往往是不可控的。你无法修改模型 API 的限流策略,也无法保证工具服务永远稳定。所以 Agent 的异常处理必须假设“外部组件随时可能出问题”,并且在这种假设下仍然让系统具备可用的运行路径。这也是为什么我们说,Agent 异常处理的本质是容错设计,而不仅仅是错误捕捉。

1.4 小结论:从“精确捕捉错误”转向“系统容错设计”

到这里,我们可以给出一个更精确的定义:Agent 异常处理不是 catch 住错误,而是在失败发生时,系统仍然能走通流程。这就要求你提前设计好重试时机、降级路径、输出校验和人工确认的接入点。本文后面三节,就是围绕这几个关键点展开的。

2. 先认识 Agent 的异常来源:知道它会怎么挂

2.1 四类常见异常来源

在动手写异常处理代码之前,先把 Agent 常见的失败类型梳理清楚。根据实践经验,Agent 项目里的异常大体可以分为四类:

异常类别典型表现具体举例
基础设施类请求超时、连接中断、限流模型 API 返回 429,网络波动导致连接 reset
模型输出类输出格式不符合预期、上下文超长、内容被安全策略拦截期望 JSON 却输出 JSON 加解释文字,达到 token 上限
工具调用类工具名不存在、参数类型错误、外部系统拒绝Agent 生成了不存在的函数名,或把字符串参数传给数字参数
编排逻辑类Agent 反复调用同一工具、多步推理结果矛盾Agent 陷入死循环,反复查询同一个接口

这四类异常的处理方式完全不同。基础设施类异常适合用重试来解决;模型输出类异常适合用护栏校验和修复来解决;工具调用类异常既可能是瞬时故障(需要重试),也可能是持续故障(需要降级或停止);编排逻辑类异常则要从架构层面设置限制,比如最大步数,避免死循环拖垮系统。

2.2 从“什么时候会失败”反推处理方式

简单来说,重试主要针对“瞬时故障”,比如超时、限流;降级主要针对“持续故障”,比如模型服务不可用、某个功能模块不稳定;护栏校验主要针对“输出不符合预期”和“错误结果被当正常使用”的情况;人工确认则更多用在“高风险操作”和“模型不确定该不该执行”的场景。

这四个手段有一个清晰的递进关系。重试是成本最低的第一道防线,它假设失败是暂时的,过一会儿就好了;降级是第二道防线,它假设失败会持续一段时间,需要换一条路走;护栏校验是第三道防线,它在拿到结果后做质量把关;人工确认是最后一道闸门,它把高风险决策交给人类来把握。理解了这条递进关系,后面几节的代码就很容易读懂。

2.3 异常处理的两个常见误区

第一个误区是过度依赖重试。很多人习惯用一个大 try-catch 包住模型调用,失败就循环重试,结果限流越刷越严重,token 成本飙升。之所以会这样,是因为他们没有区分“瞬时故障”和“持续故障”。一个参数错误、鉴权失败、上下文超长的请求,重试多少次都不会成功,反而会不断消耗算力和费用。

第二个误区是不设护栏。Agent 的返回结果完全没有校验,下游系统直接消费。一旦模型输出格式漂移,整个链路就崩了,而且崩得非常隐蔽。大家可能遇到过这种情况:模型 API 明明返回了 200,但解析后得到的字段全是空的,下游代码还以为查询不到数据。这种问题不加护栏是发现不了的,只有等用户反馈“我的订单怎么没了”才意识到出了大问题。所以,重试和护栏必须同时存在,缺一不可。

3. 第一种方式:重试机制(Retry)

3.1 重试解决什么问题

重试是最直接、成本最低的异常处理方式,核心适用场景是瞬时故障。所谓瞬时故障,就是故障持续时间短,可能只有几十毫秒到几秒,过一会儿再试就能成功。比如模型 API 短时超时、服务重启窗口、网络抖动、单次限流,这些都属于瞬时故障。遇到这类问题,最简单有效的办法就是等一会儿再试一次。

但这里要强调一个原则:重试不是万能的,它只对“暂时性失败”有效。如果一个请求因为参数错误而失败,不管你重试多少次,结果都一样;如果一个请求触发了鉴权失败,重试同样不会改变结果。所以,设计重试机制的第一步,是把可重试的异常和不可重试的异常区分开。

3.2 重试的关键不是“重试”,而是“重试策略”

很多项目里的重试是写死的:失败了就重试 3 次,每次间隔固定 1 秒。这种做法在真实场景里远远不够。一个合格的重试策略至少要回答四个问题。

第一个问题:重试多少次?重试次数太少,瞬时抖动还没过去;重试次数太多,会放大后端压力。第二个问题:等待多久?是固定间隔还是指数退避?如果大量请求同时失败同时重试,固定间隔会让所有重试请求在同一时刻集中打到后端,反而把偶发故障放大成限流风暴。第三个问题:哪些异常值得重试?只有可恢复异常才值得重试,参数错误、授权失败这类异常重试多少次都没有意义。第四个问题:重试有没有副作用?如果 Agent 调用的工具不是幂等的,比如“创建订单”“发送邮件”,重试可能造成重复下单、重复发送。这四个问题没想清楚,重试机制反而会引入新的问题。

3.3 手写一个带退避的重试函数

当你在一个不允许引入额外依赖的项目中,可以自己实现一个带指数退避和随机抖动的重试函数。代码逻辑不复杂,但已经包含了重试机制里最核心的几个要素。

# 文件路径:agent/retry.py import time import random from typing import Callable, Tuple, Type class AgentTransientError(Exception): """Agent 瞬时错误:适合重试""" pass def retry_with_backoff( func: Callable, max_retries: int = 3, base_delay: float = 1.0, max_delay: float = 10.0, retryable_exceptions: Tuple[Type[Exception], ...] = (AgentTransientError, TimeoutError), ): """带指数退避和随机抖动的重试装饰器。""" def wrapper(*args, **kwargs): attempt = 0 while True: try: return func(*args, **kwargs) except retryable_exceptions as e: attempt += 1 if attempt > max_retries: raise delay = min(base_delay * (2 ** (attempt - 1)), max_delay) delay = delay * (0.5 + random.random() / 2) # 抖动,避免重试风暴 print(f"第 {attempt} 次调用失败:{e},{delay:.2f} 秒后重试") time.sleep(delay) return wrapper @retry_with_backoff(max_retries=3) def call_llm(prompt: str) -> str: """模拟模型调用,前 2 次抛瞬时错误,第 3 次成功。""" if random.random() < 0.6: raise AgentTransientError("模型服务暂不可用") return "模型正常返回结果" # 运行验证 if __name__ == "__main__": print(call_llm("你好"))

代码里的退避策略是 1 秒、2 秒、4 秒这样翻倍增长,并加入了随机抖动。抖动这一步在大量请求并发重试时非常重要,它能把同步的重试请求打散,避免在同一秒内集中冲击后端服务。如果你不加抖动,指数退避在同一起点上的所有请求依然会“整齐划一”地在同一时刻重试。

3.4 使用成熟的重试库:tenacity

实际项目中,除非你想严格控制依赖数量,否则更推荐直接用成熟的重试库。Python 生态里tenacity是最常用的选择,它把重试策略抽象成了装饰器,可以让业务代码保持干净。

# 文件路径:agent/llm.py from tenacity import ( retry, stop_after_attempt, wait_exponential, retry_if_exception_type, ) @retry( stop=stop_after_attempt(4), wait=wait_exponential(multiplier=1, min=1, max=15), retry=retry_if_exception_type((AgentTransientError, TimeoutError)), ) def call_llm_with_retry(prompt: str) -> str: # 开发环境中替换为真实模型调用 return call_llm(prompt)

stop_after_attempt(4)表示最多尝试 4 次;wait_exponential表示指数退避等待,初始最小 1 秒,最大 15 秒;retry_if_exception_type表示只在指定异常类型上触发重试。使用这个库可以省去自己实现重试逻辑的时间,同时它的策略配置清晰,团队维护成本也更低。

3.5 什么异常适合重试,什么不适合

适合重试不适合重试
API 超时鉴权失败(401/403)
限流(429)参数错误(400)
服务重启窗口上下文超长且未优化
网络抖动工具不存在或权限不足

判断标准很简单:这个请求换一个时间点执行,是否有可能成功?如果可能性很低,就不值得重试。比如上下文超长,你重试再多次也还是会超长,正确的做法是截断上下文或切换到更大的上下文窗口,而不是盲目重试。

3.6 重试的边界:什么时候停止重试

重试有一个容易被忽略的边界问题:什么时候停止重试?我的建议是三层边界都要设。第一层是次数上限,比如最多重试 3 次;第二层是时间上限,比如单次模型调用的总耗时不超过 30 秒,超过则立即中断;第三层是任务级上限,比如整个 Agent 流程的总重试次数不能超过 5 次,避免多个环节各自重试叠加后导致整体超时。这三层边界需要在配置中心里统一管理,方便线上动态调整,而不是散落在代码里。

结论很简单:重试只适合解决瞬时故障。如果一个问题连续重试多次仍然失败,继续重试的意义会快速下降,此时应该转入降级流程。这也是下一节要讲的内容。

4. 第二种方式:降级与回退(Fallback)

4.1 降级解决什么问题

降级是针对持续性故障的方案。什么场景会触发持续性故障?模型服务不可用、某个参数组合反复失败、第三方接口升级导致不兼容、限流持续超过阈值。这些场景下,重试已经没有意义,你需要让流程“换个路走”,而不是继续撞同一堵墙。

降级的核心思想是:主链路失败时,用备选方案继续完成业务目标。它接受了一个前提——降级后的结果质量可能不如主链路,但总比系统直接不可用要好。在生产者与消费者模型中,这就是典型的“服务降级”;在 Agent 场景里,降级的对象变成了模型提供商、工具函数和功能模块。

4.2 降级的三种层次

第一层是模型降级。主模型不可用时,切换到备选模型。比如从大型模型切换到小型模型,或者从商业模型切换到开源模型。不同模型的推理能力、输出质量、成本都不一样,但都能完成大部分任务。第二层是功能降级。Agent 的某项能力不可用时,关闭它或替换为简化实现。比如语义检索失效时退化为关键词匹配,比如记忆增强失效时退化为纯 Prompt 回答。第三层是结果降级。所有自动方案都失败时,返回统一的兜底回复,或者转人工处理。这是降级的最后一道防线,保证用户至少能得到一个明确的反馈。

4.3 多级降级链代码示例

下面这段代码实现了一个典型的多级降级链。它按优先级依次尝试主模型、备选模型、规则引擎,如果所有方案都失败,最后抛出运行时异常。

# 文件路径:agent/fallback.py import logging logger = logging.getLogger(__name__) class AgentTransientError(Exception): """瞬时错误,适合重试或降级""" pass def call_gpt4(prompt: str) -> str: raise AgentTransientError("主模型不可用,模拟异常") def call_gpt4_mini(prompt: str) -> str: return "备选模型返回结果" def call_rule_engine(prompt: str) -> str: return "无法自动回答,请联系人工客服" def run_agent_with_fallback(prompt: str) -> tuple: """ 多级降级链: 依次尝试主模型、备选模型、规则引擎。 返回 (最终结果, 实际使用的提供商) """ providers = [ ("gpt-4o", call_gpt4), ("gpt-4o-mini", call_gpt4_mini), ("rule-engine", call_rule_engine), ] for name, handler in providers: try: result = handler(prompt) logger.info("provider=%s 调用成功", name) return result, name except Exception as e: logger.warning("provider=%s 调用失败,原因:%s", name, e) continue raise RuntimeError("所有降级方案均失败")

这段代码里有一个容易被忽略的细节:降级方案返回的结果,应该标明实际使用的提供商。原因很简单,降级后的结果质量通常低于主链路,如果调用方不知道这是降级结果,就可能把低质量结果当成正常答案来使用。比如用户问“帮我查一下退换货规则”,降级后被规则引擎回答了一个通用解释,用户可能以为得到了准确答案,实际上内容并不针对他的具体订单。所以,降级结果必须带标记。

4.4 降级的时机与代价

降级不是越早越好。过早降级会让主模型形同虚设,影响效果;过晚降级又会拖慢响应,用户等不起。比较稳妥的节奏是:先快速重试 1 到 2 次,再进入降级链。在降级链里也要设置整体耗时预算,比如总耗时不超过 20 秒,超过预算就直接走兜底回复。

这里还要提一个关键指标——降级率。所谓降级率,就是所有请求中触发降级的比例。如果一个系统长期处于高降级率状态,说明主链路本身已经不稳定,需要介入排查,而不是让降级机制默默承担所有压力。降级机制是为了兜底,不是为了掩盖故障。

4.5 功能降级的实际场景

举一个电商客服 Agent 的例子。它依赖订单查询接口、售后规则库、兜底知识库三个模块。如果订单查询接口持续故障,让 Agent 反复调用失败接口只会浪费时间。更合理的做法是:检测到订单接口故障后,跳过订单详情查询,直接向用户表达“订单查询暂时不可用,请稍后再试”,同时提供售后规则库的通用回答。

实现这种功能降级,要求开发者在编排 Agent 任务时想清楚“最小可用路径”。比如一个完整的客服流程原本是:获取用户身份、查询订单、分析售后诉求、生成回答。最小可用路径可能只是:获取用户身份、生成一个友好的提示。这个最小可用路径要在架构设计阶段就预留出来,而不是等故障发生时临时拼凑。

4.6 小结:降级解决的是“持续失败”

降级解决的是持续失败的问题。它的核心思想是不追求每次调用都用最强方案,而是保障整个任务在弱方案下也能完成。降级的副作用是质量下降,所以必须用日志、告警和标记来兜住质量感知。降级率应该被当作一个重要监控指标,持续跟踪。

5. 第三种方式:护栏校验与人工确认(Guardrails & HITL)

5.1 护栏解决问题的关键:错误结果被当成正常结果

如果说重试解决瞬时失败、降级解决持续失败,那么护栏解决的是另一类问题:Agent 没有报错,但返回的结果无法被下游解析,或者内容不安全。很多初学者容易忽略这类异常,因为程序运行过程中没有任何异常抛出,代码正常执行,但结果就是不对。

更危险的场景是,Agent 返回了一个看起来正常、实际上错误的结论,并且它还会被后续流程继续使用。举个例子,Agent 被要求从文本中提取用户意图,然后调用对应的业务接口。如果模型把“退款”误识别成“退货”,系统就会走错流程。这类异常没有堆栈,不会触发 try-catch,只能靠输出校验来拦截。这就是护栏机制存在的意义。

5.2 三类护栏

第一类是结构化校验。要求 Agent 的返回必须符合约定的 JSON Schema,字段类型、必填项、取值范围都要校验。第二类是语义合规校验。比如结果长度是否合理、是否包含禁止词、是否超出业务范围。第三类是业务审计校验。高风险操作在真正执行前,必须经过人工确认或权限校验。前两类针对的是模型输出的“格式”和“内容”,第三类针对的是系统执行的“风险”。

5.3 使用 Pydantic 做结构化校验

Agent 的返回内容校验,建议不要用零散的 if-else 判断,而是定义数据模型,让校验逻辑集中化。Pydantic 是 Python 生态里最常用的数据校验库,它可以非常简洁地定义 Agent 输出的结构。

# 文件路径:agent/schema.py from pydantic import BaseModel, Field, ValidationError class AgentResponse(BaseModel): action: str = Field(description="要执行的行动,如 query_order, refund, reply") params: dict = Field(description="行动参数") reasoning: str = Field(description="模型做出该决策的推理过程") confidence: float = Field(ge=0.0, le=1.0, description="置信度") def parse_agent_response(raw: str) -> AgentResponse: """ 解析模型返回内容。 注意:模型可能输出 JSON 之外的文本,这里只演示核心解析逻辑。 """ if not raw or not raw.strip(): raise ValidationError("模型返回为空") # 实际项目中建议做 JSON 提取和预处理 return AgentResponse.model_validate_json(raw)

通过 Pydantic 定义 Agent 返回的结构,一旦模型输出缺少字段、类型错误、数值超出范围,校验会立即失败并给出具体错误信息。这一步可以在异常扩散到下游之前拦截大部分格式问题。

5.4 校验失败后的修复策略

模型输出校验失败,不一定要直接判失败。更实用的做法是:把校验错误信息回传给模型,让模型自己修复。比如 JSON 缺少字段,就把错误信息拼进 Prompt 再让它重试一次。这比直接失败更高效,也符合 Agent“可对话、可修正”的特点。

# 文件路径:agent/guardrail.py from pydantic import ValidationError class AgentOutputValidationError(Exception): """Agent 输出多次校验失败""" pass def call_llm(prompt: str) -> str: # 真实项目中替换为模型调用 ... def call_llm_with_json_repair(prompt: str, max_repair_times: int = 1) -> AgentResponse: """ 调用模型并做结构化校验,失败时让模型自动修复一次。 """ raw = call_llm(prompt) for _ in range(max_repair_times): try: return parse_agent_response(raw) except ValidationError as e: print(f"校验失败,尝试修复:{e}") raw = call_llm( f"你上一次的返回不符合要求,请修正 JSON。" f"原始返回:{raw},错误信息:{e}" ) # 修复失败,走失败兜底 raise AgentOutputValidationError("模型输出多次校验失败")

这里要注意一个细节:修复策略里一定要把上一次的具体错误信息传给模型。很多项目里的修复失败,是因为修复 Prompt 只写了“你的输出格式不对”,却没有告诉模型到底哪里不对。模型不知道自己缺了哪个字段、哪个字段类型错误,自然不知道该修什么。

5.5 人工确认与 HITL

业务风险高的动作,必须设置人工确认环节。典型场景包括:发送付款、修改订单、删除数据、执行外部系统写操作。实现思路是:Agent 先产出“待执行计划”,系统拦截,发送给人工审核,审核通过后才真正执行。

# 文件路径:agent/hitl.py class HumanApprovalRequired(Exception): """需要人工确认的异常""" def __init__(self, task_id: str, plan: dict): self.task_id = task_id self.plan = plan super().__init__(f"任务 {task_id} 需要人工确认:{plan}") def approve_task(task_id: str) -> bool: """模拟人工确认接口。实际项目可以接企业微信、钉钉、邮件等审批渠道。""" print(f"请审核任务 {task_id},审核通过请回复 yes,拒绝请回复 no") # 实际项目可以通过消息服务自动推送 return input("审核结果 (yes/no): ").strip().lower() == "yes" def execute_with_human_approval(task_id: str, plan: dict): try: raise HumanApprovalRequired(task_id, plan) except HumanApprovalRequired as e: print(f"请审核任务 {e.task_id}:{e.plan}") if not approve_task(e.task_id): raise PermissionError("人工审核未通过,任务已拦截") print("人工确认通过,开始执行任务")

这段代码使用异常来传递“需要人工确认”的信号,实际项目中你可以把人工确认做成一个独立的状态机。但核心思想是一样的:高风险操作必须经过一个审批入口,而不能绕过。

5.6 人工确认的取舍

人工确认会带来延迟,所以不能对每个请求都启用。更合理的做法是分级处理:低风险操作自动执行,中风险操作自动执行但留审计日志,高风险操作强制人工确认。分级规则建议由业务方和研发共同制定,并且在配置中心里可动态调整。

比如,“查询订单状态”属于低风险,直接执行;“修改订单收货地址”属于中风险,执行后留日志;“发起退款”属于高风险,必须人工确认。这个分级可以在系统初始化时加载到内存里,也可以做成一个配置表,由运营人员维护。分级的核心是找到业务风险与自动化程度之间的平衡点。

5.7 小结:护栏解决的是“错误结果被正常使用”

护栏校验解决的是“错误结果被正常使用”的问题。它通过结构化校验、语义校验和人工确认,把 Agent 的自由发挥限制在一个可控范围内。护栏不是越多越好,过重的护栏会让 Agent 失去灵活性,关键是找到业务风险与自动化程度之间的平衡点。在实际项目中,建议先做语法校验,保证系统不被格式错误打崩,再逐步增加语义和审计护栏。

6. 三种方式如何组合成一个完整的容错框架

6.1 组合原则:按失败类型分流

前面三节讲的是三种独立手段,但真实项目里它们需要组合使用。当任务进入 Agent 流程后,推荐按以下顺序处理。

第一步,调用主模型;第二步,确认是否出现异常。如果是瞬时故障,走重试;如果重试后仍然失败,根据失败类型决定是否降级。第三步,拿到模型输出后,先做结构化校验;校验失败则尝试修复,修复失败则降级或返回兜底。第四步,如果 Agent 的决策涉及高风险动作,进入人工确认环节。第五步,整个过程打日志、出监控。

这套流程的核心是把异常按“失败类型”分流,而不是笼统地抛给一个 catch 块。瞬时故障交给重试,持续故障交给降级,格式问题交给护栏,风险问题交给人工。每个环节各司其职,才能组成完整的容错链路。

6.2 一个组合示例

下面是一个把三种方式串起来的完整示例。它先走重试,失败后走降级,拿到结果后做结构化校验,最后根据行动类型决定是否进入人工确认。

# 文件路径:agent/pipeline.py import logging from pydantic import ValidationError logger = logging.getLogger(__name__) # 高风险动作清单 REQUIRES_APPROVAL_ACTIONS = {"refund", "delete", "send_message", "modify_order"} def generate_task_id() -> str: import uuid return str(uuid.uuid4()) def run_agent(prompt: str) -> str: # 第一层:重试 try: raw = call_llm_with_retry(prompt) except AgentTransientError: # 第二层:降级 raw, provider = run_agent_with_fallback(prompt) logger.info("已降级到 provider=%s", provider) except Exception as e: logger.error("模型调用最终失败:%s", e) return "抱歉,当前服务繁忙,请稍后再试。" # 第三层:护栏校验 try: response = parse_agent_response(raw) except ValidationError: response = call_llm_with_json_repair(prompt) # 第四层:人工确认 if response.action in REQUIRES_APPROVAL_ACTIONS: task_id = generate_task_id() try: execute_with_human_approval(task_id, response.model_dump()) except PermissionError as e: logger.warning("任务 %s 被拦截:%s", task_id, e) return "该操作需要审批,当前未通过,已取消执行。" else: execute_action(response) return response.reasoning def execute_action(response: AgentResponse): # 根据 action 分发到对应业务处理函数 print(f"执行动作:{response.action},参数:{response.params}")

注意代码中的顺序:重试在前,降级在后,然后才是结构化校验和人工确认。这个顺序对应的是失败发现的时间线。模型调用阶段可能发生瞬时或持续故障,所以先处理重试和降级;输出结果拿到后,再校验它的格式和语义;校验通过后,再判断是否需要人工确认。

6.3 正确的日志与监控是容错的一部分

没有日志和监控的重试、降级是没有意义的。你要记录的信息包括:异常类型、发生位置、重试次数、最终是否成功、是否降级、降级到哪个提供商、耗时、token 消耗、是否触发人工确认。这些数据可以用来持续调优重试参数和降级阈值。

在多人协作的 Agent 项目里,建议为每个 Agent 任务生成一个 trace_id,贯穿重试、降级、校验、人工确认的全过程。每一行日志都带上 trace_id,遇到用户投诉时,可以根据 trace_id 快速拉出整条链路的执行情况。这一点在排查 Agent 问题时尤其重要,因为 Agent 链路长、分支多,没有统一的 trace_id,定位问题会非常痛苦。

6.4 容错的终极目标

容错框架里出现“错误”不再只是坏事,它同时是系统收集反馈、自我调整的机会。一个完整的容错框架应该具备四个能力:失败可发现(日志),失败可恢复(重试与降级),失败可拦截(护栏),失败可审计(人工确认与 trace)。四者缺一不可。如果你目前的 Agent 项目只做了重试,建议尽快补上降级和护栏;如果只做了护栏,也要回头想想模型调用失败时系统能不能自动恢复。

7. 常见问题与排查思路

在 Agent 项目里,异常处理相关的坑比较集中。下面整理了一些常见现象、可能原因、排查方式和解决方案,方便你在实际工作中快速对号入座。

| 问题

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 22:35:09

Rust实现RAG:为AI Agent打造本地知识库问答最小原型

这次我们来看 Rust AI Agent 系列的第 8 篇&#xff1a;RAG 简介。前几篇文章已经把 Agent 的动作闭环搭起来了&#xff1a;模型能理解任务、能调用工具、能处理结果。但很多实际场景里&#xff0c;Agent 面对的问题不是“不会调用工具”&#xff0c;而是“缺少领域知识”。比如…

作者头像 李华
网站建设 2026/8/31 22:34:28

基于TrustZone的LTDC安全显示配置:从原理到实践

最近在调一块带显示功能的工业HMI板卡&#xff0c;主控是带TrustZone的Cortex-A7/M4组合。项目进行到中期&#xff0c;客户的系统架构师突然提了个需求&#xff1a;屏幕上的安全告警画面“绝对不允许被非安全世界改掉一个字”。换句话说&#xff0c;显示控制器LTDC必须被配置为…

作者头像 李华
网站建设 2026/8/31 22:31:30

PW6606平芯微代理商,内置自动降级机制,目标电压不可用时回退低档

PW6606 PD快充和QC快充协议电压诱骗控制器介绍 摘要&#xff1a; PW6606 是一款高度集成的USB电源传输接收&#xff08;SINK&#xff09;端控制器芯片&#xff0c;支持PD快充和QC快充协议&#xff0c;能够从PD/QC适配器电源请求设定的电压。该芯片内置PD通讯模块和QC通讯模块&a…

作者头像 李华
网站建设 2026/8/31 22:31:20

浏览器投屏轻量实现:WebRTC与getDisplayMedia从原理到代码

很多人一说“投屏”&#xff0c;第一反应就是买硬件盒子、装视频会议软件、或者让接收端电脑提前安装一个专用客户端。但如果你只是想把当前屏幕、某个应用窗口或浏览器标签页&#xff0c;实时投到另一台电脑的浏览器里&#xff0c;这套方案其实太重了。浏览器本身就是最好的投…

作者头像 李华
网站建设 2026/8/31 22:30:20

快手工程A卷复盘:大厂后端笔试考点与备考策略

“工程A卷”这四个字&#xff0c;对我来说记忆太深了。2020年秋招那会儿&#xff0c;我投了快手后端开发岗&#xff0c;点开笔试链接看到试卷标题的瞬间&#xff0c;心跳直接加速。那套题不算特别难&#xff0c;但它的考察覆盖面、题目类型和埋坑方式&#xff0c;基本代表了当年…

作者头像 李华