Advisors
Advisor(切面)是 Spring AI 的横切关注点机制,采用围绕调用链模式——在每个模型请求前后插入自定义逻辑,实现日志记录、内容安全、对话记忆、RAG 检索等能力。
1. 注册 Advisor
ChatClient 提供两种注册方式:defaultAdvisors() 在构建时设置全局生效,advisors() 在单次调用时追加。
// 全局注册:所有请求生效
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
// 单次注册:仅本次调用生效
chatClient.prompt()
.user("今天的天气怎么样?")
.advisors(new SafeGuardAdvisor())
.call()
.content();
全局和单次注册的 Advisor 可以叠加使用,按优先级排序后依次执行。
2. 内置 Advisor
2.1 请求日志
SimpleLoggerAdvisor 在请求前后记录日志,输出请求内容和模型响应,调试时无需手动打印。
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
// 调试时自动输出:
// [Request]: user="北京天气?"
// [Response]: "北京今天晴,22°C"
默认使用 SLF4J debug 级别,可通过 Builder 自定义日志格式:
SimpleLoggerAdvisor advisor = SimpleLoggerAdvisor.builder()
.requestToString(req -> "用户说了: " + req.prompt().getInstructions())
.responseToString(resp -> "模型回复: " + resp.chatResponse().getResult().getOutput().getText())
.build();
2.2 内容安全
SafeGuardAdvisor 在请求到达模型之前检测敏感词,命中则直接返回拒绝响应,不调用模型。
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new SafeGuardAdvisor())
.build();
// 包含敏感词时直接返回:
// "I'm unable to respond to that due to sensitive content..."
默认使用内置的敏感词列表,可通过构造函数传入自定义的 List<String>:
List<String> customWords = List.of("密码", "密钥", "token");
SafeGuardAdvisor advisor = new SafeGuardAdvisor(customWords);
2.3 对话记忆——基于消息
MessageChatMemoryAdvisor 自动将每次对话的 Message 对象存入 ChatMemory,下次请求时检索历史并追加到消息列表中。
ChatMemory chatMemory = new InMemoryChatMemory();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
// 第一轮
chatClient.prompt()
.user("我叫张三")
.advisors(a -> a.param("chat_memory_conversation_id", "conv-001"))
.call()
.content();
// 第二轮——自动携带上轮记忆
chatClient.prompt()
.user("我叫什么名字?")
.advisors(a -> a.param("chat_memory_conversation_id", "conv-001"))
.call()
.content();
// 模型回答: "你叫张三"
1.1.3 起,
MessageChatMemoryAdvisor和PromptChatMemoryAdvisor支持ToolResponseMessage——工具调用结果也会被纳入对话记忆。
2.4 对话记忆——基于提示词
PromptChatMemoryAdvisor 将对话历史渲染到 System Prompt 中,而非追加为独立 Message。适合不希望消息列表无限增长的场景。
ChatMemory chatMemory = new InMemoryChatMemory();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory))
.build();
chatClient.prompt()
.user("我叫李四")
.advisors(a -> a.param("chat_memory_conversation_id", "conv-002"))
.call()
.content();
自定义记忆提示词模板:
PromptChatMemoryAdvisor advisor = PromptChatMemoryAdvisor.builder(chatMemory)
.systemTextTemplate("以下是对话历史,请参考:\n{memory}")
.build();
2.5 向量检索增强
QuestionAnswerAdvisor 在请求模型前,先用用户问题检索 VectorStore,将检索到的文档作为上下文注入提示词。
VectorStore vectorStore = new SimpleVectorStore(embeddingModel);
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
.build();
String response = chatClient.prompt()
.user("Spring AI 如何实现函数调用?")
.call()
.content();
// 模型基于检索到的相关文档回答
2.6 模块化 RAG
RetrievalAugmentationAdvisor 提供完整的 RAG 流水线,支持查询转换、检索、后处理、增强等可配置阶段。适合需要精细控制 RAG 流程的场景。
RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(vectorStore)
.queryAugmenter(new ContextualQueryAugmenter())
.build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(advisor)
.build();
3. 传递参数给 Advisor
通过 AdvisorSpec.param() 向 Advisor 传递参数,如对话 ID、检索阈值等。
chatClient.prompt()
.user("我叫王五")
.advisors(a -> a
.param("chat_memory_conversation_id", "conv-003")
.param("chat_memory_response_size", 10)
)
.call()
.content();
param() 的参数会被写入请求上下文,各 Advisor 按需读取。常用参数:
| 参数名 | 适用的 Advisor | 说明 |
|---|---|---|
chat_memory_conversation_id | 记忆类 Advisor | 对话唯一标识,区分不同会话 |
chat_memory_response_size | 记忆类 Advisor | 每次检索的历史消息数量 |
3.1 AdvisorParams 预置常量(1.1.1 新增)
AdvisorParams 类提供预定义的 Advisor 参数常量,避免手动拼接字符串键。当前内置:
| 常量 | 键 | 说明 |
|---|---|---|
ENABLE_NATIVE_STRUCTURED_OUTPUT | structured_output_native | 启用原生结构化输出 |
// 启用原生结构化输出
chatClient.prompt()
.user("列出三个 Java 设计模式")
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.call()
.entity(Actor.class);
1.1.4 起,可以通过设置
Boolean.FALSE动态禁用原生结构化输出。此前只要该 Key 存在(containsKey)即启用;现在只有值为Boolean.TRUE时才生效,允许运行时按条件开关。// 动态禁用chatClient.prompt().user("列出三个 Java 设计模式").advisors(a -> a.param("structured_output_native", false)).call().entity(Actor.class);
4. 完整示例
完整示例:AdvisorExample.java
public class AdvisorExample {
public static void main(String[] args) {
OllamaApi ollamaApi = OllamaApi.builder()
.baseUrl("http://localhost:11434")
.build();
OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaOptions.builder().model("qwen3:8b").build())
.build();
// 创建带记忆的 ChatClient
ChatMemory chatMemory = new InMemoryChatMemory();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new SimpleLoggerAdvisor(),
new MessageChatMemoryAdvisor(chatMemory)
)
.defaultSystem("你是一个乐于助人的助手")
.build();
// 第一轮对话
String response1 = chatClient.prompt()
.user("我叫张三,是一名 Java 工程师")
.advisors(a -> a.param("chat_memory_conversation_id", "conv-001"))
.call()
.content();
System.out.println("第一轮: " + response1);
// 第二轮对话——自动携带记忆
String response2 = chatClient.prompt()
.user("我叫什么名字?做什么工作?")
.advisors(a -> a.param("chat_memory_conversation_id", "conv-001"))
.call()
.content();
System.out.println("第二轮: " + response2);
}
}