Spring AI Alibaba 拦截Advisor

一、概述

1.1 什么是Advisor

Spring AI Alibaba的Advisor(顾问/拦截器)是专门针对AI交互场景设计的增强组件,其核心灵感来源于责任链模式和**面向切面编程(AOP)**思想,为AI应用提供了统一、可扩展的请求/响应拦截与增强能力。

Advisor本质上是AI交互的"中间件",在用户请求发送到大语言模型(LLM)之前和模型响应返回给用户之后,自动执行通用逻辑,让开发者专注于业务核心而非重复的横切关注点。

1.2 核心优势

  • 代码复用:将对话记忆、敏感词过滤、日志记录等通用功能封装为可重用组件
  • 统一治理:在单一位置实现跨所有AI调用的安全、监控、限流等能力
  • 灵活扩展:支持自定义拦截逻辑,轻松集成第三方服务
  • 可移植性:编写的Advisor可在不同模型和用例间无缝迁移
  • 链式执行:支持多个Advisor按指定顺序协同工作

1.3 适用场景

  • 对话历史管理
  • 检索增强生成(RAG)
  • 敏感内容过滤
  • 请求/响应日志记录
  • 性能监控与指标采集
  • 限流与熔断
  • 提示词动态注入
  • 工具调用拦截与增强

二、核心原理剖析

2.1 执行流程:洋葱模型

Advisor采用经典的**洋葱模型(Onion Model)**执行方式,请求从外层到内层依次经过每个Advisor的前置处理,到达模型后,响应再从内层到外层依次经过每个Advisor的后置处理。

用户请求
   ↓
Advisor1 前置处理
   ↓
Advisor2 前置处理
   ↓
实际调用大模型
   ↓
Advisor2 后置处理
   ↓
Advisor1 后置处理
   ↓
返回给用户

关键特性:第一个处理请求的Advisor,最后一个处理响应。

2.2 接口体系

Spring AI Alibaba的Advisor接口体系设计清晰,主要分为同步和流式两大分支:

Advisor (顶层接口)
├─ CallAdvisor (同步调用拦截)
│  └─ ChatClientResponse adviseCall(ChatClientRequest, CallAdvisorChain)
├─ StreamAdvisor (流式调用拦截)
│  └─ Flux<ChatClientResponse> adviseStream(ChatClientRequest, StreamAdvisorChain)
└─ BaseAdvisor (抽象基类,提供通用实现)

核心方法说明

  • getName():返回Advisor的唯一名称
  • getOrder():返回执行优先级,值越小优先级越高
  • adviseCall()/adviseStream():拦截方法,包含前置和后置处理逻辑
  • chain.nextCall()/chain.nextStream():调用链中的下一个Advisor

2.3 上下文共享:AdvisorContext

AdvisorContext是一个Map<String, Object>对象,用于在整个Advisor链中传递和共享数据。通过它,多个Advisor可以协同工作,例如第一个Advisor计算的值可以被后续的Advisor使用。

使用方式

// 在前置处理中设置上下文
request.context().put("userId", "12345");

// 在后置处理中获取上下文
String userId = (String) response.context().get("userId");

三、内置Advisor详解

Spring AI Alibaba提供了多种开箱即用的内置Advisor,覆盖了大部分常见的AI应用场景:

3.1 MessageChatMemoryAdvisor

功能:自动管理对话历史,将历史消息注入到每次请求中,实现多轮对话能力。

使用示例

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory)
            .conversationId("default-conversation")
            .maxMessages(10)  // 保留最近10条消息
            .build()
    )
    .build();

3.2 QuestionAnswerAdvisor

功能:实现检索增强生成(RAG),自动从向量库中检索相关文档并注入到提示词中。

使用示例

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        QuestionAnswerAdvisor.builder(vectorStore)
            .topK(5)  // 返回最相关的5个文档
            .similarityThreshold(0.7)  // 相似度阈值
            .build()
    )
    .build();

3.3 SimpleLoggerAdvisor

功能:记录AI请求和响应的详细日志,便于调试和问题排查。

使用示例

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(new SimpleLoggerAdvisor())
    .build();

3.4 SafeGuardAdvisor

功能:敏感词过滤,检测请求和响应中的敏感内容并进行拦截或替换。

使用示例

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        new SafeGuardAdvisor(List.of("敏感词1", "敏感词2"))
    )
    .build();

四、自定义Advisor开发

4.1 同步调用拦截器(CallAdvisor)

适用于chatClient.prompt().call()这种同步调用场景。

完整示例:自定义日志拦截器

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.core.Ordered;

@Slf4j
public class CustomLoggerAdvisor implements CallAdvisor {

    @Override
    public String getName() {
        return "CustomLoggerAdvisor";
    }

    @Override
    public int getOrder() {
        // 较低优先级,接近模型调用
        return Ordered.LOWEST_PRECEDENCE - 100;
    }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        // 前置处理:记录请求信息
        long startTime = System.currentTimeMillis();
        log.info("=== AI 请求开始 ===");
        log.info("用户ID: {}", request.context().get("userId"));
        log.info("用户消息: {}", request.prompt().getUserMessage().getText());
        
        // 记录系统提示词
        request.prompt().getSystemMessage().ifPresent(
            sysMsg -> log.info("系统提示词: {}", sysMsg.getText())
        );

