【AI城市旅游助手技术专题】Spring AI Advisors:AI调用的拦截增强神器

小编头像

小编

管理员

发布于:2026年04月29日

79 阅读 · 0 评论

发布时间:2026年4月10日

各位开发者,当你在使用Spring AI构建应用时,是否遇到过这样的困境:每个AI调用都需要手动拼接对话历史、添加RAG检索上下文、记录日志、过滤敏感词?这些与核心业务无关却又不得不写的重复代码散落在各个方法中,让代码变得臃肿难维护。这正是Spring AI Advisors(顾问/拦截器) 要解决的核心问题。作为Spring AI生态中不可或缺的组件,AI城市旅游助手在智能客服、实时推荐等场景中,正是借助Advisors实现了对话记忆和知识库增强等功能。今天,我们就从零开始,彻底搞懂Spring AI Advisors的设计思想、工作原理和落地实践。


一、痛点切入:为什么需要Advisors?

让我们先看一个典型的AI调用场景。假设你正在开发一个智能客服系统,每个用户请求都需要做以下处理:

  • 加载该用户的对话历史,让AI理解上下文

  • 从向量数据库中检索相关文档,做RAG增强

  • 记录请求和响应的日志,用于监控调试

  • 过滤用户输入中的敏感词

  • 统计本次调用的token消耗和成本

传统实现方式(无Advisors)

java
复制
下载
@Service
public class CustomerService {
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private ChatMemoryService memoryService;
    @Autowired
    private VectorStore vectorStore;
    
    public String askQuestion(String userId, String question) {
        // 痛点1:每个方法都要写一遍记忆加载
        List<Message> history = memoryService.loadHistory(userId);
        
        // 痛点2:每个方法都要写一遍RAG检索
        List<Document> docs = vectorStore.similaritySearch(question);
        String context = buildRagContext(docs);
        
        // 痛点3:敏感词过滤逻辑散落各处
        if (containsSensitiveWords(question)) {
            return "您的输入包含敏感词";
        }
        
        // 痛点4:日志记录代码冗余
        log.info("用户{}提问: {}", userId, question);
        long startTime = System.currentTimeMillis();
        
        String response = chatClient.prompt()
            .system("基于以下上下文回答问题:" + context)
            .user(question)
            .call()
            .content();
        
        log.info("回答耗时: {}ms, token消耗: {}", 
                 System.currentTimeMillis() - startTime, getTokenUsage());
        memoryService.saveHistory(userId, question, response);
        return response;
    }
}

痛点分析

  1. 关注点耦合:记忆管理、RAG检索、日志监控等非业务逻辑与核心问答逻辑紧密耦合

  2. 代码重复:同样的记忆加载、日志记录逻辑在每个AI调用方法中都要重复书写

  3. 难以扩展:新增一个需求(如添加限流功能),需要修改所有涉及AI调用的方法

  4. 维护成本高:某个公共逻辑的修改(如记忆存储方式变更)将影响数十处代码

这正是 Spring AI Advisors 的设计初衷——将AI交互中的横切关注点(cross-cutting concerns)抽离成独立的、可复用的拦截组件,实现关注点分离和代码复用。


二、核心概念:什么是Advisor?

标准定义

Advisor(顾问/拦截器) 是Spring AI框架中用于拦截、修改和增强AI交互请求与响应流程的模块化组件-5。它以AOP(Aspect-Oriented Programming,面向切面编程)风格工作,允许开发者在AI调用生命周期的关键节点插入自定义逻辑,而无需修改核心业务代码-1

关键词拆解

关键词解释
拦截(Intercept)在请求发送给LLM之前和响应返回给客户端之后介入
修改(Modify)可以对用户输入进行增强(如添加对话历史),也可以对模型输出进行处理(如脱敏)
增强(Enhance)为AI调用附加额外能力,如RAG、记忆、日志、护栏等
模块化(Modular)每个Advisor独立封装一种能力,可以自由组合
可复用(Reusable)编写一次,在任何ChatClient调用中均可使用

生活化类比

