尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Agent异常处理可选性:从try-catch到重试降级熔断的策略设计

Agent异常处理可选性:从try-catch到重试降级熔断的策略设计 Agent 异常处理之“可选性”别再让一次超时拖垮整个智能体一个在线上跑了三周的 Agent 应用突然在某天早上工具调用全部超时。你打开日志发现异常堆栈被吞掉了用户只收到一句“您的订单查询失败请稍后再试”。你以为是模型抽风查了半天才发现是下游库存服务的慢 SQL。为什么一个下游服务的故障会让整个 Agent 表现得像“智力退化”答案往往不是模型问题而是异常处理策略从一开始就写死了。很多人做 Agent 开发时会把异常处理简化为三件事捕获、重试、抛出。捕获是为了不让进程崩溃重试是期望下一次调用能成功抛出是把问题甩给上层。这在传统接口开发里勉强够用但在 Agent 场景里远远不够。因为 Agent 的一个任务往往要经过意图理解、工具选择、工具调用、结果解析、回复生成多个阶段每个阶段的故障特征不一样恢复成本不一样对用户体验的影响也不一样。用一个固定的 catch 块去处理所有异常本质上是在“赌”赌重试一定能成功赌降级返回一定不会被用户察觉。这篇文章要讲的是 Agent 异常处理的“可选性”。可选性不是说多写几个 if 分支而是从异常分类、策略选择、代码结构和编排层面让系统在同一类异常面前可以根据执行阶段、重试次数、上下文状态和业务要求灵活选择“重试”“降级”“快速失败”“熔断”“人工介入”等不同处理路径。我会结合 CompletableFuture 异步编程、Spring AOP 切面、Agent 编排层三层场景给出可运行的 Java 代码和工程建议。1. 什么是“可选异常处理”它和 try-catch 有什么区别先理清一个容易混淆的问题异常处理不是只有“语法层面的 try-catch”和“设计层面的策略选择”两种而是二者缺一不可。try-catch 解决的是“异常发生了怎么接住”可选异常处理解决的是“接住之后往哪个方向走”。可以这样理解try-catch 像是电路里的保险丝它只负责在电流过大的时候切断电路可选异常处理像是断路器加路由器的组合它不仅要切断故障链路还要决定故障发生后流量往哪里走——是走备用电路还是降低负载还是通知运维人员手动处理。传统 Java 开发里常见的错误写法是把所有业务异常用同一个 catch 块处理try { String result agentService.execute(userInput); return result; } catch (Exception e) { log.error(agent execute failed, e); return 系统繁忙请稍后再试; }这段代码的问题不在于它“捕获了异常”而在于它把“网络超时”“模型返回格式错误”“业务规则校验失败”“上下文超长”全部混为一谈。网络超时可以重试一次格式错误可能重新解析就能解决上下文超长需要裁剪后重试而业务校验失败重试一百次也没有意义。在 Agent 场景里异常处理真正需要的是一种“路由能力”同样的异常在重试次数未超过阈值时选择重试在重试次数已超过阈值时选择降级在降级也失败时选择快速失败并保留原始异常在连续失败达到熔断条件时选择暂时屏蔽该工具调用在涉及资金、权限、关键业务时选择停止自动化流程并转入人工确认。这就是“可选性”的核心含义不是把异常处理写成一个固定模板而是把它设计成一个可配置、可扩展、可观测的策略系统。2. Agent 场景的异常到底特殊在哪里要理解为什么 Agent 的异常处理需要“可选性”先要理解 Agent 场景的异常和普通后端接口异常有什么不同。传统的 REST 接口异常来源比较单一。数据库连接失败、Redis 超时、下游接口返回 500这些异常通常发生在一次同步调用链中处理方式也比较固定记录日志、返回错误码、由调用方决定是否重试。Agent 场景完全不同。Agent 的执行过程通常包含多个环节。以“订单助手”为例用户的请求进来之后Agent 需要先判断用户意图再决定调用哪一个工具可能同时调用订单查询、库存查询、物流查询三个工具最后把结果组织成自然语言回复。这个过程中任何一个环节出问题都会影响最终回复质量。Agent 异常的特殊性可以归纳为三点。第一异常具有“传播性”。一次工具调用的异常不仅会影响当前这一步还会顺着编排链路上传给后续步骤。更麻烦的是Agent 每执行一步都会生成中间结果这些中间结果可能会进入下一次模型调用的上下文。如果工具调用失败后没有清理上下文模型可能基于错误信息继续生成导致回复内容越来越离谱。第二异常具有“累积性”。在普通接口开发中一个请求失败了可以整体返回调用方看到的是明确失败。但在 Agent 场景中失败往往不是“全有或全无”。可能订单查询成功、库存查询失败、物流查询超时这三个结果如何组织成回复是一次失败就让整个任务失败还是保留成功部分、对失败部分做降级说明这需要策略选择。第三异常的恢复成本差异巨大。模型调用超时重试一次可能就成功了工具返回的数据格式不符合预期直接解析修正即可但是上下文超过模型限制可能需要重写整个 prompt 并重跑成本高得多如果是权限校验失败重试多少次都没有意义应该立刻换人工处理。所以Agent 异常处理不是简单的“技术问题”而是“架构设计问题”。一个成熟的 Agent 项目应该提前定义好异常分类体系再把处理策略配置化。3. 异步编程层的可选异常处理CompletableFuture 的三种处理姿势Agent 的编排往往伴随并行调用。Java 开发者在做 Agent 时最常用的并发工具是 CompletableFuture。但 CompletableFuture 的异常处理也很容易用错有人只用 exceptionally有人到处套 whenComplete有人直接用 get() 把异步调用重新变成阻塞调用。CompletableFuture 提供了三个容易混淆的方法exceptionally、whenComplete、handle。这三个方法正好对应可选异常处理的三种不同策略。exceptionally 只在异步任务出现异常时回调它的返回值会替换原始 Future 的结果。适合做“降级返回”。whenComplete 无论成功还是失败都会回调但它不能改变结果只能观察。适合做“日志记录和指标上报”。handle 无论成功还是失败都会回调而且可以返回一个新结果。适合做“根据异常情况重新编排后续链路”。看一个具体例子。假设 Agent 需要并行调用库存服务和价格服务// 文件路径src/main/java/com/example/agent/async/AgentAsyncDemo.java import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class AgentAsyncDemo { public static void main(String[] args) { ExecutorService executor Executors.newFixedThreadPool(4); // 模拟库存服务调用这里故意抛出异常 CompletableFutureString inventoryFuture CompletableFuture .supplyAsync(AgentAsyncDemo::callInventoryService, executor); // 可选策略1exceptionally 降级返回 CompletableFutureString withFallback inventoryFuture .exceptionally(ex - { System.out.println(库存服务调用失败执行降级策略); return inventory_unavailable; }); // 可选策略2handle 根据异常决定后续动作 CompletableFutureString finalResult withFallback.handle((value, ex) - { if (ex ! null) { System.out.println(handle 捕获到异常 ex.getMessage()); return fallback_message; } return value; }); System.out.println(最终结果 finalResult.join()); executor.shutdown(); } private static String callInventoryService() { throw new RuntimeException(inventory service timeout); } }运行这段代码控制台会输出库存服务调用失败执行降级策略 handle 捕获到异常java.lang.RuntimeException: inventory service timeout 最终结果inventory_unavailable这里的关键点是“降级策略”是选择出来的而不是写死的。在真正的 Agent 项目中exceptionally 的回调逻辑应该去查一下当前任务的配置看看这个工具允许不允许降级降级返回什么内容是否需要记录一条审计日志。也就是说异常处理的“可选性”在异步编程层就开始了。需要提醒的是不要在同一个 CompletableFuture 对象上无节制地叠加 exceptionally、whenComplete、handle。链条太长会让异常流动路径非常难排查。更合理的做法是每一条异步链路只保留一类处理逻辑把策略选择收敛到一个专用方法里。4. AOP 切面层实现可选异常处理把策略写进注解而不是代码异步编程层解决了“单次异步调用怎么选降级策略”的问题但 Agent 项目里还有大量同步工具方法。这些工具方法可能是内部服务也可能对接外部 API。如果每个方法都写一遍异常处理逻辑代码会非常冗余。解决这个问题可以借助 Spring AOP把异常处理策略从业务代码中剥离出来。实现思路是定义一个注解注解中声明这个方法的异常处理策略再定义一个切面在方法执行失败时读取注解里的策略决定是重试、降级还是快速失败。先定义一个异常处理策略注解// 文件路径src/main/java/com/example/agent/aop/AgentExceptionHandler.java package com.example.agent.aop; import java.lang.annotation.*; Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface AgentExceptionHandler { /** 是否允许重试 */ boolean retry() default false; /** 最大重试次数 */ int maxRetryTimes() default 2; /** 忽略的异常类型命中后直接抛出 */ Class? extends Throwable[] ignore() default {}; /** 降级返回的 message */ String fallbackMessage() default ; }然后定义一个切面在方法抛异常时读取注解依次执行重试、降级、抛出的可选逻辑// 文件路径src/main/java/com/example/agent/aop/AgentExceptionAspect.java package com.example.agent.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; Slf4j Aspect Component public class AgentExceptionAspect { Around(annotation(agentExceptionHandler)) public Object handleAgentException(ProceedingJoinPoint pjp, AgentExceptionHandler agentExceptionHandler) throws Throwable { int maxRetryTimes agentExceptionHandler.maxRetryTimes(); int currentRetry 0; while (true) { try { return pjp.proceed(); } catch (Throwable ex) { // 如果异常类型命中 ignore 列表直接抛出 for (Class? extends Throwable ignoreType : agentExceptionHandler.ignore()) { if (ignoreType.isAssignableFrom(ex.getClass())) { log.warn(命中忽略异常类型 {}直接抛出, ignoreType.getName()); throw ex; } } // 可选策略1重试 if (agentExceptionHandler.retry() currentRetry maxRetryTimes) { currentRetry; log.warn(方法调用失败开始第 {} 次重试异常{}, currentRetry, ex.getMessage()); continue; } // 可选策略2降级返回 if (!agentExceptionHandler.fallbackMessage().isEmpty()) { log.warn(方法调用失败且重试无效执行降级返回); return agentExceptionHandler.fallbackMessage(); } // 可选策略3快速失败 log.error(方法调用失败且未配置重试和降级快速失败, ex); throw ex; } } } }使用这个注解时业务方法本身不需要写任何 try-catch只需要声明自己的异常处理偏好// 文件路径src/main/java/com/example/agent/service/InventoryToolService.java package com.example.agent.service; import com.example.agent.aop.AgentExceptionHandler; import org.springframework.stereotype.Service; Service public class InventoryToolService { AgentExceptionHandler( retry true, maxRetryTimes 2, fallbackMessage 库存服务暂时不可用 ) public String queryStock(String skuId) { // 模拟调用下游库存服务 throw new RuntimeException(inventory service timeout); } }这里真正体现“可选性”的地方是注解上的retry、maxRetryTimes、ignore、fallbackMessage四个字段。同一个工具方法在开发环境可以配置成重试三次在线上紧急故障时通过配置中心动态调整成“不重试、直接降级”。异常处理策略从业务代码中解耦出来变成了可配置的元数据。需要注意一个经典坑Spring AOP 只对通过 Spring 容器代理调用的方法生效。如果同一个类内部调用this.queryStock()切面不会生效。正确写法是注入自己的代理对象或者把工具方法放到独立的 Service Bean 中由外部调用方触发。5. Agent 编排层的可选异常处理重试、降级、熔断、人工介入前两层解决的是“单点问题”。但在 Agent 编排链路里真正复杂的不是某一步失败怎么处理而是链路的不同阶段失败后怎么处理。举例来说一个 Agent 任务的执行流程可以简化为四个阶段意图理解阶段把用户输入转化为结构化指令。工具选择阶段决定调用哪个或哪几个工具。工具执行阶段真正调用外部服务。回复生成阶段把工具结果组织成自然语言。这四个阶段对异常处理的诉求完全不一样。意图理解阶段如果出现问题比如模型返回的 JSON 格式不合法可以重试一次也可以让用户重新表述。这种情况不适合降级返回因为降级会导致意图理解错误后续所有工具调用都失去意义。工具选择阶段如果出现问题比如没有找到匹配的工具不应该一味重试而应该返回“很抱歉我目前无法处理这个请求”并列出能够处理的能力范围。这是典型的 FAIL_FAST 场景。工具执行阶段最容易出现超时和下游故障适合用重试、降级、熔断组合策略。如果连续多次失败应该对这个工具做熔断让后续请求不再进入防止雪崩。回复生成阶段如果上下文超长可能需要裁剪历史消息后重试。如果模型连续返回空内容则应该告知用户“暂时无法生成有效回复”。为了把不同阶段的策略统一管理可以先定义一个异常分类枚举// 文件路径src/main/java/com/example/agent/policy/ErrorCategory.java package com.example.agent.policy; public enum ErrorCategory { /** 可重试超时、临时性网络故障 */ RETRYABLE, /** 可降级下游服务不可用但可以用备用数据源 */ FALLBACKABLE, /** 熔断连续失败次数超过阈值 */ CIRCUIT_BREAKER, /** 阻塞需要人工介入 */ BLOCKING, /** 快速失败重试没有意义 */ FAIL_FAST }再定义一个策略决策器输入异常和上下文输出下一步动作// 文件路径src/main/java/com/example/agent/policy/AgentExceptionPolicy.java package com.example.agent.policy; import lombok.extern.slf4j.Slf4j; Slf4j public class AgentExceptionPolicy { private final int maxRetryCount; public AgentExceptionPolicy(int maxRetryCount) { this.maxRetryCount maxRetryCount; } public AgentAction decide(Throwable ex, AgentContext context) { // 如果上下文已经重试过 n 次不再重试 if (context.getRetryCount() maxRetryCount) { log.warn(重试次数已达上限 {}, 当前异常: {}, maxRetryCount, ex.getMessage()); if (context.isAllowFallback()) { return AgentAction.FALLBACK; } return AgentAction.FAIL_FAST; } // 网络超时类异常优先重试 if (isTimeout(ex)) { return AgentAction.RETRY_WITH_BACKOFF; } // 校验类异常直接失败 if (ex instanceof IllegalArgumentException) { return AgentAction.FAIL_FAST; } // 下游服务不可用且上下文允许降级 if (isDownstreamUnavailable(ex) context.isAllowFallback()) { return AgentAction.FALLBACK; } // 连续失败达到阈值切断链路 if (context.getContinuousFailCount() 5) { return AgentAction.OPEN_CIRCUIT; } return AgentAction.FAIL_FAST; } private boolean isTimeout(Throwable ex) { return ex instanceof java.util.concurrent.TimeoutException || ex.getMessage() ! null ex.getMessage().contains(timeout); } private boolean isDownstreamUnavailable(Throwable ex) { return ex.getMessage() ! null ex.getMessage().contains(unavailable); } }对应的动作枚举// 文件路径src/main/java/com/example/agent/policy/AgentAction.java package com.example.agent.policy; public enum AgentAction { RETRY_WITH_BACKOFF, FALLBACK, FAIL_FAST, OPEN_CIRCUIT, MANUAL_INTERVENTION }这里能看出来可选性不只是“方法层面能不能重试”还包括“链路层面能不能降级”。Agent 编排层设计里上下文对象需要携带retryCount、continuousFailCount、allowFallback等状态字段。每执行一步这些状态都会更新。真正好的 Agent 异常处理应该是“每一步都知道自己还有哪些选择”。如果你在社区里看过一些 Agent 框架的异常报错比如搜索时经常见到的“the agent execution provider did not respond in time”这类超时异常本质上就是模型或工具执行 provider 没有在规定时间内返回。处理它时也不能无脑重试而应该先看这次超时发生在哪个阶段、重试次数是否还有额度、是否还有备用 provider 可以切换。6. 完整示例给 Agent 调用链加上可选异常处理前面三层讨论比较分散。这一节用一个相对完整的示例把异步编程、策略决策和 Agent 编排串起来。场景设计Agent 需要根据用户请求查询库存并生成回复。库存服务可能超时也可能返回不可用。我们希望在超时时先重试一次再失败则降级返回并保留原始异常上下文。先定义一个工具调用任务的返回封装// 文件路径src/main/java/com/example/agent/orchestration/ToolResult.java package com.example.agent.orchestration; import lombok.AllArgsConstructor; import lombok.Data; Data AllArgsConstructor public class ToolResultT { private T value; private Throwable exception; private boolean success; public static T ToolResultT success(T value) { return new ToolResult(value, null, true); } public static T ToolResultT failed(Throwable ex) { return new ToolResult(null, ex, false); } }再定义一个 Agent 编排类里面包含一个异步工具调用并应用策略决策// 文件路径src/main/java/com/example/agent/orchestration/AgentOrchestrator.java package com.example.agent.orchestration; import com.example.agent.policy.AgentAction; import com.example.agent.policy.AgentContext; import com.example.agent.policy.AgentExceptionPolicy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; Slf4j Component RequiredArgsConstructor public class AgentOrchestrator { private final AgentExceptionPolicy exceptionPolicy new AgentExceptionPolicy(2); private final ExecutorService executor Executors.newFixedThreadPool(4); public String execute(String userInput) { AgentContext context AgentContext.builder() .retryCount(0) .continuousFailCount(0) .allowFallback(true) .build(); return callInventoryWithRetry(context); } private String callInventoryWithRetry(AgentContext context) { CompletableFutureToolResultString future CompletableFuture .supplyAsync(() - { try { String stock doQueryStock(SKU-1001); return ToolResult.success(stock); } catch (Throwable ex) { return ToolResult.failed(ex); } }, executor) .orTimeout(3, TimeUnit.SECONDS); ToolResultString toolResult future.join(); if (toolResult.isSuccess()) { return 查询成功库存为 toolResult.getValue(); } Throwable ex toolResult.getException(); AgentAction action exceptionPolicy.decide(ex, context); switch (action) { case RETRY_WITH_BACKOFF: log.warn(库存服务超时准备重试当前次数{}, context.getRetryCount()); context.setRetryCount(context.getRetryCount() 1); context.setContinuousFailCount(context.getContinuousFailCount() 1); sleep(500); return callInventoryWithRetry(context); case FALLBACK: log.warn(库存服务不可用降级返回默认库存); return 查询成功降级库存暂时未知建议联系客服确认; case FAIL_FAST: log.error(库存服务快速失败原始异常, ex); return 查询失败请稍后重试; default: log.error(异常处理策略未定义{}默认快速失败, action); return 查询失败; } } private String doQueryStock(String skuId) { // 模拟真实调用前 3 次会抛出超时异常 if (System.currentTimeMillis() % 10 7) { throw new RuntimeException(inventory service timeout); } return 128; } private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }这个示例不算复杂但把关键思路讲清楚了工具调用本身不做异常处理而是把异常封装成ToolResult交给策略决策器选择下一步动作。这样做的最大好处是“动作可预测、决策可观测”。实际项目中还应该把AgentAction的决策结果记录到日志中比如决策上下文retryCount1, continuousFailCount2, allowFallbacktrue 决策结果RETRY_WITH_BACKOFF 决策原因inventory service timeout有了这些日志线上排查时才能回答“为什么这次失败没有降级”“为什么重试了两次还是失败”。7. 可选异常处理的可观测性设计可选异常处理能运转起来除了代码层面有策略还需要在运行时有“可观测性”。没有观测策略再多也只是黑盒。可观测性至少包括三层日志、指标、链路上下文。日志层要记录异常处理的“决策轨迹”。不是只记录异常堆栈还要记录决策前上下文、决策结果、决策依据。比如2397 [inventory-tool] WARN AgentExceptionPolicy - context[retryCount1, continuousFailCount2, allowFallbacktrue] decisionRETRY_WITH_BACKOFF causetimeout指标层要对关键动作计数。可选的异常处理动作最好都埋上指标包括重试次数、降级次数、快速失败次数、熔断断开次数、熔断恢复次数。这些指标在故障演练和容量评估时非常有用。链路上下文层要保证异常处理和调用链追踪打通。既然 Agent 是多阶段编排那么一次用户请求应该有一个全局的 traceId。每一步的异常处理决策都应该带上这个 traceId方便把“意图理解失败”和“工具调用超时”关联起来。举一个实际排查场景用户反馈“我早上询问订单状态Agent 回复了一堆无关内容”。如果日志里只有一句order service timeout根本判断不了问题。但如果有了决策轨迹你一眼就能看到调用订单服务失败 - 降级返回 - Agent 收到降级文本 - 模型把降级文本当成真实订单信息 - 生成无关回复。这说明降级策略本身没错错的是没有在降级结果中标记“不可信”属性模型无法区分真实数据和降级数据。8. 常见问题与排查方法可选异常处理在 Agent 项目里落地时遇到的问题往往不在策略本身而在策略之外。下面是一份高频问题清单。问题现象可能原因排查方式解决方案Agent 执行失败但日志里只有堆栈没有上下文信息异常处理时只记录了堆栈没记录 traceId 和 Agent 执行步骤查看日志是否有 traceId检查异常处理切面是否记录上下文统一日志规范决策日志中必须包含 traceId、工具名、执行阶段重试过多导致下游服务压力变大策略配置成所有异常都重试 3 次以上查看重试次数指标确认异常分类是否覆盖完整可重试异常限定为超时、临时网络错误业务异常禁止重试AOP 切面不生效方法通过 this 调用不走 Spring 代理增加断点查看调用栈中是否存在代理对象通过注入自身 Bean 或拆分 Service 触发代理CompletableFuture 内异常被吞掉在 supplyAsync 里直接 catch Exception 并返回 null检查工具方法返回是否为 null日志是否缺少异常记录用 ToolResult 或 exceptionally 显式处理禁止吞异常降级后 Agent 回复质量明显下降降级返回的文本被模型当成真实业务结果查看降级数据是否未标记来源降级文本增加标记头如[fallback]并告知模型此为不可信数据连续失败后下游恢复但 Agent 仍然失败缺少熔断恢复机制查看熔断器状态是否一直处于 OPEN配置半开状态定期放量探测下游是否恢复模型生成阶段上下文超长重试后仍然失败重试时没有裁剪历史消息查看请求模型前的 token 统计重试前压缩历史消息或删除无关工具结果这里我想单独强调 CompletableFuture 的“吞异常”问题。很多开发者想当然地认为在supplyAsync里 try-catch 后返回 null外部用join拿到 null 再判断就好。这在 Agent 场景里是非常危险的。因为 null 可能是真实业务结果也可能是失败产生的空值极易被后续模型调用误读。更合理的做法是使用ToolResult这样的封装让成功和失败在类型层面可见。9. 最佳实践与工程建议把“可选异常处理”落地到 Agent 工程中以下几条建议值得认真参考。第一先做异常分类再写处理代码。不要等到代码写了一半再补 try-catch。建议在项目初期就拉一张异常枚举表把可能出现的异常归类为可重试、可降级、快速失败、熔断、人工介入五类并明确每一类的触发条件和默认动作。这张表应该放进团队文档而不是只存在于某个开发者的编辑器里。第二所有异常处理策略都应该是可配置的。用注解、配置中心或者规则引擎把策略从代码中解耦。一个工具方法在测试环境允许重试三次上线后可以自动调整为一次。如果策略写死在代码里每次调整都要发版这个灵活性就没有了。第三超时是 Agent 项目里最需要认真对待的异常类型。Agent 调用模型、调用工具、调用外部 API每一个环节都应该设置超时时间。可以使用 CompletableFuture 的orTimeout也可以使用Timeout注解。重点不是超时后被捕获而是超时后继续走哪条策略路径。第四尽量不要全局吞异常。我们做可选异常处理不是为了隐藏故障而是为了在故障发生时仍然能给出可控的返回。但是真正的故障信息必须完整保留至少要在日志中保留原始异常类型和堆栈。如果只记录系统繁忙后面排查会非常痛苦。第五人工介入应该被视为一种可选的、合法的异常处理策略而不是最后的补救手段。在资金操作、权限变更、高价值订单等场景Agent 遇到不确定性时可以做“人工确认”的降级而不是强行猜测。这个动作也是异常处理可选项的一种体现。第六区分 Agent 的“程序异常”和“模型误判”。很多看起来像是异常处理的问题实际上是模型把降级内容当成了正常结果。对这种问题要从提示词和数据标记上解决。比如降级数据统一加[fallback]前缀并在系统提示词中明确要求模型不得把降级数据作为事实依据。第七关于工具链选择下面是一些方向性的建议具体版本要结合项目实际情况如果你在使用 Spring Boot可以考虑基于 Spring AOP 做统一异常切面如果你在编排多路异步任务尽量用CompletableFutureorTimeout 自定义线程池如果你在 Agent 框架层做策略控制可以借鉴开源 Agent 框架中的可插拔策略设计例如把异常策略注册成 Bean运行时动态替换。10. 总结与后续学习方向回到开头那个问题为什么一个下游服务的故障会让整个 Agent 表现得像“智力退化”因为很多 Agent 项目的异常处理只解决了“接住异常”这个最低问题没有解决“接下来怎么选”这个核心问题。可选异常处理的价值不在于每一个异常都有最优解而在于系统在面对异常时不再是“条件反射式”地重试或降级而是有意识地选择下一步动作。它真正降低的是 Agent 项目的稳定性成本一种设计模式避免让模型、工具、异步任务之间互相污染。如果你正在做 Agent 开发下一步可以按照这个顺序继续深入先把文中第一节的异常分类矩阵画出来再在异步编程层把 ToolResult 封装做起来然后引入 AOP 注解简化同步方法最后把策略决策器接入你的 Agent 编排链路。每完成一层Agent 的可控性都会明显提升一个台阶。更远一步可以研究 Agent 框架中更完整的错误处理与恢复机制比如工具调用重试策略、模型切换策略、RAG 检索失败的降级策略。也可以关注社区里 Agent 项目在异常可观测性上的实践比如 Hermes Agent、pi Agent 等开源项目它们往往会在可观测性层做更多设计值得借鉴。继续学习时多问自己一句话这个异常发生之后是必须终止还是可以选择继续
返回列表