AI Agent 的容错与韧性:从错误处理到生产级可靠性保障的 Java 实战 系列文章
理解 AI Agent 的大脑:ReAct 模式从入门到实战
AI Agent 的工具箱:深入理解 Tool Use 与 Spring AI Function Calling 实战
AI Agent 的记忆力是怎么实现的——LangChain4j Memory 机制深度解析
AI Agent 的记忆系统:从 ChatMemory 到持久化记忆的 Java 实战
AI Agent 的灵魂对话:Prompt Engineering 系统提示词设计的艺术与工程
AI Agent 的规划大脑:从任务分解到自适应执行策略
AI Agent 的推理引擎:从 Chain-of-Thought 到推理模型的深度解析与 Java 实战
AI Agent 的工具箱:深入理解 Tool Use 与 Spring AI Function Calling 实战
AI Agent 的可观测性:从链路追踪到成本监控的 Java 实战
AI Agent 的安全防线:Prompt 注入防御与生产级安全防护实战
AI Agent 评估与优化:从基准测试到生产环境的质量守护实战
AI Agent 团队协作:多 Agent 系统架构设计与 Java 实战
AI Agent 的容错与韧性:从错误处理到生产级可靠性保障的 Java 实战 (本文)
前言:你的 Agent 翻过车吗? 你精心设计了一个 AI Agent,Demo 演示行云流水,老板拍手叫好。上线第一天,Agent 就给你来了个”惊喜”:
调用天气 API 超时,整个对话卡死 30 秒后返回一个莫名其妙的错误
LLM 偶尔返回了格式错误的 JSON,下游解析直接 NPE
用户输入了一段超长文本,Token 爆了,Agent 直接”失忆”
连续 50 个请求都是恶意 Prompt 注入,Agent 被”玩坏了”
这不是段子,这是每一个把 Agent 部署到生产环境的开发者都会遇到的真实场景。
传统软件的错误处理相对简单——网络超时就重试,数据库挂了就切主从,代码 Bug 就 Hotfix。但 AI Agent 的错误模式完全不同:它的核心”大脑”是一个概率系统 ,LLM 的输出天然具有不确定性。同样的输入,两次调用可能得到不同的结果;格式化要求再严格,偶尔也会输出不符合 JSON Schema 的文本。
这篇文章就来聊聊:如何让 AI Agent 在各种”翻车”场景下依然能优雅地工作。我们会从 Agent 的独特失败模式讲起,逐步深入到重试策略、Fallback 降级、断路器模式、Human-in-the-Loop 等生产级容错机制,并用 LangChain4j 和 Spring AI 给出完整的 Java 实战代码。
AI Agent 的独特失败模式 在设计容错方案之前,我们需要先理解 Agent 到底会怎么”挂”。和传统微服务相比,Agent 的失败模式有三个显著不同:
1. LLM 调用的不确定性 传统 RPC 调用是确定性的——给定相同输入,总是返回相同输出。但 LLM 不一样:
1 2 3 String result1 = chatModel.call("今天星期几?" ); String result2 = chatModel.call("今天星期几?" );
这种不确定性带来的问题远不止”答案不一样”。更严重的是,LLM 可能返回结构上就不对 的内容——你要求 JSON,它给你返回了一段自然语言;你要求枚举值,它给你返回了一个不在枚举中的词。
2. 级联失败的放大效应 Agent 的调用链通常很长:用户输入 → LLM 理解 → 选择工具 → 执行工具 → LLM 总结 → 返回结果。任何一环出错,后续全部失败。而且由于 LLM 的不确定性,重试不一定能解决问题 ——甚至可能让问题更糟(比如 LLM 在重试时改变了工具选择策略)。
3. 资源消耗的不可预测性 传统 API 调用的资源消耗基本恒定,但 Agent 的 Token 消耗完全不可预测。一个看似简单的用户问题,可能触发 Agent 进入一个复杂的推理循环,消耗大量 Token 和时间。
理解了这些独特性,我们就能有针对性地设计容错方案了。
重试策略:不只是 for 循环加 sleep 重试是最基础的容错手段,但 Agent 场景下的重试远比传统场景复杂。
指数退避 + 抖动 对于瞬时故障(如 API 限流、网络抖动),指数退避是最经典的策略。但在 Agent 场景中,我们需要加上抖动(Jitter) ,避免多个 Agent 实例在同一时刻重试导致”惊群效应”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 @Component public class ResilientLlmClient { private static final int MAX_RETRIES = 3 ; private static final long BASE_DELAY_MS = 1000 ; private static final double JITTER_FACTOR = 0.3 ; private final ChatModel chatModel; private final Random random = new Random (); public String callWithRetry (String prompt) { Exception lastException = null ; for (int attempt = 0 ; attempt <= MAX_RETRIES; attempt++) { try { return chatModel.call(prompt); } catch (Exception e) { lastException = e; if (!isRetryable(e) || attempt == MAX_RETRIES) { break ; } long baseDelay = BASE_DELAY_MS * (1L << attempt); long jitter = (long ) (baseDelay * JITTER_FACTOR * random.nextDouble()); long delay = baseDelay + jitter; log.warn("LLM 调用失败,第 {} 次重试,等待 {}ms" , attempt + 1 , delay, e); try { Thread.sleep(delay); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break ; } } } throw new LlmCallFailedException ("LLM 调用在 " + MAX_RETRIES + " 次重试后仍然失败" , lastException); } private boolean isRetryable (Exception e) { if (e instanceof HttpTimeoutException) return true ; if (e instanceof RateLimitException) return true ; if (e instanceof ServiceUnavailableException) return true ; if (e instanceof HttpClientErrorException httpEx) { return httpEx.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS || httpEx.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE; } return false ; } }
模型降级重试 比普通重试更聪明的策略是模型降级 :主力模型失败时,切换到备用模型。这就像传统架构中的主从切换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Component public class ModelFallbackChain { private final List<ModelCandidate> modelChain; public ModelFallbackChain ( @Qualifier("primaryModel") ChatModel primary, @Qualifier("fallbackModel") ChatModel fallback, @Qualifier("emergencyModel") ChatModel emergency) { this .modelChain = List.of( new ModelCandidate ("gpt-4o" , primary, 0.005 ), new ModelCandidate ("gpt-4o-mini" , fallback, 0.00015 ), new ModelCandidate ("gpt-3.5-turbo" , emergency, 0.0005 ) ); } public ModelResult callWithFallback (String systemPrompt, String userPrompt) { List<String> errors = new ArrayList <>(); for (ModelCandidate candidate : modelChain) { try { long start = System.currentTimeMillis(); String result = candidate.model().call(systemPrompt + "\n\n" + userPrompt); long latency = System.currentTimeMillis() - start; return new ModelResult (result, candidate.name(), latency, candidate != modelChain.get(0 )); } catch (Exception e) { errors.add(candidate.name() + ": " + e.getMessage()); log.warn("模型 {} 调用失败,尝试下一个: {}" , candidate.name(), e.getMessage()); } } throw new AllModelsFailedException ("所有模型均调用失败: " + errors); } record ModelCandidate (String name, ChatModel model, double costPer1kTokens) {} record ModelResult (String content, String modelUsed, long latencyMs, boolean wasDegraded) {} }
关键设计点 :降级链中的模型应该是”能力递减但稳定性递增”的。GPT-4o 推理能力最强但偶尔超时,GPT-4o-mini 稍弱但更稳定,GPT-3.5-turbo 最快最便宜。根据业务场景决定降级深度——对于客服场景,降级到 mini 完全可以接受;对于医疗诊断场景,宁可失败也不能降级。
参数调整重试 有时候 LLM 返回错误不是因为服务不可用,而是因为输出格式不对。这时候不应该原样重试 ,而应该调整参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public String callWithFormatRetry (String prompt, Class<?> expectedType) { String result = chatModel.call(prompt); if (isValidJson(result, expectedType)) { return result; } ChatOptions strictOptions = ChatOptions.builder() .temperature(0.0 ) .responseFormat(ResponseFormat.builder() .type(ResponseFormat.Type.JSON_SCHEMA) .schema(extractSchema(expectedType)) .build()) .build(); result = chatModel.call(new Prompt (prompt, strictOptions)).getResult().getOutput().getContent(); if (isValidJson(result, expectedType)) { return result; } String strictPrompt = prompt + "\n\n注意:你必须且只能返回有效的 JSON,不要包含任何其他文字、解释或 markdown 标记。" ; return modelFallbackChain.callWithFallback("你是一个严格的 JSON 输出器。" , strictPrompt).content(); }
这种”逐步加约束”的重试策略,比简单重复调用有效得多。
Fallback 降级:多层兜底方案 当重试也无法解决问题时,我们需要 Fallback——提供一个”次优但可用”的结果。Fallback 的核心思想是:有总比没有好 。
分层 Fallback 架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Service public class AgentFallbackService { private final ChatModel chatModel; private final CacheService cacheService; private final RuleEngine ruleEngine; private final TemplateEngine templateEngine; public String handleWithFallback (UserRequest request) { try { return chatModel.call(buildFullPrompt(request)); } catch (Exception e) { log.warn("L1 失败: {}" , e.getMessage()); } try { String simplifiedPrompt = "请简要回答:" + request.getQuestion(); return chatModel.call(simplifiedPrompt); } catch (Exception e) { log.warn("L2 失败: {}" , e.getMessage()); } Optional<String> cached = cacheService.findSimilarAnswer(request.getQuestion()); if (cached.isPresent()) { log.info("L3 缓存命中" ); return cached.get(); } Optional<String> ruleResult = ruleEngine.match(request.getQuestion()); if (ruleResult.isPresent()) { log.info("L4 规则引擎命中" ); return ruleResult.get(); } log.info("L5 模板兜底" ); return templateEngine.render("fallback-response" , Map.of( "question" , request.getQuestion(), "supportEmail" , "support@example.com" )); } }
为什么 L3(缓存)放在 L2(简化调用)之后? 因为 L2 的成功率通常很高(短 Prompt 更不容易超时),而且返回的是实时生成的内容,比缓存更”新鲜”。缓存只有在 LLM 完全不可用时才使用。
工具调用的 Fallback Agent 不只是调用 LLM,还要调用各种工具。工具调用同样需要 Fallback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ToolExecutionWithFallback { private final Map<String, ToolExecutor> primaryTools; private final Map<String, ToolExecutor> backupTools; public ToolResult executeWithFallback (String toolName, Map<String, Object> params) { ToolExecutor primary = primaryTools.get(toolName); ToolExecutor backup = backupTools.get(toolName); try { return primary.execute(params); } catch (ToolExecutionException e) { log.warn("主工具 {} 执行失败: {}" , toolName, e.getMessage()); if (backup != null ) { try { ToolResult result = backup.execute(params); result.setDegraded(true ); return result; } catch (Exception backupEx) { log.error("备用工具 {} 也失败了: {}" , toolName, backupEx.getMessage()); } } return ToolResult.softFailure( "工具 " + toolName + " 暂时不可用,请基于你的知识尝试回答," + "或者告诉用户该功能暂时不可用。" ); } } }
注意最后的 ToolResult.softFailure()——这不是抛异常,而是返回一个包含错误信息的结果给 LLM 。这样 LLM 可以自主决定是用自身知识回答,还是告知用户功能暂时不可用。这就是 Agent 和传统软件的根本区别:Agent 有能力理解和应对失败 。
断路器模式:保护你的 LLM 预算 如果说重试和 Fallback 是”事后补救”,那断路器就是”事前预防”。当 LLM 服务连续失败时,断路器会”跳闸”,直接拒绝后续请求,避免无意义的重试消耗资源和预算。
三态断路器 断路器有三个状态:关闭(正常) 、打开(拒绝请求) 、半开(试探恢复) 。用 Resilience4j 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 @Configuration public class CircuitBreakerConfig { @Bean public CircuitBreaker llmCircuitBreaker () { CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(50 ) .slowCallRateThreshold(80 ) .slowCallDurationThreshold(Duration.ofSeconds(15 )) .waitDurationInOpenState(Duration.ofSeconds(30 )) .permittedNumberOfCallsInHalfOpenState(3 ) .slidingWindowSize(20 ) .minimumNumberOfCalls(5 ) .recordExceptions( HttpTimeoutException.class, RateLimitException.class, ServiceUnavailableException.class ) .ignoreExceptions( ValidationException.class, PromptTooLongException.class ) .build(); return CircuitBreaker.of("llm-service" , config); } @Bean public CircuitBreaker toolCircuitBreaker () { CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(70 ) .waitDurationInOpenState(Duration.ofSeconds(10 )) .slidingWindowSize(10 ) .build(); return CircuitBreaker.of("tool-service" , config); } } @Service public class CircuitBreakerProtectedAgent { private final CircuitBreaker llmCircuitBreaker; private final CircuitBreaker toolCircuitBreaker; private final ModelFallbackChain fallbackChain; public AgentResponse process (UserRequest request) { String llmResult = CircuitBreaker.decorateSupplier(llmCircuitBreaker, () -> { return fallbackChain.callWithFallback( buildSystemPrompt(), request.getQuestion() ).content(); }).apply(); if (needsToolCall(llmResult)) { ToolResult toolResult = CircuitBreaker.decorateSupplier(toolCircuitBreaker, () -> { return toolExecutor.execute(parseToolCall(llmResult)); }).apply(); return composeResponse(llmResult, toolResult); } return AgentResponse.of(llmResult); } }
为什么 LLM 和工具需要独立的断路器? 因为它们的故障模式不同。LLM 可能因为限流而不可用,但工具(如数据库查询)可能正常工作;反过来也一样。独立断路器让它们互不影响。
断路器 + Fallback 的组合 断路器跳闸后,请求直接走 Fallback 路径:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public AgentResponse processWithFullResilience (UserRequest request) { Supplier<String> llmCall = () -> chatModel.call(buildPrompt(request)); Supplier<String> fallback = () -> fallbackService.getCachedOrTemplate(request); String result = Try.ofSupplier( CircuitBreaker.decorateSupplier(llmCircuitBreaker, llmCall) ).recover(CallNotPermittedException.class, e -> { log.warn("断路器跳闸,走 Fallback 路径" ); return fallback.get(); }).recover(Exception.class, e -> { log.error("LLM 调用异常: {}" , e.getMessage()); return fallback.get(); }).get(); return AgentResponse.of(result); }
Human-in-the-Loop:让人类守住最后防线 有些场景下,Agent 不能”自作主张”——比如涉及资金操作、数据删除、医疗建议等高风险决策。这时候需要 Human-in-the-Loop(人在回路),让人类确认后再执行。
设计原则 Human-in-the-Loop 不是简单地”每步都问用户”,而是要智能地判断何时需要人类介入 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public enum RiskLevel { LOW, MEDIUM, HIGH, CRITICAL } @Component public class RiskAssessor { private static final Map<String, RiskLevel> TOOL_RISK_MAP = Map.of( "search" , RiskLevel.LOW, "send_email" , RiskLevel.MEDIUM, "delete_record" , RiskLevel.HIGH, "transfer_money" , RiskLevel.CRITICAL ); public RiskLevel assess (String toolName, Map<String, Object> params) { RiskLevel baseRisk = TOOL_RISK_MAP.getOrDefault(toolName, RiskLevel.MEDIUM); if ("delete_record" .equals(toolName)) { Integer count = (Integer) params.getOrDefault("limit" , 1 ); if (count > 100 ) { return RiskLevel.CRITICAL; } } if ("send_email" .equals(toolName)) { List<String> recipients = (List<String>) params.getOrDefault("to" , List.of()); if (recipients.size() > 50 ) { return RiskLevel.HIGH; } } return baseRisk; } }
确认机制实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Service public class HumanInTheLoopAgent { private final RiskAssessor riskAssessor; private final ConfirmationService confirmationService; public AgentResponse executeWithGuardrails (AgentContext context, String toolName, Map<String, Object> params) { RiskLevel risk = riskAssessor.assess(toolName, params); return switch (risk) { case LOW -> { ToolResult result = toolExecutor.execute(toolName, params); yield AgentResponse.of(result); } case MEDIUM -> { ToolResult result = toolExecutor.execute(toolName, params); confirmationService.notifyAsync(context.getUserId(), "Agent 已执行 " + toolName + ",结果: " + result.summary()); yield AgentResponse.of(result); } case HIGH -> { ConfirmationRequest confirmation = ConfirmationRequest.builder() .userId(context.getUserId()) .action(toolName) .params(params) .timeout(Duration.ofMinutes(5 )) .riskLevel(risk) .build(); ConfirmationResponse response = confirmationService.confirm(confirmation); if (response.isApproved()) { ToolResult result = toolExecutor.execute(toolName, params); yield AgentResponse.of(result); } else { yield AgentResponse.of("操作已取消。" + response.getReason()); } } case CRITICAL -> { ApprovalRequest approval = ApprovalRequest.builder() .userId(context.getUserId()) .action(toolName) .params(params) .requiredApprovals(2 ) .timeout(Duration.ofHours(24 )) .build(); ApprovalResponse approvalResponse = confirmationService.requestApproval(approval); if (approvalResponse.isApproved()) { ToolResult result = toolExecutor.execute(toolName, params); yield AgentResponse.of(result); } else { yield AgentResponse.of("操作需要人工审批,已提交审批流程。" + "审批ID: " + approvalResponse.getApprovalId()); } } }; } }
关键设计点 :超时机制。用户可能不会立即响应确认请求,Agent 不能无限等待。设置合理的超时时间,超时后自动取消操作并通知用户。
Guardrails:输入输出的安全护栏 在之前的文章 AI Agent 的安全防线 中,我们详细讨论了 Prompt 注入防御。这里从容错 的角度补充 Guardrails 的设计——它们不仅是安全机制,更是可靠性保障。
输入护栏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Component public class InputGuardrails { private static final int MAX_INPUT_LENGTH = 10000 ; private static final Pattern INJECTION_PATTERN = Pattern.compile( "ignore previous|忽略之前的|disregard|forget your instructions" , Pattern.CASE_INSENSITIVE ); public GuardrailResult validate (String userInput) { if (userInput.length() > MAX_INPUT_LENGTH) { return GuardrailResult.rejected("输入过长,请精简到 " + MAX_INPUT_LENGTH + " 字以内" ); } if (INJECTION_PATTERN.matcher(userInput).find()) { return GuardrailResult.rejected("检测到潜在的安全风险,请重新表述您的问题" ); } if (userInput.isBlank()) { return GuardrailResult.rejected("请输入您的问题" ); } return GuardrailResult.accepted(); } }
输出护栏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Component public class OutputGuardrails { private final JsonSchemaValidator schemaValidator; public GuardrailResult validate (String llmOutput, OutputConstraints constraints) { if (llmOutput == null || llmOutput.isBlank()) { return GuardrailResult.rejected("模型返回了空结果" ); } if (constraints.requiresJson()) { try { JsonNode json = objectMapper.readTree(llmOutput); if (constraints.getJsonSchema() != null ) { schemaValidator.validate(json, constraints.getJsonSchema()); } } catch (Exception e) { return GuardrailResult.rejected("输出格式不符合要求: " + e.getMessage()); } } if (containsSensitiveContent(llmOutput)) { return GuardrailResult.rejected("输出包含敏感内容,已拦截" ); } if (llmOutput.length() > constraints.getMaxLength()) { return GuardrailResult.rejected("输出过长,已截断" ); } return GuardrailResult.accepted(llmOutput); } private boolean containsSensitiveContent (String content) { return false ; } }
LangChain4j 和 Spring AI 的内置容错机制 在实际开发中,我们不一定需要从零实现所有容错逻辑。LangChain4j 和 Spring AI 都提供了一些内置的容错能力。
LangChain4j 的自动重试 LangChain4j 的 AiServices 在底层集成了自动重试:
1 2 3 4 5 6 7 8 9 10 11 12 13 Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel( OpenAiChatModel.builder() .apiKey(apiKey) .modelName("gpt-4o" ) .maxRetries(3 ) .timeout(Duration.ofSeconds(30 )) .logRequests(true ) .logResponses(true ) .build() ) .build();
但 LangChain4j 的内置重试比较基础,不支持模型降级、断路器等高级特性。生产环境中建议结合 Resilience4j 使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Configuration public class LangChain4jResilienceConfig { @Bean public ChatLanguageModel resilientModel () { ChatLanguageModel delegate = OpenAiChatModel.builder() .apiKey(apiKey) .modelName("gpt-4o" ) .build(); CircuitBreaker cb = CircuitBreaker.of("langchain4j" , CircuitBreakerConfig.custom() .failureRateThreshold(50 ) .waitDurationInOpenState(Duration.ofSeconds(30 )) .build()); Retry retry = Retry.of("langchain4j" , RetryConfig.custom() .maxAttempts(3 ) .waitDuration(Duration.ofMillis(500 )) .retryExceptions(HttpTimeoutException.class, RateLimitException.class) .build()); return new ResilientChatModel (delegate, cb, retry); } }
Spring AI 的 Advisor 链容错 Spring AI 的 Advisor Chain 提供了优雅的错误处理扩展点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Component public class ResilientAdvisor implements CallAroundAdvisor { private final ModelFallbackChain fallbackChain; @Override public AdvisedResponse aroundCall (AdvisedRequest request, CallAroundAdvisorChain chain) { try { return chain.nextAroundCall(request); } catch (Exception e) { log.warn("Advisor 链执行失败,进入 Fallback: {}" , e.getMessage()); String fallbackResult = fallbackChain.callWithFallback( request.adviseContext().get("system" ).toString(), request.userText() ).content(); return new AdvisedResponse ( new Generation (fallbackResult), Map.of("fallback" , true , "originalError" , e.getMessage()) ); } } @Override public String getName () { return "ResilientAdvisor" ; } @Override public int getOrder () { return Ordered.HIGHEST_PRECEDENCE; } }
生产环境最佳实践 1. 超时分层设置 不同调用应该有不同的超时时间:
1 2 3 4 5 6 7 agent: timeout: llm-call: 30s tool-call: 10s total-agent-run: 120s human-confirmation: 300s
2. 资源预算控制 防止 Agent “烧钱”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Component public class BudgetGuard { private final MeterRegistry meterRegistry; private static final double MAX_COST_PER_REQUEST = 0.10 ; private static final double MAX_DAILY_COST = 50.0 ; public BudgetCheckResult checkBudget (String userId) { double dailyCost = meterRegistry.get("agent.cost.daily" ) .tag("user" , userId) .counter().count(); if (dailyCost >= MAX_DAILY_COST) { return BudgetCheckResult.exceeded("今日使用额度已用完,请明天再来" ); } return BudgetCheckResult.ok(MAX_DAILY_COST - dailyCost); } public void recordCost (String userId, double cost) { meterRegistry.counter("agent.cost.daily" , "user" , userId).increment(cost); } }
3. 优雅降级的用户提示 当 Agent 进入降级模式时,要透明地告知用户 ,而不是默默返回低质量结果:
1 2 3 4 5 6 7 8 9 10 public String formatDegradedResponse (String content, DegradationReason reason) { String notice = switch (reason) { case MODEL_UNAVAILABLE -> "⚠️ 当前 AI 服务繁忙,以下回答基于简化模型生成,可能不如平时准确。" ; case TOOL_UNAVAILABLE -> "⚠️ 部分功能暂时不可用,以下回答可能不完整。" ; case BUDGET_EXCEEDED -> "⚠️ 今日使用额度即将用完,已切换到轻量模式。" ; case RATE_LIMITED -> "⚠️ 请求过于频繁,请稍后再试。" ; }; return notice + "\n\n" + content; }
4. 失败事件的可观测性 结合之前文章 AI Agent 的可观测性 中的方案,为每次失败建立完整的追踪链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Component public class FailureEventPublisher { private final ApplicationEventPublisher eventPublisher; public void publishFailure (AgentContext context, Exception error, RecoveryAction action) { AgentFailureEvent event = AgentFailureEvent.builder() .traceId(context.getTraceId()) .userId(context.getUserId()) .agentName(context.getAgentName()) .failedStep(context.getCurrentStep()) .errorType(error.getClass().getSimpleName()) .errorMessage(error.getMessage()) .recoveryAction(action) .timestamp(Instant.now()) .build(); eventPublisher.publishEvent(event); if (action == RecoveryAction.FALLBACK_TO_TEMPLATE || action == RecoveryAction.HUMAN_INTERVENTION) { alertService.sendAlert(AlertLevel.HIGH, event); } } }
总结 AI Agent 的容错不是简单地套用传统微服务的容错模式。由于 LLM 的不确定性、级联失败的放大效应和资源消耗的不可预测性,Agent 需要一套多层次、智能化 的容错体系:
层次
机制
适用场景
第一层
重试(指数退避 + 参数调整)
瞬时故障、格式错误
第二层
Fallback 降级(模型降级、缓存、规则引擎)
服务不可用
第三层
断路器(Resilience4j)
连续失败、保护预算
第四层
Human-in-the-Loop
高风险决策、关键操作
第五层
Guardrails(输入输出护栏)
安全防护、质量保障
核心设计原则:
让 Agent 自己处理失败 ——返回错误信息给 LLM,让它决定下一步,而不是硬编码错误处理逻辑
渐进式降级 ——从高质量结果逐步降到”有总比没有好”
透明告知用户 ——降级时明确告知,不要默默降低质量
预算守卫 ——防止 Agent 在故障重试中”烧钱”
可观测性 ——每次失败都应该有迹可循,为优化提供数据支撑
容错做得好不好,决定了你的 Agent 是只能在 Demo 中表演,还是能在生产环境中扛住真实用户的考验。
相关阅读推荐 :