接手 Agent 项目后我发现最让人头疼的不是 Agent 答错问题而是 Agent 在调用工具、执行任务、串联多个外部服务时动不动就抛异常。刚开始我的处理思路很朴素把所有异常统统 catch 住打印一条日志然后返回一个兜底文案。可上线后很快就被打脸了有的异常被吞掉后Agent 继续拿错误结果往下跑最后输出了一个特别离谱的回答有的异常其实可以重试一次就能成功结果被我直接当成失败处理白白浪费一次调用还有的异常属于外部服务不可用本来应该走备用模型通道却被统一当成了普通错误。后来反复排查和重构我才意识到一个问题Agent 的异常处理重点不在 try-catch 写了多少而在于异常处理是否具备“可选性”。也就是说当一个任务执行失败时系统能不能根据异常类型、严重程度、当前上下文选择不同的处理路径而不是所有异常都走同一条路。这篇文章就围绕“Agent 异常处理之可选性”展开结合我自己的项目实践和经验总结分享一套从基础设计到落地实现的完整思路。无论你是刚开始接触 Agent 开发还是已经在业务中接入 Agent 框架这篇文章应该都能给你一些参考。1. 为什么 Agent 异常处理会这么复杂先看传统服务中的异常处理。一个普通的 Java 接口调用一个 ServiceService 里查数据库、调外部 API。出错时我们通常就是 try-catch记日志返回错误码。够用了。可 Agent 不一样。Agent 的本质是一个大模型驱动的决策循环它可以自主决定调用哪些工具、按什么顺序调用、在什么条件下停止。传统的异常处理建立在“流程已知、步骤固定”的基础上而 Agent 的执行过程是不确定的。这就导致了一个很尴尬的局面你写好了完整的 try-catch但根本不知道异常会发生在哪一轮、哪个工具调用、哪条决策路径上。举个实际例子。假设我们用 Agent 做一个订单查询助手流程大概是Agent 理解用户问题判断需要调用订单查询工具。Agent 调用订单工具传入参数。工具返回结果Agent 根据结果生成回答。如果结果异常Agent 需要决定是重试、换一种查询方式还是直接告诉用户查不到。这个过程里每一步都可能出问题。参数格式不对、工具超时、工具返回了恶意异常、大模型上下文太长、第三方接口限流……如果异常处理没有可选性Agent 就会在错误路径上不断打转用户感知就是“这个助手怎么老答非所问”。1.1 传统异常处理和 Agent 异常处理的区别先看一组对比。维度传统异常处理Agent 异常处理执行流程固定流程异常位置可预判动态决策异常位置不可预判异常类型以业务异常、系统异常为主工具异常、模型异常、上下文异常、决策异常补救策略重试、事务回滚、降级重试、换工具、换模型、调整提示词、终止上下文依赖依赖当前调用栈依赖整个任务状态和历史记录目标保证系统稳定保证任务完成的可能性和输出质量从这个表可以看出来Agent 的异常处理更像是一个“策略选择问题”而不只是“代码健壮性问题”。1.2 什么是 Agent 异常处理的可选性我在项目中把“可选性”定义为三个层面第一层异常分类的可选性。不是所有异常都视为失败先判断异常属于哪一类可重试的、可降级的、可忽略的、必须终止的。第二层处理策略的可选性。同一类异常在不同上下文中有不同处理方式。比如超时异常在用户只是闲聊时直接提示超时即可但在用户明确要求查询订单时应自动重试一次。第三层降级路径的可选性。当主路径不可用时系统有哪些备选路径可以走。比如主模型超时是否可以用备选模型主工具不可用是否有备用工具这三层合起来才是完整的可选性设计。如果只是每个异常写一个 catch那不叫可选性那叫兜底。2. 环境准备与基础框架在进入实战之前先说清楚本文的环境和依赖。因为 Agent 开发还在快速演进中各组件版本变动较大我下面给出的是常用组合供参考。实际项目请以官方文档和你的项目现状为准。操作系统macOS / Linux / Windows 均可开发语言Java 17Agent 中间件开发常用构建工具Maven 3.8核心框架Spring Boot 3.x用于整合 Web 服务和配置管理Agent 执行框架LangChain4j 或 Spring AI二者都是 Java 生态常用 Agent 开发框架模型OpenAI 兼容接口本文以 OpenAI 兼容 API 为例JDK 工具CompletableFuture用于异步任务编排如果你用的是 Python 生态核心思路一样只是实现细节不同。本文代码以 Java 为主因为要用到 CompletableFuture 来演示异步任务异常的可选性处理这和热搜词里提到的 AI Agent 异步编程场景是吻合的。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.33.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version0.33.0/version /dependency /dependencies如果你的网络环境无法访问默认 Maven 仓库请配置阿里云等镜像源。3. 核心概念拆解从确定性异常到可选性异常处理3.1 两种不同的异常系统我在开发中习惯把 Agent 的异常分成两大类确定性异常和不确定性异常。确定性异常指的是无论执行多少次只要条件相同就会稳定复现的异常。比如参数校验失败、JSON 解析失败、配置缺失。这类异常的信息是明确的根因是单一的。处理方式是尽量在进入 Agent 之前就拦截不要让大模型去“猜”一个错误参数应该怎么办。不确定性异常则指依赖外部环境、执行时序、模型输出的异常。比如网络超时、模型返回格式漂移、第三方工具偶发失败。这类异常可能在这次运行失败下次运行就成功。处理方式不能是简单的 catch 后返回错误而应该进入策略选择流程。“可选性”在异常系统中的意义就是为不同类型的不确定性异常提供不同的处理路径。3.2 异常处理的“策略树”设计我在项目中设计了一个简单的策略树用来指导异常处理决策任务执行异常 ├── 可重试Transient │ ├── 重试次数 上限 → 退避重试 │ └── 重试次数 ≥ 上限 → 降级或终止 ├── 可降级Degradable │ ├── 有备用模型 → 切换备用模型 │ ├── 有备用工具 → 切换备用工具 │ └── 无备用资源 → 返回部分结果或终止 ├── 可忽略Ignorable │ ├── 不影响最终结果 → 记录日志并继续 │ └── 影响部分结果 → 标记缺陷并继续 └── 必须终止Fatal ├── 安全风险 → 立即终止 └── 数据不可恢复 → 立即终止这个树的核心思想是在处理异常之前先问三个问题——这个异常能重试吗有别的路可走吗影响任务核心目标吗3.3 异常分类器的设计要落实上面的策略树第一步是写一个异常分类器。分类器的作用是把一个异常对象映射到对应的处理策略上。先定义一个异常类型枚举package com.example.agent.exception; public enum AgentExceptionType { RETRYABLE, // 可重试 DEGRADABLE, // 可降级 IGNORABLE, // 可忽略 FATAL, // 必须终止 UNKNOWN // 未知类型按最保守策略处理 }再定义一个统一异常类package com.example.agent.exception; public class AgentExecutionException extends RuntimeException { private final AgentExceptionType type; private final String stepName; private final boolean retryable; public AgentExecutionException(String message, AgentExceptionType type, String stepName, boolean retryable) { super(message); this.type type; this.stepName stepName; this.retryable retryable; } public AgentExceptionType getType() { return type; } public String getStepName() { return stepName; } public boolean isRetryable() { return retryable; } }注意这里的retryable和type是两个不同维度。type表示异常的整体归类而retryable只表示“这一次失败是否值得再试一次”。一个异常可以同时是可重试的也是可降级的。3.4 可选性处理的核心接口有了异常分类接下来要定义处理策略接口。每个策略都负责回答同一个问题这个任务执行失败了我现在选哪条路继续走package com.example.agent.executor; import com.example.agent.exception.AgentExecutionException; public interface OptionalRecoveryStrategy { boolean supports(AgentExecutionException exception); RecoveryResult recover(RecoveryContext context); }再定义一个执行结果对象package com.example.agent.executor; public class RecoveryResult { private final boolean recovered; private final String description; private final Object alternativeResult; public RecoveryResult(boolean recovered, String description, Object alternativeResult) { this.recovered recovered; this.description description; this.alternativeResult alternativeResult; } public boolean isRecovered() { return recovered; } public String getDescription() { return description; } public Object getAlternativeResult() { return alternativeResult; } }看到这里你可能觉得有点抽象别急下面我会用一个真实的 Agent 任务执行场景把这些接口全部落地。4. 从 CompletableFuture 到 Agent 异步异常处理Agent 任务天然是异步的调用大模型需要等待网络返回调用多个工具可能并行执行一个步骤超时不能拖垮整个任务。因此Agent 的异常处理必然要和异步编程结合在一起。在 Java 生态中CompletableFuture 是处理异步任务最常用的工具之一。但很多人在使用 CompletableFuture 时对异常处理写得非常随意。先看一段反面示例// 错误示例异常被静默吞掉外层无法感知 CompletableFuture.supplyAsync(() - { return callTool(query_order, params); }).exceptionally(ex - { log.error(工具调用失败, ex); return 抱歉查询失败; });这段代码的问题在于exceptionally把异常拦截后返回了一个兜底字符串但调用方并不知道这个结果究竟是真实结果还是异常兜底结果。如果后续流程拿这个字符串去继续做判断就会出现“假成功”。更好的做法是异步任务返回一个包含状态和数据的包装对象4.1 定义异步执行结果包装类package com.example.agent.async; public class AsyncTaskResultT { private final boolean success; private final T data; private final Throwable error; private final String stepName; private AsyncTaskResult(boolean success, T data, Throwable error, String stepName) { this.success success; this.data data; this.error error; this.stepName stepName; } public static T AsyncTaskResultT success(T data, String stepName) { return new AsyncTaskResult(true, data, null, stepName); } public static T AsyncTaskResultT failure(Throwable error, String stepName) { return new AsyncTaskResult(false, null, error, stepName); } public boolean isSuccess() { return success; } public T getData() { return data; } public Throwable getError() { return error; } public String getStepName() { return stepName; } }4.2 使用 handle 而不是 exceptionallyCompletableFuture 的handle方法可以同时处理正常结果和异常非常适合这个场景。package com.example.agent.async; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; public class AsyncTaskExecutor { public T CompletableFutureAsyncTaskResultT executeTask(String stepName, SupplierT task) { return CompletableFuture.supplyAsync(() - { try { T result task.get(); return AsyncTaskResult.success(result, stepName); } catch (Throwable error) { return AsyncTaskResult.failure(error, stepName); } }); } }这个执行器的好处是异常不会再被吞掉而是保留在 AsyncTaskResult 中等待上层策略决策。4.3 异步任务的组合与异常可选性在 Agent 场景中一个任务往往由多个异步步骤组成。比如第一步调用模型第二步根据模型输出调用工具第三步再根据工具结果生成最终回答。此时可选性体现在每个步骤之间的衔接上。package com.example.agent.executor; import com.example.agent.async.AsyncTaskExecutor; import com.example.agent.async.AsyncTaskResult; import com.example.agent.exception.AgentExecutionException; import com.example.agent.exception.AgentExceptionType; import java.util.concurrent.CompletableFuture; public class AgentPipelineExecutor { private final AsyncTaskExecutor asyncTaskExecutor new AsyncTaskExecutor(); public CompletableFutureAsyncTaskResultString executeOrderQueryPipeline(String userId) { CompletableFutureAsyncTaskResultString step1 asyncTaskExecutor.executeTask(parse_user_intent, () - { // 模拟第一步解析用户意图 if (userId null) { throw new AgentExecutionException(用户ID为空, AgentExceptionType.FATAL, parse_user_intent, false); } return query_order; }); return step1.thenCompose(result - { if (!result.isSuccess()) { // 第一步失败不再继续 return CompletableFuture.completedFuture(result); } // 第二步根据意图调用订单服务 return asyncTaskExecutor.executeTask(call_order_service, () - { String intent result.getData(); if (!query_order.equals(intent)) { throw new AgentExecutionException(不支持的意图, AgentExceptionType.FATAL, call_order_service, false); } // 模拟调用外部订单服务 return 订单信息订单号20240501状态已发货; }); }); } }在这个例子中你看到的已经不是“异常被 catch 后统一返回”而是异常被包装、传递、最终由某个决策器决定怎么处理。4.4 异步编排里的超时异常处理Agent 调用大模型时最常见的异常就是超时。下面用 CompletableFuture 的实例演示超时异常的可选性处理。package com.example.agent.executor; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; public class AiModelExecutor { public CompletableFutureString callModelWithTimeout(String prompt, long timeoutSeconds) { CompletableFutureString future CompletableFuture.supplyAsync(() - { // 模拟调用大模型这里实际会替换为真实的 LLM 调用 return callRemoteModel(prompt); }); return future.orTimeout(timeoutSeconds, TimeUnit.SECONDS); } private String callRemoteModel(String prompt) { // 模拟耗时和异常 try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(模型调用线程被中断); } return 模型生成结果; } }在callModelWithTimeout中orTimeout会在指定时间内没有返回时抛出TimeoutException。上层拿到这个异常后可以选择重试也可以选择切换到备用模型。这正是异常处理“可选性”的体现同一类超时异常在不同场景下处理路径可以不同。5. 完整实战构建一个带异常可选性处理的 Agent 执行框架接下来我把上面所有的设计整合成一个完整的、可以运行的 Agent 执行框架示例。这个示例会包含异常分类器重试策略降级策略策略选择器一个模拟的 Agent 任务执行入口因为完整代码量较大我这里给出核心代码并说明每一部分的作用。5.1 项目结构├── pom.xml └── src/main/java/com/example/agent ├── AgentApplication.java ├── exception │ ├── AgentExceptionType.java │ └── AgentExecutionException.java ├── async │ ├── AsyncTaskResult.java │ └── AsyncTaskExecutor.java ├── recovery │ ├── RecoveryResult.java │ ├── RecoveryContext.java │ ├── OptionalRecoveryStrategy.java │ ├── RetryStrategy.java │ ├── DegradeStrategy.java │ └── FatalStrategy.java ├── executor │ ├── AgentPipelineExecutor.java │ └── OptionalRecoveryExecutor.java └── controller └── AgentDemoController.java5.2 恢复上下文package com.example.agent.recovery; import java.util.Map; public class RecoveryContext { private final String taskName; private final int retryCount; private final MapString, Object executionState; public RecoveryContext(String taskName, int retryCount, MapString, Object executionState) { this.taskName taskName; this.retryCount retryCount; this.executionState executionState; } public String getTaskName() { return taskName; } public int getRetryCount() { return retryCount; } public MapString, Object getExecutionState() { return executionState; } }5.3 重试策略package com.example.agent.recovery; import com.example.agent.exception.AgentExecutionException; import com.example.agent.exception.AgentExceptionType; import java.util.concurrent.TimeUnit; public class RetryStrategy implements OptionalRecoveryStrategy { private static final int MAX_RETRY_COUNT 3; Override public boolean supports(AgentExecutionException exception) { return exception.getType() AgentExceptionType.RETRYABLE || exception.isRetryable(); } Override public RecoveryResult recover(RecoveryContext context) { int retryCount context.getRetryCount(); if (retryCount MAX_RETRY_COUNT) { return new RecoveryResult(false, 重试次数已达上限, null); } // 模拟退避等待每次重试前等待 1 秒 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return new RecoveryResult(false, 重试等待被中断, null); } return new RecoveryResult(true, 准备进行第 (retryCount 1) 次重试, null); } }这里有一个关键点重试策略并不负责真正执行任务。它只负责回答“这次能不能重试、下一次重试前需要做什么准备”真正的重试执行由外部调用方完成。这样做的目的是保持策略单一职责。5.4 降级策略降级策略和重试策略的区别在于重试是“走原路再试一次”降级是“换一条路走”。package com.example.agent.recovery; import com.example.agent.exception.AgentExecutionException; import com.example.agent.exception.AgentExceptionType; public class DegradeStrategy implements OptionalRecoveryStrategy { Override public boolean supports(AgentExecutionException exception) { return exception.getType() AgentExceptionType.DEGRADABLE; } Override public RecoveryResult recover(RecoveryContext context) { // 示例降级逻辑从执行状态中读取备用模型配置 Object backupModel context.getExecutionState().get(backupModel); if (backupModel ! null) { return new RecoveryResult(true, 已切换备用模型: backupModel, backupModel); } return new RecoveryResult(false, 没有可用备用模型, null); } }5.5 终止策略终止策略适合 FATAL 类型的异常。注意终止策略的recover方法并不是“恢复”而是“有序终止”并尽可能保留现场信息。package com.example.agent.recovery; import com.example.agent.exception.AgentExecutionException; import com.example.agent.exception.AgentExceptionType; public class FatalStrategy implements OptionalRecoveryStrategy { Override public boolean supports(AgentExecutionException exception) { return exception.getType() AgentExceptionType.FATAL; } Override public RecoveryResult recover(RecoveryContext context) { // 记录终止原因兜底返回错误信息 return new RecoveryResult(false, 任务终止不允许重试或降级, 任务执行失败请检查输入参数或系统配置); } }5.6 策略选择器策略选择器是“可选性”的核心入口。它根据异常类型选择可用的策略链依次尝试恢复。package com.example.agent.executor; import com.example.agent.exception.AgentExecutionException; import com.example.agent.recovery.OptionalRecoveryStrategy; import com.example.agent.recovery.RecoveryContext; import com.example.agent.recovery.RecoveryResult; import java.util.List; import java.util.Map; public class OptionalRecoveryExecutor { private final ListOptionalRecoveryStrategy strategies; public OptionalRecoveryExecutor(ListOptionalRecoveryStrategy strategies) { this.strategies strategies; } public RecoveryResult handleExecutionException(AgentExecutionException exception, int retryCount, MapString, Object executionState) { RecoveryContext context new RecoveryContext(exception.getStepName(), retryCount, executionState); for (OptionalRecoveryStrategy strategy : strategies) { if (strategy.supports(exception)) { RecoveryResult result strategy.recover(context); // 只记录不可恢复的状态具体日志由上层统一处理 if (!result.isRecovered()) { System.out.println([恢复失败] 步骤: exception.getStepName() , 原因: result.getDescription()); } return result; } } return new RecoveryResult(false, 未找到匹配的恢复策略, null); } }策略选择器本身也是一个普通组件可以通过 Spring 注入策略列表也可以手工组装。5.7 Spring 配置与策略注入推荐用 Spring 的自动注入方式管理策略后续新增策略只需要实现接口并注册为 Bean不需要修改核心逻辑。package com.example.agent.config; import com.example.agent.recovery.DegradeStrategy; import com.example.agent.recovery.FatalStrategy; import com.example.agent.recovery.OptionalRecoveryStrategy; import com.example.agent.recovery.RetryStrategy; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; Configuration public class AgentRecoveryConfig { Bean public ListOptionalRecoveryStrategy recoveryStrategies() { return List.of( new RetryStrategy(), new DegradeStrategy(), new FatalStrategy() ); } }5.8 模拟 Agent 任务入口下面写一个模拟任务执行入口演示异常如何从底层传播到策略选择器。package com.example.agent.controller; import com.example.agent.exception.AgentExecutionException; import com.example.agent.exception.AgentExceptionType; import com.example.agent.executor.OptionalRecoveryExecutor; import com.example.agent.recovery.RecoveryResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; RestController public class AgentDemoController { private final OptionalRecoveryExecutor recoveryExecutor; public AgentDemoController(OptionalRecoveryExecutor recoveryExecutor) { this.recoveryExecutor recoveryExecutor; } GetMapping(/agent/order) public String queryOrder(RequestParam String userId) { MapString, Object executionState new HashMap(); executionState.put(backupModel, gpt-4-turbo); try { // 模拟 Agent 调用外部订单服务 return callOrderService(userId); } catch (AgentExecutionException e) { RecoveryResult result recoveryExecutor.handleExecutionException( e, 0, executionState); if (result.isRecovered()) { return 已通过可选策略恢复任务策略说明 result.getDescription(); } return 任务最终处理结果 result.getAlternativeResult(); } } private String callOrderService(String userId) { if (userId.startsWith(error)) { throw new AgentExecutionException(订单服务超时, AgentExceptionType.RETRYABLE, call_order_service, true); } return 订单查询成功用户 userId 的订单已发货; } }5.9 运行与验证启动 Spring Boot 应用后用不同参数访问接口来验证。正常情况curl http://localhost:8080/agent/order?userIdu1001 # 输出订单查询成功用户 u1001 的订单已发货异常但可重试curl http://localhost:8080/agent/order?userIderror1001 # 输出已通过可选策略恢复任务策略说明准备进行第 1 次重试注意因为示例中callOrderService是直接抛异常并没有真正的“重试执行”所以你看到的输出是策略层返回的说明。在真实项目中这里应该把重试回调放进RecoveryResult或由上层根据isRecovered结果重新执行任务。6. 常见问题与排查思路在实现 Agent 异常可选性处理的过程中很容易踩一些坑。下面是我整理的常见问题按项目中出现频率排序。问题现象常见原因解决思路异常被吞掉后Agent 输出假结果exceptionally 里直接返回兜底字符串不再抛出使用包装结果对象区分“成功”和“失败”重试无效果反复失败重试的是同一套参数问题出在参数本身重试前修改提示词或参数做差异化重试同一个异常在不同流程中处理不一致异常类型判断散落在各处引入统一异常分类器和策略选择器备用模型切换后仍然失败没有检查备用模型的服务状态增加健康检查降级前确认备用资源可用线程池满了导致任务排队CompletableFuture 使用公共 ForkJoinPool自定义线程池并监控队列长度Agent 执行 provider 没有及时响应模型调用时间过长未配置超时为所有异步调用配置orTimeout6.1 典型问题异常分类不完整不少项目会在异常分类时只处理 Retryable 和 Fatal忽略了 Degradable 和 Ignorable。结果就是明明可以换个备用模型继续跑却直接终止了任务明明某个异常不影响最终答案却中断了整个流程。我的建议是在项目初期就把异常类型枚举设计完整宁可先用 UNKNOWN 兜底也不要漏掉关键分类。后续遇到新异常类型只需要补充策略不需要改造核心流程。6.2 典型问题重试导致重复执行副作用Agent 调用工具时如果工具本身不是幂等的比如创建订单、发送短信重试可能导致重复操作。这在异常处理的可选性里是一个非常重要的边界问题。解决思路在进入重试策略前先判断操作是否幂等。对非幂等操作重试前必须检查上一次操作是否真正失败如查订单状态、查数据库。如果无法确认宁可终止任务也不要盲目重试。我在项目中专门给所有 Agent 可调用工具增加了一个idempotent标记只有标记为 true 的工具才允许重试。7. 最佳实践与工程建议7.1 给异常处理加“上下文”传统的 try-catch 里你拿到的是一个异常栈。但在 Agent 场景中只有异常栈是不够的。你还需要知道当前 Agent 任务的目标是什么。当前执行到了第几步。前面几步的结果是什么。模型当前的对话上下文是什么。我建议在异常对象或日志上下文中携带这些信息。具体做法可以是在AgentExecutionException里增加一个context字段或者在日志系统中直接打印全链路 traceId。package com.example.agent.exception; import java.util.Map; public class AgentExecutionException extends RuntimeException { private final AgentExceptionType type; private final String stepName; private final boolean retryable; private final MapString, Object context; public AgentExecutionException(String message, AgentExceptionType type, String stepName, boolean retryable, MapString, Object context) { super(message); this.type type; this.stepName stepName; this.retryable retryable; this.context context; } }这样在排查问题时可以快速还原异常发生时的完整现场。7.2 异常处理要做到可观测异常处理策略本身也需要被监控。我给项目加了一个简单的统计组件每次触发重试、降级、终止时都会记录一条结构化日志后续可以接入 Prometheus 或 Grafana。推荐的日志格式agent_recovery_event{taskquery_order, stepcall_order_service, strategyretry, retry_count1, recoveredtrue}通过监控这些事件你可以发现哪些步骤经常触发重试、哪些策略经常失效、哪个外部服务最不稳定从而做针对性的优化。7.3 尽量使用自定义异常而不是裸抛 RuntimeException在 Agent 框架中如果你到处抛RuntimeException异常分类器根本没办法区分这是超时、限流还是参数错误。自定义异常的成本很低收益却很大。异常类型本身就是一种“可选性”的元数据分类越多可选路径就越精确。7.4 异常消息不要直接暴露给用户Agent 抛出的原始异常信息可能包含调用栈、内部 IP、数据库信息等敏感内容。在返回给用户前必须做脱敏处理。特别是在降级策略的兜底文案中只应返回“任务执行失败请稍后重试”这种安全信息内部细节只在日志中保留。7.5 做最小权限和合法的系统变更如果 Agent 项目涉及文件操作、数据库写入、外部系统调用务必遵循最小权限原则。异常处理中的“降级”也不代表可以绕过权限限制。比如主数据库不可用时切到备用库备用库的账号权限要提前配置好并且只能在授权范围内读写。8. 总结Agent 异常处理的可选性核心思路可以概括为一句话不要把所有异常都当成人生的终局要为异常准备多条可选的路径。在本文中我们围绕这个话题做了几件事分析了传统异常处理与 Agent 异常处理的区别。提出了异常分类的三层可选性模型。用 Java 代码实现了一个包含异常分类器、重试策略、降级策略、终止策略的可选性处理框架。结合 CompletableFuture 讨论了异步编排中异常的可选性处理。给出了项目中常见的坑和最佳实践。下一步你可以继续深入的方向包括在真实 Agent 框架LangChain4j、Spring AI、LangGraph 等中集成本文的策略选择器。将异常可选性处理与 LLM 的提示词工程结合让 Agent 在遇到异常时自己决定是否重试。引入更多的恢复策略比如上下文裁剪、模型缓存、降级为检索式问答等。如果你正在做 Agent 项目建议先从异常分类器入手把所有可能出现的异常先列出来再给每个异常类型设计一条可选路径。你会发现Agent 的稳定性不是靠“尽量不报错”换来的而是靠“报错之后还有路可走”撑起来的。