1. 概述
AI驱动应用已经成为现实。我们大量开发RAG检索增强应用、提示词接口,基于大语言模型构建各类项目。借助Spring AI,可以更高效、规范地完成这类开发。
本文将介绍 Spring AI 中非常实用的组件:Advisor(顾问/拦截器),它可以帮我们处理各类通用重复任务。
2. 什么是 Spring AI Advisor
Advisor 是AI应用中的请求‑响应拦截器,可以为提示词调用流程增加附加能力。例如维护聊天历史、屏蔽敏感词、为每一次请求追加额外上下文。
该功能的核心接口是
CallAroundAdvisor。实现该接口可以构建一条Advisor责任链,对请求和响应做加工处理。
执行流程:提示词发给聊天模型时,会经过整条Advisor链。

- 在提示词发送给模型之前,链上每一个Advisor执行自己的
before前置逻辑; - 在拿到模型返回结果之后,每一个Advisor执行自己的
after后置逻辑。
3. 聊天记忆相关Advisor
聊天记忆系列Advisor是一组非常实用的实现类,用于把对话历史注入聊天请求,提升大模型回答的准确性。
3.1 MessageChatMemoryAdvisor
使用 MessageChatMemoryAdvisor,通过messages消息属性把聊天历史带入ChatClient调用。 所有消息存放在ChatMemory实现类中,还可以控制保存的历史消息条数。
测试示例代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@SpringBootTest(classes = ChatModel.class) @EnableAutoConfiguration @ExtendWith(SpringExtension.class) public class SpringAILiveTest { @Autowired @Qualifier("openAiChatModel") ChatModel chatModel; ChatClient chatClient; @BeforeEach void setup() { chatClient = ChatClient.builder(chatModel).build(); } @Test void givenMessageChatMemoryAdvisor_whenAskingChatToIncrementTheResponseWithNewName_thenNamesFromTheChatHistoryExistInResponse() { ChatMemory chatMemory = new InMemoryChatMemory(); MessageChatMemoryAdvisor chatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory); String responseContent = chatClient.prompt() .user("Add this name to a list and return all the values: Bob") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Bob"); responseContent = chatClient.prompt() .user("Add this name to a list and return all the values: John") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Bob") .contains("John"); responseContent = chatClient.prompt() .user("Add this name to a list and return all the values: Anna") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Bob") .contains("John") .contains("Anna"); } } |
测试逻辑说明: 示例中使用内存版InMemoryChatMemory构建MessageChatMemoryAdvisor。多次发送提示词,要求模型维护姓名列表。可以看到,模型能够读取之前对话中的全部姓名。
3.2 PromptChatMemoryAdvisor
PromptChatMemoryAdvisor 同样实现把对话历史交给大模型。 区别:它会直接把聊天记忆文本追加到Prompt提示词内部。
底层会自动把如下文本拼接到提示词:
|
1 2 3 4 5 |
Use the conversation memory from the MEMORY section to provide accurate answers. --------------------- MEMORY: {memory} --------------------- |
测试验证代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
@Test void givenPromptChatMemoryAdvisor_whenAskingChatToIncrementTheResponseWithNewName_thenNamesFromTheChatHistoryExistInResponse() { ChatMemory chatMemory = new InMemoryChatMemory(); PromptChatMemoryAdvisor chatMemoryAdvisor = new PromptChatMemoryAdvisor(chatMemory); String responseContent = chatClient.prompt() .user("Add this name to a list and return all the values: Bob") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Bob"); responseContent = chatClient.prompt() .user("Add this name to a list and return all the values: John") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Bob") .contains("John"); responseContent = chatClient.prompt() .user("Add this name to a list and return all the values: Anna") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Bob") .contains("John") .contains("Anna"); } |
这一次,我们使用 PromptChatMemoryAdvisor,再次构造了若干提示词,要求大模型读取对话历史。和预期一样,所有历史数据都被正确返回。
3.3 VectorStoreChatMemoryAdvisor
VectorStoreChatMemoryAdvisor 能力更强:基于向量库做相似度匹配检索历史对话上下文,检索时会带上会话ID过滤相关文档。 示例使用改造后的SimpleVectorStore,也可以替换为任意向量数据库。
首先注册向量库Bean:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class SimpleVectorStoreConfiguration { @Bean public VectorStore vectorStore(@Qualifier("openAiEmbeddingModel") EmbeddingModel embeddingModel) { return new SimpleVectorStore(embeddingModel) { @Override public List<Document> doSimilaritySearch(SearchRequest request) { float[] userQueryEmbedding = embeddingModel.embed(request.query); return this.store.values() .stream() .map(entry -> Pair.of(entry.getId(), EmbeddingMath.cosineSimilarity(userQueryEmbedding, entry.getEmbedding()))) .filter(s -> s.getSecond() >= request.getSimilarityThreshold()) .sorted(Comparator.comparing(Pair::getSecond)) .limit(request.getTopK()) .map(s -> this.store.get(s.getFirst())) .toList(); } }; } } |
说明:重写相似度检索方法。原生
SimpleVectorStore不支持元数据过滤;本测试只有单条会话,因此可以正常运行。
测试历史记忆效果:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
@Test void givenVectorStoreChatMemoryAdvisor_whenAskingChatToIncrementTheResponseWithNewName_thenNamesFromTheChatHistoryExistInResponse() { VectorStoreChatMemoryAdvisor chatMemoryAdvisor = new VectorStoreChatMemoryAdvisor(vectorStore); String responseContent = chatClient.prompt() .user("Find cats from our chat history, add Lion there and return a list") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Lion"); responseContent = chatClient.prompt() .user("Find cats from our chat history, add Puma there and return a list") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Lion") .contains("Puma"); responseContent = chatClient.prompt() .user("Find cats from our chat history, add Leopard there and return a list") .advisors(chatMemoryAdvisor) .call() .content(); assertThat(responseContent) .contains("Lion") .contains("Puma") .contains("Leopard"); } |
执行逻辑:发起提问,Advisor底层执行向量相似度查询获取历史文档,大模型结合检索到的内容生成答案。
4. QuestionAnswerAdvisor(问答检索Advisor)
RAG检索增强场景高频使用QuestionAnswerAdvisor。 该Advisor会基于向量库相似度检索获取上下文,把上下文拼装进提示词给大模型。
测试示例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Test void givenQuestionAnswerAdvisor_whenAskingQuestion_thenAnswerShouldBeProvidedBasedOnVectorStoreInformation() { Document document = new Document("The sky is green"); List<Document> documents = new TokenTextSplitter().apply(List.of(document)); vectorStore.add(documents); QuestionAnswerAdvisor questionAnswerAdvisor = new QuestionAnswerAdvisor(vectorStore); String responseContent = chatClient.prompt() .user("What is the sky color?") .advisors(questionAnswerAdvisor) .call() .content(); assertThat(responseContent) .containsIgnoringCase("green"); } |
流程:向量库存入文档;Advisor检索相关上下文,模型基于文档内容回答问题。
5. SafeGuardAdvisor(安全防护Advisor)
业务需要拦截提示词中的敏感词汇,可以使用SafeGuardAdvisor:传入禁止词列表给Advisor实例。 当用户输入命中禁用词,请求直接拒绝,提示用户改写问题。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void givenSafeGuardAdvisor_whenSendPromptWithSensitiveWord_thenExpectedMessageShouldBeReturned() { List<String> forbiddenWords = List.of("Word2"); SafeGuardAdvisor safeGuardAdvisor = new SafeGuardAdvisor(forbiddenWords); String responseContent = chatClient.prompt() .user("Please split the 'Word2' into characters") .advisors(safeGuardAdvisor) .call() .content(); assertThat(responseContent) .contains("I'm unable to respond to that due to sensitive content"); } |
在这个示例中,首先实例化了一个SafeGuardAdvisor,配置了单个禁用词。随后在提示词中尝试使用该禁用词,不出所料,收到了禁用词校验拦截提示。
6. 实现自定义Advisor
开发者可以编写自定义Advisor,实现任意业务逻辑。下面实现CustomLoggingAdvisor,打印所有请求与响应日志。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public class CustomLoggingAdvisor implements CallAroundAdvisor { private final static Logger logger = LoggerFactory.getLogger(CustomLoggingAdvisor.class); @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { advisedRequest = this.before(advisedRequest); AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest); this.observeAfter(advisedResponse); return advisedResponse; } private void observeAfter(AdvisedResponse advisedResponse) { logger.info(advisedResponse.response() .getResult() .getOutput() .getContent()); } private AdvisedRequest before(AdvisedRequest advisedRequest) { logger.info(advisedRequest.userText()); return advisedRequest; } @Override public String getName() { return "CustomLoggingAdvisor"; } @Override public int getOrder() { return Integer.MAX_VALUE; } } |
要点说明:
- 实现
CallAroundAdvisor接口,在调用模型前后插入日志打印; getOrder()返回Integer.MAX_VALUE,代表该Advisor会放在责任链最末尾执行。
测试自定义Advisor:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Test void givenCustomLoggingAdvisor_whenSendPrompt_thenPromptTextAndResponseShouldBeLogged() { CustomLoggingAdvisor customLoggingAdvisor = new CustomLoggingAdvisor(); String responseContent = chatClient.prompt() .user("Count from 1 to 10") .advisors(customLoggingAdvisor) .call() .content(); assertThat(responseContent) .contains("1") .contains("10"); } |
执行后控制台输出日志示例:
|
1 2 |
c.b.s.advisors.CustomLoggingAdvisor : Count from 1 to 10 c.b.s.advisors.CustomLoggingAdvisor : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 |
7. 总结
本篇教程介绍了Spring AI强大的Advisor组件。 借助Advisor,可以快速实现聊天记忆、敏感词过滤、向量库RAG集成;同时还可以轻松编写自定义扩展实现专属业务逻辑。Advisor提供一套统一、简洁的方式实现上述能力。