想象你去一家高级餐厅点餐。你向服务员说出你的需求,但服务员并不是直接把它交给厨师,而是经过层层处理:

  • 备忘录顾问:先翻看你的用餐记录(对话历史)

  • 推荐顾问:根据你的口味偏好,在点单上添加推荐菜品(RAG增强)

  • 安全检查顾问:检查是否有过敏食材(敏感词过滤)

  • 记录顾问:记录下点餐内容和时间(日志监控)

每一层处理都是独立的、可插拔的。如果你换一家餐厅,这些“顾问”依然可以复用。这正是Spring AI Advisors的设计哲学——让AI调用像搭积木一样灵活。

核心价值

  • 封装常见的生成式AI模式(如RAG、对话记忆、结构化输出)为可重用单元-15

  • 非侵入式增强:无需修改核心业务代码即可满足企业级复杂需求-1

  • 跨模型可移植性:同一套Advisor可在不同LLM供应商间复用-12


三、关联概念:从Spring AOP理解Advisors

对于熟悉Spring生态的开发者来说,Advisors的概念并不陌生——它本质上是将AOP(面向切面编程) 理念迁移到了AI交互场景中-45

AOP经典术语 vs Spring AI Advisors映射

AOP概念在Spring AI Advisors中的对应
Join Point(连接点)执行LLM调用的时刻(如调用 chatClient.call()-4
Advice(增强逻辑)Advisor中实现的拦截逻辑(before/after/around)-4
Pointcut(切点)哪些ChatClient调用应用该Advisor(通过客户端配置指定)-4
Weaving(织入)在运行时将Advisor附加到AI客户端的过程-4

一句话理解

AOP为方法调用提供横切能力,Spring AI Advisors为LLM调用提供横切能力——思想一致,只是拦截的对象从“Java方法”变成了“AI模型调用”。

在传统的Spring AOP中,我们写一个 @Around 切面来给方法添加日志或事务;在Spring AI中,我们实现一个Advisor来给LLM调用添加记忆或RAG-4

概念关系总结

维度Spring AOPSpring AI Advisors
拦截目标Java方法调用LLM API调用
实现方式@Aspect + @Around实现CallAdvisor接口
链式执行切面顺序(@Order顾问顺序(getOrder()
核心接口ProceedingJoinPointCallAdvisorChain
底层依赖动态代理、CGLIB责任链模式 + 动态代理

四、代码示例:从零搭建Advisors

4.1 环境准备

pom.xml 中添加Spring AI依赖:

xml
复制
下载
运行
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-chroma</artifactId>
</dependency>

4.2 基础用法:添加内置Advisor

Spring AI提供了多个开箱即用的内置Advisor,最常用的是对话记忆AdvisorRAG问答Advisor-8-30

java
复制
下载
@Configuration
public class AiConfig {
    
    @Bean
    public ChatClient chatClient(ChatModel chatModel, 
                                  VectorStore vectorStore,
                                  ChatMemory chatMemory) {
        return ChatClient.builder(chatModel)
            // 注册默认Advisor:所有调用都会自动应用
            .defaultAdvisors(
                // Advisor 1: 对话记忆 —— 自动维护多轮对话历史
                MessageChatMemoryAdvisor.builder(chatMemory).build(),
                // Advisor 2: RAG增强 —— 自动从向量库检索相关文档并注入Prompt
                QuestionAnswerAdvisor.builder(vectorStore).build()
            )
            .build();
    }
}

4.3 运行时动态配置Advisor

有些场景需要按请求动态调整Advisor行为(如不同会话使用不同的对话ID):

java
复制
下载
@Service
public class ChatService {
    private final ChatClient chatClient;
    
    public String chatWithMemory(String conversationId, String userInput) {
        return chatClient.prompt()
            // 运行时指定Advisor并传入参数
            .advisors(advisor -> advisor
                .param(ChatMemory.CONVERSATION_ID, conversationId))
            .user(userInput)
            .call()
            .content();
    }
}

4.4 自定义Advisor:实现日志拦截器

如果你需要实现自定义逻辑(如请求/响应日志记录、耗时统计),可以实现 CallAdvisor 接口:

java
复制
下载
@Component
public class LoggingAdvisor implements CallAdvisor {
    
    // 控制执行顺序:数值越小优先级越高
    @Override
    public int getOrder() {
        return 100;
    }
    
    @Override
    public String getName() {
        return "loggingAdvisor";
    }
    
    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, 
                                          CallAdvisorChain chain) {
        // 【前置处理】请求到达LLM之前
        long startTime = System.currentTimeMillis();
        String userText = request.userText();
        log.info("📨 用户请求: {}", truncate(userText, 200));
        
        // 调用下一个Advisor(最终会调用LLM)
        ChatClientResponse response = chain.nextCall(request);
        
        // 【后置处理】LLM返回之后
        long elapsed = System.currentTimeMillis() - startTime;
        String content = response.content();
        log.info("✅ 响应完成, 耗时: {}ms, 内容长度: {}字符", 
                 elapsed, content.length());
        
        return response;
    }
}

4.5 对比效果:使用前后代码简化

对比维度无Advisors使用Advisors
业务代码行数~30行(含记忆加载、RAG检索、日志、过滤)5行(仅核心问答逻辑)
新增功能成本修改所有AI调用方法编写一个Advisor + 注册到Builder
代码复用性各方法重复实现一处编写,全局复用
可测试性需要mock多个依赖Advisor可独立单元测试
java
复制
下载
// 使用Advisors后的清爽代码
@Service
public class CleanChatService {
    private final ChatClient chatClient;
    
    public String ask(String userId, String question) {
        // 仅保留核心业务逻辑!记忆、RAG、日志都交给Advisor
        return chatClient.prompt()
            .advisors(advisor -> advisor.param("userId", userId))
            .user(question)
            .call()
            .content();
    }
}

五、底层原理:Advisor链如何工作?

5.1 执行流程

Advisor链的核心机制是 责任链模式(Chain of Responsibility) ,执行流程如下-11-15

  1. 请求封装:Spring AI框架从用户Prompt创建 AdvisedRequest 对象,并创建一个空的 AdvisorContext 用于跨Advisor共享状态

  2. 链式处理(请求) :各Advisor按 getOrder() 值从小到大顺序执行,每个Advisor可以对请求进行修改或决定是否阻断

  3. LLM调用:最后一个Advisor将请求发送给LLM

  4. 链式处理(响应) :LLM响应沿着链条返回,各Advisor依次处理后置逻辑

  5. 返回客户端:最终响应返回给调用方

text
复制
下载
用户请求 → AdvisorA(前置) → AdvisorB(前置) → LLM调用 → AdvisorB(后置) → AdvisorA(后置) → 返回用户

⚠️ 关键注意:由于链条是堆栈结构,第一个处理请求的Advisor,是最后一个处理响应的-11。这在设计依赖响应结果的逻辑时需要特别注意。

5.2 递归Advisor(Recursive Advisor)

Spring AI从 1.1.0-M4 版本开始引入递归Advisor,它允许链条多次循环执行,支持更复杂的迭代式工作流-30

  • 工具调用循环:依次执行多个工具,每个工具的输出作为下一个决策的输入

  • 输出验证:验证结构化响应,验证失败时重新调用LLM

  • 重试逻辑:根据响应质量或外部条件优化请求

  • Agent循环:构建自主Agent,通过分析结果和决定下一步行动来迭代执行任务直到目标达成

传统单次Advisor链无法处理这类场景,而递归Advisor通过创建下游Advisor的子链并重复调用,实现了可控的迭代处理。

5.3 底层技术支撑

Spring AI Advisors的底层实现依赖于以下核心技术:

技术作用
责任链模式实现Advisor的串联调用和传递逻辑
动态代理在运行时将Advisor链织入ChatClient调用
Spring的Ordered接口通过 getOrder() 控制执行顺序
Builder模式提供流畅的API配置Advisor
不可变对象模式AdvisedRequest 采用Record实现,确保链式传递的安全性-5

六、高频面试题与参考答案

Q1:什么是Spring AI Advisors?它的核心作用是什么?

参考答案

Spring AI Advisors是Spring AI框架中用于拦截、修改和增强AI交互请求与响应流程的模块化组件。它的核心作用包括三个方面:

  1. 关注点分离:将对话记忆、RAG检索、日志监控等横切关注点从业务逻辑中抽离

  2. 代码复用:封装常见的生成式AI模式为可重用单元,一处编写全局使用

  3. 非侵入式增强:通过责任链模式在AI调用前后插入自定义逻辑,无需修改核心业务代码-4-5

踩分点:拦截/增强、横切关注点、模块化复用、责任链模式。

Q2:Advisors的执行顺序是如何控制的?请求处理和响应处理的顺序有什么关系?

参考答案

执行顺序通过 getOrder() 方法控制,数值越小,优先级越高,越先执行请求处理。由于Advisor链采用堆栈结构

  • 第一个处理请求的Advisor,是最后一个处理响应的

  • 最后一个处理请求的Advisor(最接近LLM的),是第一个处理响应的

前置逻辑(如日志记录)适合放在低Order值Advisor中,后置逻辑(如响应脱敏)适合放在高Order值Advisor中-11-15

踩分点getOrder() 控制顺序、堆栈结构、请求与响应处理顺序相反。

Q3:Spring AI Advisors和Spring AOP有什么联系和区别?

参考答案

联系:Spring AI Advisors借鉴了Spring AOP的设计理念,两者都用于处理横切关注点。

区别

  • AOP拦截的是Java方法调用,Advisors拦截的是LLM API调用

  • AOP通过 @Around 注解和 ProceedingJoinPoint 实现,Advisors通过实现 CallAdvisor 接口和 CallAdvisorChain 实现

  • Advisors天然支持链式组合和RAG、对话记忆等AI领域特定能力

可以理解为:AOP是为Java方法调用提供横切能力,Advisors是为LLM调用提供横切能力-4-45

踩分点:思想相同(横切关注点)、对象不同(方法调用 vs LLM调用)、AI领域特有封装。

Q4:自定义Advisor需要实现哪些方法?各有什么作用?

参考答案

自定义Advisor需要实现 CallAdvisor 接口,核心方法包括:

  1. getOrder():返回执行顺序,数值越小优先级越高

  2. getName():返回Advisor唯一标识,用于日志和调试

  3. adviseCall(ChatClientRequest request, CallAdvisorChain chain):核心拦截方法,包含前置逻辑(请求处理)、调用 chain.nextCall(request) 传递调用、后置逻辑(响应处理)

如果需要支持流式场景,还需实现 StreamAdvisor 接口及对应的 adviseStream 方法-30-2

踩分点getOrder()(顺序)、getName()(标识)、adviseCall(核心逻辑+调用next)、流式需额外实现。

Q5:什么是递归Advisor(Recursive Advisor)?适用于哪些场景?

参考答案

递归Advisor是Spring AI 1.1.0-M4引入的新特性,允许Advisor链多次循环执行,而不是传统的一次性单次执行。它通过创建下游Advisor的子链并重复调用来实现迭代处理。

适用场景包括:

  • 工具调用循环:依次执行多个工具,每个工具的输出作为下一个决策的输入

  • 输出验证:验证结构化响应,失败时重新调用LLM并附带错误反馈

  • Agent循环:构建自主Agent,通过分析结果和决定下一步行动来迭代执行任务-30

踩分点:多次循环执行、工具调用循环、输出验证、Agent循环。


七、总结

本文全面解析了Spring AI Advisors,我们从四个维度逐步深入:

维度核心要点
是什么拦截/增强AI交互的模块化组件,采用AOP思想处理横切关注点
为什么需要解决传统开发中关注点耦合、代码重复、难以扩展的痛点
怎么用内置Advisor开箱即用,自定义Advisor实现 CallAdvisor 接口
底层原理责任链模式 + 动态代理,支持递归执行复杂迭代逻辑

重点与易错点

  • 关注点分离:核心业务逻辑只关注问答本身,横切能力交给Advisor

  • 执行顺序getOrder() 值越小越先执行请求处理;堆栈结构下请求和响应处理顺序相反

  • 递归能力:从1.1.0-M4版本开始,支持多次循环执行,实现Agent和工具调用循环

  • 两种Advisor:非流式(CallAdvisor)和流式(StreamAdvisor)需分别实现


📌 预告

下一篇我们将深入 Spring AI中的Function Calling机制,讲解如何让AI模型动态调用外部工具和API,实现真正的Agent能力。敬请期待!


📢 本文为【AI城市旅游助手】技术系列第N篇,欢迎点赞、收藏、转发,一起探索Java + AI的技术前沿!

标签:

相关阅读