
这次我们来看一个 Agent 开发中容易被忽略、但直接影响稳定性的设计点异常处理的可选性。Agent 跑起来之后失败几乎是必然的。模型接口超时、工具调用报错、执行提供者无响应、批量任务中途崩溃这些都是常态。很多团队在处理异常时喜欢写死一种策略要么一律重试要么直接抛异常。结果就是代码写得很满但系统仍然不稳定。所谓 Agent 异常处理的可选性简单说就是同一个异常系统允许按场景选择不同的处理方式。重试、忽略、降级、终止、切换备用通道都可以做成可配置的而不是让开发者在一个 try-catch 里把所有逻辑写死。这篇文章不绑定某个特定 Agent 框架的私有用例而是从工程模式角度把 Agent 异常处理的可选性拆开讲包括常见策略、Java 实现方式、超时场景处理、配置化设计以及批量任务里怎么复用。核心代码用 Java 和 CompletableFuture 演示但思路不限于 Java。1. Agent 异常处理可选性核心能力速览能力项说明主题类型Agent 开发中的异常处理设计模式核心概念同一个异常可配置多种处理策略按场景运行时选择常见策略重试、忽略、降级、终止、切换备用执行通道典型触发场景LLM/模型 API 超时、工具调用报错、执行提供者无响应、批量任务部分失败开发语言示例Java基于 CompletableFuture 和自定义策略接口环境要求JDK 9建议 JDK 17无额外框架依赖是否支持批量任务可以异常策略可复用到批量任务队列是否支持接口 API可嵌入各类 API 服务文章给出通用 HTTP 接口调用思路适合读者Agent 开发者、后端工程师、AI 应用集成者可观测性收益策略可记录、可监控、可根据失败类型动态调整需要强调的是这不是某个开源项目的一键部署教程而是一套可以直接落到工程代码里的设计方法。你可以在自己的 Agent 项目里逐步引入。2. 为什么 Agent 异常处理需要“可选性”很多 Agent 项目的代码里异常处理是“线性”的try 住一个 Agent 执行方法catch 到异常后要么打印日志要么统一重试三次。这种写法在场景简单时没问题但 Agent 的真实运行环境比普通接口复杂得多。先看几个具体场景模型调用返回 429限流此时重试可能有用但如果连续重试会加重限流。工具调用返回业务校验错误比如输入参数不合法此时重试没有任何意义应该忽略或直接告诉上游。某个执行提供者长期无响应此时继续等待会拖垮整体任务需要快速降级或切换到备用执行通道。批量任务里有 100 个任务其中 5 个失败如果所有失败都终止那 95 个成功结果也丢了。异步任务中线程被中断此时重试往往是错误选择应该恢复中断标记并快速退出。如果异常处理策略只能写死一种上面这些场景就无法同时兼顾。这也是“可选性”的价值不是“要不要处理异常”而是“如何处理这次异常应该由配置、任务类型、异常类型和当前上下文共同决定”。从工程角度看可选性意味着三层设计策略可插拔重试、忽略、降级都是一等公民可以自由组合。配置可修改不需要改代码就能调整某个 Agent 任务的异常行为。运行时可选择同一个异常类型在不同任务里走不同处理分支。把这三层做出来Agent 系统的稳定性会明显提升。3. Agent 异常处理可选性的常见策略在设计异常处理器之前先确定可选的处理策略有哪些。下面是我建议的通用策略集合。3.1 重试策略Retry适合临时性错误例如网络抖动、超时、限流。实现时要注意三个参数最大重试次数。重试间隔最好带退避因子。重试条件不是所有异常都适合重试。核心代码逻辑public class RetryHandler implements AgentExceptionHandler { private final int maxRetries; private final Duration delay; public RetryHandler(int maxRetries, Duration delay) { this.maxRetries maxRetries; this.delay delay; } Override public boolean supports(Throwable t) { return t instanceof java.net.SocketTimeoutException || t instanceof java.io.IOException; } Override public AgentOutcome handle(AgentContext ctx, Throwable t) { int retryCount ctx.getRetryCount(); if (retryCount maxRetries) { return AgentOutcome.retryExhausted(t); } try { Thread.sleep(delay.toMillis()); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return AgentOutcome.failed(ie); } return AgentOutcome.retry(); } }注意重试不是越多越好。重试次数过多会占用线程池资源还可能让下游服务进入雪崩。3.2 忽略策略Ignore适合业务校验失败、重复消息、已知的非法参数等。这类异常无论重试多少次都会失败直接忽略比反复尝试更合理。public class IgnoreHandler implements AgentExceptionHandler { Override public boolean supports(Throwable t) { return t instanceof IllegalArgumentException || t instanceof IllegalStateException; } Override public AgentOutcome handle(AgentContext ctx, Throwable t) { return AgentOutcome.ignored(t); } }忽略不代表不记录日志而是不中断 Agent 链路返回一个空结果或默认结果。3.3 降级策略Fallback / Degrade当模型接口不可用、执行提供者无响应时使用本地缓存、规则引擎或备用模型返回结果。这是 Agent 系统里非常重要的可选处理方式。public class FallbackHandler implements AgentExceptionHandler { private final SupplierAgentOutcome fallbackSupplier; public FallbackHandler(SupplierAgentOutcome fallbackSupplier) { this.fallbackSupplier fallbackSupplier; } Override public boolean supports(Throwable t) { return t instanceof ProviderNotRespondingException || t instanceof TimeoutException; } Override public AgentOutcome handle(AgentContext ctx, Throwable t) { return fallbackSupplier.get(); } }降级策略要保证兜底结果同样可观测否则用户会拿到一个看起来正常但实际是空壳的结果。3.4 失败快速终止策略FailFast适合致命错误例如权限异常、配置加载失败、任务中断。这类异常应当立即终止任务并向上抛出。public class FailFastHandler implements AgentExceptionHandler { Override public boolean supports(Throwable t) { return t instanceof InterruptedException || t instanceof SecurityException; } Override public AgentOutcome handle(AgentContext ctx, Throwable t) { if (t instanceof InterruptedException) { Thread.currentThread().interrupt(); } return AgentOutcome.failed(t); } }终止策略不是“不管”而是快速失败、快速释放资源。3.5 切换备用执行通道有些 Agent 框架支持多个执行提供者Provider一个 provider 长时间无响应时可以自动切换到备用 provider。从异常处理可选性的角度看切换通道是“重试”的升级版不再等待同一个服务恢复而是寻找另一个可用的执行来源。public class SwitchProviderHandler implements AgentExceptionHandler { private final ListAgentExecutor executors; private final AtomicInteger index new AtomicInteger(0); public SwitchProviderHandler(ListAgentExecutor executors) { this.executors executors; } Override public boolean supports(Throwable t) { return t instanceof ProviderNotRespondingException; } Override public AgentOutcome handle(AgentContext ctx, Throwable t) { int next (index.getAndIncrement() 1) % executors.size(); AgentExecutor executor executors.get(next); return executor.execute(ctx.getTask()); } }这个策略需要额外关注备用通道是否具备相同的数据权限切换后上下文是否仍然连续。4. 环境准备与最小工程结构下面用 Java 做一套最小可运行示例。不需要 Spring Boot不需要额外依赖只需要 JDK 17 和一个构建工具。4.1 环境检查java -version如果输出中包含 17 或更高版本即可直接使用。如果你还在用 JDK 8需要把 CompletableFuture 的异常处理改成回调方式但设计思路不变。4.2 项目目录建议按下面结构组织agent-exception-demo/ ├── src/main/java/ │ └── com/example/agent/ │ ├── AgentExecutor.java │ ├── AgentOutcome.java │ ├── AgentContext.java │ ├── AgentExceptionHandler.java │ ├── RetryHandler.java │ ├── IgnoreHandler.java │ ├── FallbackHandler.java │ └── FailFastHandler.java └── src/main/resources/ └── application.yml这个结构不绑定任何框架后续接 Spring、Micronaut 或纯 Java 都方便。5. 基于 CompletableFuture 的可选异常处理实现Agent 执行通常是异步的CompletableFuture 提供了多个异常处理入口。这里重点看三个exceptionally、handle、whenComplete。先定义一个策略接口public interface AgentExceptionHandler { boolean supports(Throwable throwable); AgentOutcome handle(AgentContext context, Throwable throwable); }再定义注册中心用于运行时按异常类型选择处理器public class ExceptionHandlerRegistry { private final ListAgentExceptionHandler handlers new ArrayList(); public void register(AgentExceptionHandler handler) { handlers.add(handler); } public AgentExceptionHandler find(Throwable t) { return handlers.stream() .filter(h - h.supports(t)) .findFirst() .orElse(null); } }然后模拟一个 Agent 执行器public class AgentExecutor { private final ExceptionHandlerRegistry registry; private final ExecutorService executorService Executors.newFixedThreadPool(4); public AgentExecutor(ExceptionHandlerRegistry registry) { this.registry registry; } public CompletableFutureAgentOutcome execute(AgentContext ctx) { return CompletableFuture.supplyAsync(() - { if (ctx.getTask().startsWith(timeout)) { throw new java.util.concurrent.TimeoutException(provider no response); } if (ctx.getTask().startsWith(invalid)) { throw new IllegalArgumentException(task invalid); } return AgentOutcome.success(task result); }, executorService) .exceptionally(t - { AgentExceptionHandler handler registry.find(t); if (handler ! null) { return handler.handle(ctx, t); } return AgentOutcome.failed(t); }); } }这里的关键点在于异常处理不是固定在 execute 方法里的某个 catch 块而是通过registry.find(t)在运行时选择一个处理器。这个“选择”就是可选性的核心体现。接下来用实际调用验证public class Demo { public static void main(String[] args) throws Exception { ExceptionHandlerRegistry registry new ExceptionHandlerRegistry(); registry.register(new RetryHandler(2, Duration.ofMillis(200))); registry.register(new IgnoreHandler()); registry.register(new FallbackHandler(() - AgentOutcome.success(fallback result))); registry.register(new FailFastHandler()); AgentExecutor executor new AgentExecutor(registry); AgentContext timeoutCtx new AgentContext(timeout-task); AgentContext invalidCtx new AgentContext(invalid-task); System.out.println(executor.execute(timeoutCtx).get()); System.out.println(executor.execute(invalidCtx).get()); System.exit(0); } }运行到 timeout-task 时匹配到重试处理器理论上有重试动作运行到 invalid-task 时匹配到忽略处理器返回一个 ignored 状态但不会中断链路。实际运行时可以把处理器返回的AgentOutcome打到日志里观察每次异常被哪个策略接管。6. Agent 执行提供者超时的可选处理方案“the agent execution provider did not respond in time”这种错误在真实 Agent 项目里非常常见。很多框架默认会直接抛超时异常但更好的做法是把它纳入可选异常处理。超时处理的难点在于等待时间长了会卡住线程等待时间短了又可能误杀正常任务。通常需要在两个地方做超时控制。6.1 使用 orTimeout 控制异步任务超时public CompletableFutureAgentOutcome executeWithTimeout(AgentContext ctx) { return execute(ctx) .orTimeout(30, TimeUnit.SECONDS) .exceptionally(t - { AgentExceptionHandler handler registry.find(t); if (handler ! null) { return handler.handle(ctx, t); } return AgentOutcome.timeout(t); }); }orTimeout触发后异常类型是java.util.concurrent.TimeoutException。我们可以让它走重试也可以走降级取决于配置。6.2 自定义 ProviderNotRespondingException很多框架会把“provider did not respond”包装成自定义异常。为了统一可选性建议在适配层做一次转换public class ProviderNotRespondingException extends RuntimeException { public ProviderNotRespondingException(String message, Throwable cause) { super(message, cause); } }然后在 Agent 执行入口处做转换try { return provider.execute(task); } catch (TimeoutException e) { throw new ProviderNotRespondingException(provider not responding in time, e); }这样异常类型明确注册中心就能精准选中降级或切换通道策略。6.3 超时后的可选分支超时之后至少要允许三种选择重试同一条执行链路但限制重试次数。降级到本地缓存或规则引擎。切换到备用执行提供者。这三种选择都可以通过注册中心配置而不是修改业务代码。7. 配置化、批量任务与测试验证7.1 把策略做成配置项为了让异常处理可选性真正落地建议将“哪个异常走哪个策略”放到配置文件中。下面是一个 YAML 配置示例agent: exception: default-strategy: failfast strategies: - type: retry exceptions: - java.net.SocketTimeoutException - java.util.concurrent.TimeoutException max-retries: 3 backoff-millis: 500 - type: ignore exceptions: - java.lang.IllegalArgumentException - type: fallback exceptions: - com.example.ProviderNotRespondingException fallback: 当前服务繁忙请稍后再试 - type: failfast exceptions: - java.lang.InterruptedException在启动时读取这些配置动态构建ExceptionHandlerRegistry。这样运维同学不需要改代码就能调整某个 Agent 任务的异常处理方式。如果使用 Spring Boot可以写一个简单的ConfigurationProperties类来绑定配置但这不是必须的。7.2 批量任务中的异常隔离批量任务是 Agent 异常处理可选性的重要使用场景。假设你要处理 100 个文本任务每个任务都会调用模型。如果一个任务失败就抛异常整个批次都会崩溃。批量任务的最小可用代码public ListAgentOutcome processBatch(ListAgentContext tasks, AgentExecutor executor) { ListCompletableFutureAgentOutcome futures tasks.stream() .map(executor::executeWithTimeout) .collect(Collectors.toList()); ListAgentOutcome results futures.stream() .map(future - { try { return future.get(); } catch (Exception e) { return AgentOutcome.failed(e); } }) .collect(Collectors.toList()); return results; }这个实现里每个任务都有独立的异常处理策略。失败任务不会影响其他任务。批量任务结束后可以统一分析AgentOutcome里的状态分布。7.3 功能测试步骤建议按下面顺序验证先跑一个会抛IllegalArgumentException的任务确认忽略策略生效。再跑一个会超时的任务确认重试或降级策略生效。把超时时间改小确认orTimeout能正确触发。关闭所有策略确认默认的 failfast 生效。批量提交 20 个任务其中 3 个故意失败确认失败任务不影响其他任务。在日志中检查每个异常的类型、匹配到的处理器、处理结果。7.4 性能观察方法异常处理本身不会占用大量显存或内存但要注意两点重试策略会占用线程池线程导致任务吞吐下降。降级策略如果访问外部缓存需要额外评估缓存超时时间。批量任务中大量异常被记录时日志 I/O 会成为瓶颈。观察指标主要是线程池活跃线程数、任务完成率、平均耗时、异常分布。如果重试次数过多优先调整策略配置而不是增加线程池大小。8. Agent 异常处理可选性常见问题排查问题现象可能原因排查方式解决方案异常没有进入任何处理器注册中心没有匹配的处理器打印异常类型检查 handlers 支持列表添加对应的 handler 或调整 supports 逻辑重试次数过多导致任务卡死重试间隔过短或异常一直未恢复增加重试日志观察重试计数限制最大重试次数增加退避时间fallback 返回空结果但业务无感知降级策略没有记录日志检查 AgentOutcome 的状态和日志降级结果统一打上 fallback 标记orTimeout 不生效异步任务内部阻塞未响应中断检查 Provider 调用是否支持超时中断使用带超时的 HTTP/网络调用批量任务部分失败但被吞掉异常处理器选择 ignore 后无统计检查 AgentOutcome 统计ignore 策略也要记录状态切换 provider 后上下文丢失备用 provider 没有继承会话信息检查上下文传递逻辑在切换前复制必要上下文9. 最佳实践与合规提醒9.1 工程建议第一次接入可选异常处理时先只接入“记录”和“重试”两种策略不要一上来就做复杂降级。异常策略注册表要保持简单处理器之间的顺序要有明确优先级。每个 AgentOutcome 都要包含异常类型、匹配策略、执行耗时方便监控。重试策略必须设置最大次数避免无限重试。批量任务的异常处理要保证线程安全避免共享状态被并发修改。9.2 合规与安全提醒Agent 异常处理会接触到模型返回、工具调用结果和用户输入。在配置降级策略和备用通道时必须确认数据是否允许被传给备用服务。涉及第三方模型接口时要确保接口调用符合服务协议和数据安全要求。如果要处理人脸、声音、身份信息等敏感数据必须获得合法授权并严格控制日志输出。批量任务中如果某个任务失败重试也要确保不会重复处理用户请求或产生重复扣费。10. 总结与下一步Agent 异常处理的可选性不是一个炫技的抽象而是解决 Agent 系统不稳定问题的最直接手段。把异常处理从“同一种方式处理所有异常”升级成“按场景选择策略”能明显提升超时、限流、批量任务等场景下的稳定性。建议先从一个小任务开始验证把当前代码里的 try-catch 换成策略注册表然后跑几个异常用例观察哪些异常可以重试、哪些应该忽略、哪些必须快速失败。跑通后再加入 fallback 和 provider 切换。整个过程不需要换框架只需要在现有工程里增加一个可选异常处理层收益会非常直接。