        // 放行,调用下一个Advisor
        ChatClientResponse response = chain.nextCall(request);

        // 后置处理:记录响应信息
        long endTime = System.currentTimeMillis();
        log.info("=== AI 请求结束 ===");
        log.info("响应耗时: {}ms", endTime - startTime);
        log.info("响应内容: {}", response.getResult().getOutput().getText());

        return response;
    }
}

4.2 流式调用拦截器(StreamAdvisor)

适用于chatClient.prompt().stream()这种流式响应场景。

完整示例:流式日志拦截器

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.core.Ordered;
import reactor.core.publisher.Flux;

@Slf4j
public class StreamLoggerAdvisor implements StreamAdvisor {

    @Override
    public String getName() {
        return "StreamLoggerAdvisor";
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 100;
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
        // 前置处理:记录请求信息
        long startTime = System.currentTimeMillis();
        log.info("=== 流式AI 请求开始 ===");
        log.info("用户消息: {}", request.prompt().getUserMessage().getText());

        // 放行,调用下一个Advisor
        return chain.nextStream(request)
            .doOnNext(response -> {
                // 处理每个响应片段
                if (response.getResult() != null && response.getResult().getOutput() != null) {
                    log.debug("收到响应片段: {}", response.getResult().getOutput().getText());
                }
            })
            .doOnComplete(() -> {
                // 流式响应完成后的处理
                long endTime = System.currentTimeMillis();
                log.info("=== 流式AI 请求结束 ===");
                log.info("总耗时: {}ms", endTime - startTime);
            })
            .doOnError(error -> {
                // 异常处理
                log.error("流式AI请求失败", error);
            });
    }
}

4.3 同时支持同步和流式

为了让自定义Advisor同时支持同步和流式调用,可以同时实现两个接口:

@Slf4j
public class UniversalLoggerAdvisor implements CallAdvisor, StreamAdvisor {

    @Override
    public String getName() {
        return "UniversalLoggerAdvisor";
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 100;
    }

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        // 同步处理逻辑
        return chain.nextCall(request);
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
        // 流式处理逻辑
        return chain.nextStream(request);
    }
}

4.4 工具调用拦截器(ToolInterceptor)

Spring AI Alibaba还提供了专门用于拦截工具调用的ToolInterceptor接口,适用于Agent场景。

完整示例:工具调用耗时统计拦截器

import lombok.extern.slf4j.Slf4j;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallHandler;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallRequest;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallResponse;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolInterceptor;

@Slf4j
public class ToolPerformanceInterceptor extends ToolInterceptor {

    @Override
    public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
        // 前置处理:记录开始时间
        long startTime = System.currentTimeMillis();
        log.info("工具调用开始: {}", request.getToolName());
        log.info("工具参数: {}", request.getArguments());

        try {
            // 执行工具调用
            ToolCallResponse response = handler.call(request);
            
            // 后置处理:记录耗时和结果
            long endTime = System.currentTimeMillis();
            log.info("工具调用成功: {}", request.getToolName());
            log.info("耗时: {}ms", endTime - startTime);
            log.info("返回结果: {}", response.getResult());
            
            return response;
        } catch (Exception e) {
            // 异常处理
            long endTime = System.currentTimeMillis();
            log.error("工具调用失败: {}, 耗时: {}ms", 
                request.getToolName(), endTime - startTime, e);
            throw e;
        }
    }
}

五、最佳实践与常见误区

5.1 最佳实践

  1. 合理设置执行顺序

    • 安全相关的Advisor(如敏感词过滤、限流)应设置最高优先级
    • 日志记录Advisor应设置较低优先级,确保能记录完整的请求和响应
    • 业务相关的Advisor(如动态提示词)应在安全检查之后执行
  2. 同时支持同步和流式

    • 自定义Advisor尽量同时实现CallAdvisorStreamAdvisor接口
    • 确保两种调用方式下的行为一致
  3. 使用上下文传递数据

    • 避免使用ThreadLocal传递数据,使用AdvisorContext
    • 上下文数据应明确命名,避免冲突
  4. 异常处理

    • 在Advisor中捕获并处理异常,避免影响整个调用链
    • 对于不可恢复的异常,返回友好的错误响应
  5. 性能优化

    • 避免在Advisor中执行耗时操作
    • 对于需要外部调用的逻辑,考虑使用异步处理

5.2 常见误区

  1. 忘记调用chain.next()

    • 这是最常见的错误,会导致请求无法到达模型
    • 确保在前置处理完成后调用chain.nextCall()chain.nextStream()
  2. 在after()中抛出异常

    • 后置处理中抛出异常会导致响应丢失
    • 建议只在后置处理中记录日志和采集指标
  3. 流式调用使用CallAdvisor

    • 流式调用必须使用StreamAdvisor
    • 否则会导致响应无法正常返回
  4. 硬编码配置

    • 避免在Advisor中硬编码敏感词、限流阈值等配置
    • 使用配置文件或外部服务进行管理
  5. 过度使用Advisor

    • 不要将业务逻辑放在Advisor中
    • Advisor只应处理横切关注点

六、总结

Spring AI Alibaba的Advisor机制为AI应用开发提供了强大而灵活的增强能力。通过理解其洋葱模型执行原理和接口体系,开发者可以轻松实现各种通用功能的封装和复用。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