场景
SpringAI+Ollama本地模型实现快速对话入门实例:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/160983728
在上面基础上实现Advisor 拦截器机制与实战全解。
在 Spring AI 的体系中,Advisor(顾问) 类似于 Web 应用中的过滤器(Filter)或 Spring 切面(AOP Around Advice),
它提供了一种无侵入的方式,在调用 AI 模型的前后对请求和响应进行拦截、修改甚至短路。通过 Advisor,
我们可以把日志记录、多轮对话记忆、安全过滤、性能监控等横切关注点模块化,并灵活组合成处理链,让业务代码保持干净。
本文将聚焦于 Spring AI 1.1.2 版本的 Advisor 机制,从概念、内置实现到自定义实战,
结合完整的代码示例和常见问题,帮助你彻底掌握 Advisor 的使用。
Advisor 核心概念
执行生命周期
ChatClient 请求 → Advisor1.before → Advisor2.before → ... → 调用 AI 模型
↓
Advisor1.after ← Advisor2.after ← ... ← AI 模型响应
before:在请求发送给模型之前执行,可以修改请求内容或中断链路。
after:在模型返回响应之后执行,可以修改响应内容。
Advisor 链
Advisor 以链式组织,通过 Ordered 接口控制执行顺序(值越小优先级越高)。
请求沿 Order 由小到大执行 before,响应沿 Order 由大到小执行 after。
核心 API
目的 1.0.0-M4(旧) 1.1.2(新)
简单修改(不短路) RequestResponseAdvisor BaseAdvisor
非流式短路 CallAroundAdvisor CallAdvisor
流式短路 StreamAroundAdvisor StreamAdvisor
请求对象 AdvisedRequest ChatClientRequest
响应对象 AdvisedResponse ChatClientResponse
短路是指 Advisor 直接返回一个响应,不再调用后续 Advisor 和 AI 模型,常用于安全过滤等场景。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
实现
SimpleLoggerAdvisor:快速日志记录
SimpleLoggerAdvisor 是 Spring AI 内置的日志 Advisor,开启后可在 DEBUG 级别记录每次请求和响应,非常便于调试。
注册方式:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatConfig {
@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
}
开启日志:在 application.yml 中配置:
logging:
level:
org.springframework.ai.chat.client.advisor: DEBUG
启动后在调用接口时,控制台会输出类似:
DEBUG ... SimpleLoggerAdvisor - request: AdvisedRequest[...]
DEBUG ... SimpleLoggerAdvisor - response: AdvisedResponse[...]
MessageChatMemoryAdvisor:多轮对话记忆
1、MessageChatMemoryAdvisor 可以将对话历史存储在 ChatMemory 中,并在后续请求中自动附加上下文。
注意:
InMemoryChatMemory 在 Spring AI 1.1.2 版本中已被移除。
为了更精细地控制记忆逻辑(例如防止超出模型 Token 限制),框架的 API 进行了重构,
将存储职责与记忆管理策略分离了。
直接后果是 InMemoryChatMemory 被废弃,取而代之的是 InMemoryChatMemoryRepository 和 MessageWindowChatMemory 组合
在 Spring AI 的较新版本(如 1.1.2 及其后续版本)中,框架提供了更完善的自动配置能力。
通过配置文件定制参数:
在 application.yml 中添加配置,以控制记忆的最大消息数量。
spring:
ai:
chat:
memory:
max-messages: 10
完整配置文件
server:
port: 886
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
model: qwen2.5:7b-instruct
options:
temperature: 0.7
num-ctx: 4096 # 上下文窗口大小
chat:
memory:
max-messages: 10
logging:
level:
# 将 Advisor 包的日志级别设置为 DEBUG,才能看到日志输出
org.springframework.ai.chat.client.advisor: DEBUG
2、注册 Advisor
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
3、控制器传入会话ID
每个会话需要唯一标识,通过请求参数动态传入:
package com.badao.ai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class ToolChatController {
private final ChatClient chatClient;
public ToolChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping("/chat/multi")
public ChatResponse multiTurnChat(@RequestBody MultiTurnChatRequest request) {
String result = chatClient.prompt()
.user(request.message())
.advisors(advisor -> advisor
// 动态传入会话 ID,告诉 MessageChatMemoryAdvisor 这是哪个会话
.param("chat_memory_conversation_id", request.conversationId())
)
.call()
.content();
return new ChatResponse(200, "success", result);
}
public record MultiTurnChatRequest(String message, String conversationId) {}
public record ChatResponse(int code, String msg, String data) {}
}
自定义 Advisor 实战:敏感词过滤
许多业务需要对用户输入进行安全审查,并在发现敏感词时直接返回警告,不调用 AI 模型(短路)。
1、BaseAdvisor 是 Spring AI 1.1.2 新增的便捷基类,它统一了 Call 和 Stream 模式,你只需重写 before 和 after 方法
package com.badao.ai.advisor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.core.Ordered;
import java.util.Arrays;
import java.util.List;
public class SensitiveWordGuardAdvisor implements BaseAdvisor {
private final Logger logger = LoggerFactory.getLogger(SensitiveWordGuardAdvisor.class);
private final List<String> sensitiveWords = Arrays.asList("暴力", "违禁");
@Override
public ChatClientRequest before(ChatClientRequest request, AdvisorChain chain) {
String userText = request.prompt().getContents();
if (containsSensitiveWord(userText)) {
logger.warn("BaseAdvisor 检测到敏感词: {}", userText);
}
return request; // BaseAdvisor 无法直接短路,仅做记录或修改
}
@Override
public ChatClientResponse after(ChatClientResponse response, AdvisorChain chain) {
return response;
}
private boolean containsSensitiveWord(String text) {
return sensitiveWords.stream().anyMatch(text::contains);
}
@Override
public String getName() {
return "SensitiveWordGuardAdvisor";
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
2、注册到链中
package com.badao.ai.config;
import com.badao.ai.advisor.SensitiveWordGuardAdvisor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ai.chat.model.ChatModel;
@Configuration
public class ChatConfig {
@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultAdvisors(
new SensitiveWordGuardAdvisor(), // 自定义安全检查 Advisor
new SimpleLoggerAdvisor(), // 将 SimpleLoggerAdvisor 注册为全局 Advisor
MessageChatMemoryAdvisor.builder(chatMemory) // 对话记忆 ← 使用 builder
.build() // 记忆 Advisor
)
.build();
}
}
测试验证
测试记忆和日志功能
传递001,告诉名字,然后再询问名字

传递002,询问名字

敏感词测试
