前言
在构建生产级 AI Agent 系统时,我们经常会遇到这样的需求:如何在请求发送给大模型之前进行统一处理?如何在多个处理步骤之间协调配合?如何优雅地实现日志记录、安全检查和提示增强?
Spring AI 的 Advisor 机制为这些问题提供了优雅的解决方案。本文将结合实际项目经验,深入探讨如何设计和实现一个严谨的 Advisor 体系。
一、Advisor 体系架构设计
在一个真实的 AI Agent 项目中,我们需要多层次的请求处理:
@Configuration public class AgentAdvisorConfig { @Bean public ChatClient agentChatClient(ChatClient.Builder builder) { return builder .defaultAdvisors( new SecurityCheckAdvisor(), // 优先级最高:安全检查 new ContextEnrichAdvisor(), // 上下文增强 new ReReadingAdvisor(), // Re2 推理增强 new TokenLimitAdvisor(), // Token 限制 new LoggingAdvisor(), // 日志记录 new FallbackAdvisor() // 降级处理 ) .build(); } }
每个 Advisor 都有明确的职责和执行顺序:
public class AdvisorOrder { public static final int SECURITY = -100; // 最早:安全检查 public static final int CONTEXT = -50; // 上下文准备 public static final int ENHANCE = 0; // 提示增强 public static final int LIMIT = 50; // 限制处理 public static final int LOGGING = 100; // 日志记录 public static final int FALLBACK = 200; // 最后:兜底处理 }
二、核心 Advisor 实现详解
1. 安全防护 Advisor
@Component public class SecurityCheckAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { private final Set<String> blockedKeywords = Set.of("malware", "hack", "exploit"); private final Pattern sqlInjectionPattern = Pattern.compile( "(?i)(\\bSELECT\\b|\\bDROP\\b|\\bDELETE\\b|\\bUPDATE\\b|\\bINSERT\\b)" ); @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { String userInput = advisedRequest.userText(); // 关键词检查 if (containsBlockedKeywords(userInput)) { return createBlockedResponse("输入包含不安全内容"); } // SQL 注入检查 if (sqlInjectionPattern.matcher(userInput).find()) { return createBlockedResponse("检测到潜在的不安全操作"); } // 内容长度检查 if (userInput.length() > 10000) { return createBlockedResponse("输入内容过长"); } // 通过检查,继续执行链 return chain.nextAroundCall(advisedRequest); } @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { String userInput = advisedRequest.userText(); if (containsBlockedKeywords(userInput)) { return Flux.just(createBlockedResponse("流式响应被安全策略拦截")); } return chain.nextAroundStream(advisedRequest); } private AdvisedResponse createBlockedResponse(String message) { return new AdvisedResponse( new AssistantMessage("抱歉," + message), Map.of("blocked", true, "reason", "security_check") ); } private boolean containsBlockedKeywords(String text) { return blockedKeywords.stream().anyMatch(text.toLowerCase()::contains); } @Override public int getOrder() { return AdvisorOrder.SECURITY; } @Override public String getName() { return "SecurityCheckAdvisor"; } }
2. Re2 推理增强 Advisor(实战版本)
@Component public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { private static final String DEFAULT_TEMPLATE = """ 请仔细分析以下问题: {re2_input_query} 现在,请重新阅读理解问题,确保没有遗漏任何关键信息: {re2_input_query} 请基于完整的理解给出答案。 """; private static final String RE2_ENABLED = "RE2_ENABLED"; private static final String RE2_ORIGINAL_QUERY = "RE2_ORIGINAL_QUERY"; private static final String RE2_PROCESSING_TIME = "RE2_PROCESSING_TIME"; private final String promptTemplate; private final boolean enableForStreaming; public ReReadingAdvisor() { this(DEFAULT_TEMPLATE, true); } public ReReadingAdvisor(String promptTemplate, boolean enableForStreaming) { this.promptTemplate = promptTemplate; this.enableForStreaming = enableForStreaming; } private AdvisedRequest prepareRequest(AdvisedRequest advisedRequest) { long startTime = System.currentTimeMillis(); String userText = advisedRequest.userText(); // 边界检查:空输入 if (userText == null || userText.trim().isEmpty()) { return advisedRequest; } // 边界检查:超长输入(避免 Token 浪费) if (userText.length() > 8000) { return advisedRequest; } // 检查是否已被其他 Advisor 禁用重读 Map<String, Object> existingContext = advisedRequest.adviseContext(); if (Boolean.FALSE.equals(existingContext.get("SIMPLE_QUERY"))) { return advisedRequest; // 简单查询不需要重读 } Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams()); advisedUserParams.put("re2_input_query", userText); return AdvisedRequest.from(advisedRequest) .userText(promptTemplate) .userParams(advisedUserParams) .adviseContext(context -> { context.put(RE2_ENABLED, true); context.put(RE2_ORIGINAL_QUERY, userText); context.put(RE2_PROCESSING_TIME, System.currentTimeMillis() - startTime); return context; }) .build(); } @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { AdvisedRequest modified = prepareRequest(advisedRequest); AdvisedResponse response = chain.nextAroundCall(modified); // 在响应中添加处理标记 return enrichResponse(response, modified); } @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { if (!enableForStreaming) { return chain.nextAroundStream(advisedRequest); } return Mono.just(advisedRequest) .publishOn(Schedulers.boundedElastic()) .map(this::prepareRequest) .flatMapMany(chain::nextAroundStream) .map(this::enrichStreamResponse) .onErrorContinue((throwable, obj) -> { // 流式处理中的错误不影响其他块 System.err.println("Re2 streaming error: " + throwable.getMessage()); }); } private AdvisedResponse enrichResponse(AdvisedResponse response, AdvisedRequest request) { // 在响应上下文中添加 Re2 处理信息 Map<String, Object> context = new HashMap<>(response.adviseContext()); context.putAll(request.adviseContext()); return new AdvisedResponse( response.response(), context, response.responseMetadata() ); } private AdvisedResponse enrichStreamResponse(AdvisedResponse response) { // 流式响应不需要额外处理,但可以添加标记 return response; } @Override public int getOrder() { return AdvisorOrder.ENHANCE; } @Override public String getName() { return "ReReadingAdvisor"; } }
3. 上下文增强与 Advisor 协作
Advisor 链中的上下文共享是构建复杂 Agent 的关键:
@Component public class ContextEnrichAdvisor implements CallAroundAdvisor { @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { // 读取之前的 Advisor 设置的上下文 Map<String, Object> context = new HashMap<>(advisedRequest.adviseContext()); // 检查是否有用户认证信息(假设由 SecurityCheckAdvisor 注入) String userId = (String) context.get("AUTH_USER_ID"); if (userId != null) { // 根据用户ID加载个性化配置 UserPreferences prefs = loadUserPreferences(userId); // 更新请求上下文,供后续 Advisor 使用 AdvisedRequest enriched = AdvisedRequest.from(advisedRequest) .adviseContext(ctx -> { ctx.put("USER_PREFERENCES", prefs); ctx.put("USER_LEVEL", prefs.getLevel()); // 根据用户级别决定是否启用 Re2 if (prefs.getLevel() < 2) { ctx.put("SIMPLE_QUERY", true); // 初级用户不需要重读 } return ctx; }) .build(); return chain.nextAroundCall(enriched); } return chain.nextAroundCall(advisedRequest); } private UserPreferences loadUserPreferences(String userId) { // 实际项目中从数据库或缓存加载 return new UserPreferences(userId, 3, "zh-CN"); } @Override public int getOrder() { return AdvisorOrder.CONTEXT; } @Override public String getName() { return "ContextEnrichAdvisor"; } } // 领域对象 record UserPreferences(String userId, int level, String language) { public int getLevel() { return level; } }
4. 完整的日志监控 Advisor
@Component @Slf4j public class LoggingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { private final MeterRegistry meterRegistry; public LoggingAdvisor(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { long startTime = System.currentTimeMillis(); String requestId = UUID.randomUUID().toString().substring(0, 8); try { // 读取整个链的上下文信息 Map<String, Object> context = advisedRequest.adviseContext(); boolean re2Enabled = Boolean.TRUE.equals(context.get("RE2_ENABLED")); String originalQuery = (String) context.get("RE2_ORIGINAL_QUERY"); String userId = (String) context.get("AUTH_USER_ID"); // 记录请求开始 log.info("[{}] Request started - User: {}, Re2: {}, Query: {}", requestId, userId, re2Enabled, truncate(originalQuery != null ? originalQuery : advisedRequest.userText(), 100)); AdvisedResponse response = chain.nextAroundCall(advisedRequest); // 记录请求完成 long duration = System.currentTimeMillis() - startTime; log.info("[{}] Request completed - Duration: {}ms", requestId, duration); // 记录指标 meterRegistry.timer("advisor.request.duration", "advisor", "LoggingAdvisor", "re2_enabled", String.valueOf(re2Enabled)) .record(duration, TimeUnit.MILLISECONDS); // 在响应中携带请求ID和耗时 return enrichResponseWithMetadata(response, requestId, duration); } catch (Exception e) { log.error("[{}] Request failed - Error: {}", requestId, e.getMessage()); meterRegistry.counter("advisor.request.errors").increment(); throw e; } } @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { String requestId = UUID.randomUUID().toString().substring(0, 8); long startTime = System.currentTimeMillis(); log.info("[{}] Stream request started", requestId); return chain.nextAroundStream(advisedRequest) .doOnComplete(() -> { long duration = System.currentTimeMillis() - startTime; log.info("[{}] Stream completed - Duration: {}ms", requestId, duration); }) .doOnError(error -> { log.error("[{}] Stream failed", requestId, error); }); } private AdvisedResponse enrichResponseWithMetadata( AdvisedResponse response, String requestId, long duration) { Map<String, Object> metadata = new HashMap<>(response.adviseContext()); metadata.put("REQUEST_ID", requestId); metadata.put("RESPONSE_DURATION", duration); return new AdvisedResponse(response.response(), metadata); } private String truncate(String text, int maxLength) { return text.length() > maxLength ? text.substring(0, maxLength) + "..." : text; } @Override public int getOrder() { return AdvisorOrder.LOGGING; } @Override public String getName() { return "LoggingAdvisor"; } }
三、流式处理的高级模式
针对流式场景,我们使用 Reactor 操作符实现复杂处理:
@Component public class AdvancedStreamAdvisor implements StreamAroundAdvisor { @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { return Mono.just(advisedRequest) // 1. 在不同线程池处理,避免阻塞主线程 .publishOn(Schedulers.boundedElastic()) // 2. 请求预处理 .map(this::preprocessRequest) // 3. 转换为流式处理 .flatMapMany(request -> chain.nextAroundStream(request) // 4. 对每个流块进行过滤 .filter(response -> !isEmptyResponse(response)) // 5. 转换响应内容 .map(this::transformResponse) // 6. 限流控制 .limitRate(10) // 7. 超时控制 .timeout(Duration.ofSeconds(30)) // 8. 错误重试 .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) .maxBackoff(Duration.ofSeconds(10)) .doBeforeRetry(signal -> log.warn("Retrying stream processing: {}", signal.failure().getMessage())) ) // 9. 监控每个块的延迟 .elapsed() .map(tuple -> { long elapsed = tuple.getT1(); AdvisedResponse response = tuple.getT2(); // 记录延迟指标 recordStreamChunkLatency(elapsed); return response; }) ) // 10. 整体流程的错误处理 .onErrorResume(this::handleStreamError) // 11. 确保资源清理 .doFinally(signalType -> cleanup(signalType)); } private AdvisedRequest preprocessRequest(AdvisedRequest request) { // 可以添加流式场景特有的预处理 return request; } private boolean isEmptyResponse(AdvisedResponse response) { // 过滤空响应块 String content = response.response().getContent(); return content == null || content.trim().isEmpty(); } private AdvisedResponse transformResponse(AdvisedResponse response) { // 可以对每个块的内容进行转换 return response; } private Flux<AdvisedResponse> handleStreamError(Throwable error) { // 优雅降级:返回错误信息而不是中断流 log.error("Stream processing error, returning fallback", error); return Flux.just(new AdvisedResponse( new AssistantMessage("处理过程中出现错误,请稍后重试") )); } private void recordStreamChunkLatency(long elapsedMillis) { // 记录流块的延迟 } private void cleanup(SignalType signalType) { // 清理资源,如关闭连接等 log.debug("Stream cleanup: {}", signalType); } @Override public int getOrder() { return 200; } @Override public String getName() { return "AdvancedStreamAdvisor"; } }
四、关键最佳实践与注意事项
1. 单一职责原则
错误示例:
// ❌ 职责混乱 public class BadAdvisor implements CallAroundAdvisor { @Override public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) { // 既做安全检查 if (request.userText().contains("danger")) { throw new SecurityException(); } // 又做日志记录 log.info("Processing: {}", request.userText()); // 还做内容转换 String enhanced = "Enhanced: " + request.userText(); // 甚至做缓存处理 if (cache.contains(enhanced)) { return cache.get(enhanced); } return chain.nextAroundCall(request); } }
正确示例:
// ✅ 职责分离 public class SecurityAdvisor implements CallAroundAdvisor { // 只做安全检查 } public class LoggingAdvisor implements CallAroundAdvisor { // 只做日志记录 } public class EnhanceAdvisor implements CallAroundAdvisor { // 只做内容增强 } public class CacheAdvisor implements CallAroundAdvisor { // 只做缓存处理 }
2. 执行顺序的重要性
// 场景演示:Advisor 执行顺序的影响 @SpringBootTest class AdvisorOrderTest { @Test void testAdvisorOrderMatters() { // 错误的顺序:先增强后检查 List<Advisor> wrongOrder = List.of( new ReReadingAdvisor(), // 0: 先重读 new SecurityCheckAdvisor() // -100: 后检查(但顺序错了) ); // 正确的顺序:先检查后增强 List<Advisor> correctOrder = List.of( new SecurityCheckAdvisor(), // -100: 先检查 new ReReadingAdvisor() // 0: 后增强 ); // 如果输入包含危险内容,错误顺序会导致: // 1. ReReadingAdvisor 先处理了危险输入 // 2. SecurityCheckAdvisor 再检查时已经浪费了 Token } @Test void testOrderInRealScenario() { String dangerousInput = "DROP TABLE users; --"; ChatClient client = ChatClient.builder() .defaultAdvisors( new SecurityCheckAdvisor(), // 应该先执行 new ReReadingAdvisor(), // 应该后执行 new LoggingAdvisor() // 最后记录 ) .build(); // 这样的顺序确保: // 1. 安全检查最先执行,危险输入被拦截 // 2. 只有安全的输入才会被增强处理 // 3. 最后记录完整的处理过程 } }
3. 边界条件处理
@Component public class RobustAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { @Override public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) { // 1. 空值检查 if (request == null) { log.error("Received null request"); return createErrorResponse("系统错误:空请求"); } String userText = request.userText(); // 2. 空内容检查 if (userText == null || userText.isBlank()) { log.warn("Received empty user text"); return createErrorResponse("请输入您的问题"); } // 3. 超大输入检查 if (userText.length() > 50_000) { log.warn("Input too large: {} characters", userText.length()); return createErrorResponse("输入内容过长,请精简后重试"); } // 4. 特殊字符处理 if (containsControlCharacters(userText)) { userText = sanitizeInput(userText); } // 5. 并发安全 Map<String, Object> params = new ConcurrentHashMap<>(request.userParams()); try { // 6. 异常捕获 AdvisedResponse response = chain.nextAroundCall(request); // 7. 响应空值检查 if (response == null || response.response() == null) { log.error("Received null response from chain"); return createErrorResponse("系统处理异常,请稍后重试"); } return response; } catch (IllegalArgumentException e) { log.error("Invalid argument in processing", e); return createErrorResponse("请求参数不合法"); } catch (Exception e) { log.error("Unexpected error in advisor", e); return createErrorResponse("系统内部错误"); } } @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest request, StreamAroundAdvisorChain chain) { // 流式场景的特殊边界处理 if (request == null || request.userText() == null) { return Flux.just(createErrorResponse("Invalid stream request")); } return chain.nextAroundStream(request) .onErrorContinue((error, obj) -> { log.error("Error in stream chunk, continuing", error); }) .switchIfEmpty(Flux.just(createErrorResponse("No response generated"))) .timeout(Duration.ofSeconds(60)) .onErrorResume(TimeoutException.class, e -> Flux.just(createErrorResponse("Response timeout")) ); } private boolean containsControlCharacters(String text) { return text.codePoints().anyMatch(cp -> cp < 32 && cp != 9 && cp != 10 && cp != 13); } private String sanitizeInput(String text) { return text.replaceAll("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]", ""); } private AdvisedResponse createErrorResponse(String message) { return new AdvisedResponse( new AssistantMessage(message), Map.of("error", true, "type", "validation_error") ); } }
4. 上下文传递与协作
@SpringBootTest class AdvisorContextCooperationTest { @Test void testContextSharingBetweenAdvisors() { // 模拟复杂的上下文传递场景 Map<String, Object> initialContext = new HashMap<>(); // Advisor 1: 用户认证 AdvisedRequest afterAuth = AdvisedRequest.from(originalRequest) .adviseContext(context -> { context.put("USER_ID", "12345"); context.put("USER_ROLE", "PREMIUM"); return context; }) .build(); // Advisor 2: 读取上下文并决策 Map<String, Object> authContext = afterAuth.adviseContext(); String userRole = (String) authContext.get("USER_ROLE"); AdvisedRequest afterDecision; if ("PREMIUM".equals(userRole)) { // 高级用户使用增强策略 afterDecision = AdvisedRequest.from(afterAuth) .adviseContext(context -> { context.put("USE_RE2", true); context.put("MODEL_LEVEL", "advanced"); return context; }) .build(); } else { // 普通用户使用基础策略 afterDecision = AdvisedRequest.from(afterAuth) .adviseContext(context -> { context.put("USE_RE2", false); context.put("MODEL_LEVEL", "basic"); return context; }) .build(); } // Advisor 3: 根据前面 Advisors 的决策执行 Map<String, Object> decisionContext = afterDecision.adviseContext(); boolean useRe2 = Boolean.TRUE.equals(decisionContext.get("USE_RE2")); String modelLevel = (String) decisionContext.get("MODEL_LEVEL"); // 记录整个决策链 log.info("Final processing decision: useRe2={}, modelLevel={}, userRole={}", useRe2, modelLevel, userRole); } @Test void testContextFlowInCompleteChain() { // 完整的 Advisor 链上下文流转 ChatClient client = ChatClient.builder() .defaultAdvisors( new ContextInjectionAdvisor(), // 注入初始上下文 new BusinessLogicAdvisor(), // 业务逻辑处理 new EnhancementAdvisor(), // 根据上下文决定增强策略 new MonitoringAdvisor() // 基于完整上下文进行监控 ) .build(); // 执行请求并验证上下文传递 String response = client.prompt() .user("复杂业务问题") .advisors(spec -> spec .param("businessType", "financial") .param("riskLevel", "high") ) .call() .content(); } }
5. 性能优化策略
@Component public class PerformanceOptimizedAdvisor implements CallAroundAdvisor { private final Cache<String, AdvisedResponse> responseCache; private final ExecutorService processingExecutor; public PerformanceOptimizedAdvisor() { // 使用 Caffeine 缓存 this.responseCache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .recordStats() .build(); // 专用线程池 this.processingExecutor = Executors.newFixedThreadPool(5); } @Override public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) { String cacheKey = generateCacheKey(request); // 1. 缓存优化 AdvisedResponse cached = responseCache.getIfPresent(cacheKey); if (cached != null) { log.debug("Cache hit for key: {}", cacheKey); meterRegistry.counter("advisor.cache.hit").increment(); return cached; } meterRegistry.counter("advisor.cache.miss").increment(); // 2. 异步预处理(对于复杂预处理) CompletableFuture<AdvisedRequest> preprocessedFuture = CompletableFuture .supplyAsync(() -> expensivePreprocessing(request), processingExecutor); try { // 3. 超时控制 AdvisedRequest preprocessed = preprocessedFuture.get(5, TimeUnit.SECONDS); AdvisedResponse response = chain.nextAroundCall(preprocessed); // 4. 缓存结果 responseCache.put(cacheKey, response); return response; } catch (TimeoutException e) { log.warn("Preprocessing timeout, using original request"); return chain.nextAroundCall(request); } catch (Exception e) { log.error("Error in performance advisor", e); return chain.nextAroundCall(request); } } private String generateCacheKey(AdvisedRequest request) { return DigestUtils.md5Hex(request.userText()); } private AdvisedRequest expensivePreprocessing(AdvisedRequest request) { // 模拟耗时的预处理操作 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return request; } @Override public int getOrder() { return 0; } @Override public String getName() { return "PerformanceOptimizedAdvisor"; } }
6. 错误恢复与降级策略
@Component public class FallbackAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { private final CircuitBreaker circuitBreaker; private final Map<String, String> fallbackResponses; public FallbackAdvisor() { // 熔断器配置 this.circuitBreaker = CircuitBreaker.of("advisor-fallback", CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(30)) .slidingWindowSize(10) .build() ); // 预设降级响应 this.fallbackResponses = Map.of( "timeout", "处理超时,请简化您的问题后重试", "rate_limit", "请求过于频繁,请稍后再试", "service_error", "服务暂时不可用,请稍后重试" ); } @Override public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) { return circuitBreaker.executeSupplier(() -> { try { return chain.nextAroundCall(request); } catch (TimeoutException e) { return createFallbackResponse("timeout", request); } catch (RateLimitExceededException e) { return createFallbackResponse("rate_limit", request); } catch (Exception e) { log.error("Service error, using fallback", e); return createFallbackResponse("service_error", request); } }); } @Override public Flux<AdvisedResponse> aroundStream(AdvisedRequest request, StreamAroundAdvisorChain chain) { return chain.nextAroundStream(request) .timeout(Duration.ofSeconds(30)) .onErrorResume(TimeoutException.class, e -> { log.warn("Stream timeout, using fallback"); return Flux.just(createFallbackResponse("timeout", request)); }) .onErrorResume(e -> { log.error("Stream error, using fallback", e); return Flux.just(createFallbackResponse("service_error", request)); }) .switchIfEmpty(Flux.defer(() -> { log.warn("Empty stream response"); return Flux.just(createFallbackResponse("service_error", request)); })); } private AdvisedResponse createFallbackResponse(String reason, AdvisedRequest request) { String message = fallbackResponses.getOrDefault(reason, "处理失败,请稍后重试"); return new AdvisedResponse( new AssistantMessage(message), Map.of( "fallback", true, "reason", reason, "original_query", request.userText() ) ); } @Override public int getOrder() { return AdvisorOrder.FALLBACK; } @Override public String getName() { return "FallbackAdvisor"; } }
五、测试与验证
@SpringBootTest class AdvisorIntegrationTest { @Autowired private ChatClient chatClient; @Test void testCompleteAdvisorChain() { // 测试完整的 Advisor 链协作 String response = chatClient.prompt() .user("帮我分析一下这个复杂的逻辑问题:...") .call() .content(); assertNotNull(response); // 验证响应中包含了 Re2 处理的特征 assertTrue(response.contains("根据重新阅读理解")); } @Test void testSecurityAdvisorBlocksMaliciousInput() { assertThrows(SecurityException.class, () -> { chatClient.prompt() .user("DROP TABLE users") .call(); }); } @Test void testFallbackWhenServiceUnavailable() { // 模拟服务不可用的情况 // 验证降级策略是否正常工作 } @Test void testAdvisorOrderExecution() { // 验证 Advisors 按正确顺序执行 List<String> executionOrder = new ArrayList<>(); ChatClient testClient = ChatClient.builder() .defaultAdvisors( new OrderTrackingAdvisor("first", -100, executionOrder), new OrderTrackingAdvisor("second", 0, executionOrder), new OrderTrackingAdvisor("third", 100, executionOrder) ) .build(); testClient.prompt().user("test").call(); assertEquals(List.of("first", "second", "third"), executionOrder); } }
六、总结
构建严谨的 AI Agent Advisor 体系需要注意:
-
架构设计:合理规划 Advisor 的职责和执行顺序
-
上下文管理:充分利用 adviseContext 实现 Advisor 间协作
-
错误处理:完善的边界检查和降级策略
-
性能优化:缓存、异步处理等性能优化措施
-
可观测性:日志、指标、追踪的全面覆盖
-
测试覆盖:单元测试和集成测试确保可靠性
通过合理运用这些模式和最佳实践,可以构建出健壮、可扩展、易维护的 AI Agent 系统。
附录:完整的 Advisor 开发检查清单
-
□ 单一职责:每个 Advisor 只做一件事
-
□ 接口实现:同时支持 CallAroundAdvisor 和 StreamAroundAdvisor
-
□ 执行顺序:合理设置 getOrder() 返回值
-
□ 边界处理:空值、超长、特殊字符等异常输入
-
□ 上下文传递:使用 adviseContext 共享状态
-
□ 错误处理:完善的异常捕获和降级策略
-
□ 性能考虑:避免耗时操作,使用缓存和异步处理
-
□ 日志监控:关键节点的日志记录和指标采集
-
□ 测试用例:覆盖正常流程和边界情况
-
□ 文档注释:清晰的 Javadoc 和使用说明