> 技术文档 > 3、Spring AI_DeepSeek模型-多轮对话_messagewindowchatmemory是那个依赖中的

3、Spring AI_DeepSeek模型-多轮对话_messagewindowchatmemory是那个依赖中的


一、前言

Spring AI 提供跨 AI 供应商(如 OpenAI、Hugging Face 等)的一致性 API, 通过分装的ChatModelChatClient即可轻松调动LLM进行流式或非流式对话。

本专栏主要围绕着通过OpenAI调用各种大语言模型展开学习(因为大部分模型都兼容OpenAI方式调用接口),接下来将探索Spring AI的多轮对话(包括手动组装历史消息和Spring AI的ChatMemory

二、术语

2.1 Prompt

输入给大语言模型的指令或问题,通过提供上下文和任务要求,帮助模型理解用户意图、引导模型生成特定输出(包括特定格式-如JSON格式)

2.2 Message

包括3种角色的消息

2.3 ChatMemory

Spring AI 中的 ChatMemory 是用于解决大语言模型(LLM)无状态问题的核心组件,通过管理对话上下文实现多轮交互的连贯性。其核心模块包括 MessageWindowChatMemoryMessageChatMemoryAdvisorPromptChatMemoryAdvisor。

2.2.1 ChatMemory记忆存储接口)

  • 定位:定义对话消息的存储规范(增删查),是记忆管理的抽象底座。
  • 核心方法:
void add(String conversationId, Message message); // 存储消息List get(String conversationId); // 获取历史消息void remove(String conversationId);  // 删除会话
  • 实现类:MessageWindowChatMemory是唯一官方实现。

2.2.2 MessageWindowChatMemory(滑动窗口记忆管理)

  • 定位:ChatMemory的默认实现,通过窗口机制控制记忆长度。
  • 核心特性:
    • 默认保存最近20条消息(可配置maxMessages
    • 自动移除旧消息(保留系统消息)。
    • 依赖ChatMemoryRepository实现持久化(内存/JDBC/Redis等)。
  • 协作方式:
    • 存储原始Message对象(含角色信息)
    • 为Advisor提供原始对话数据源。

2.2.3 MessageChatMemoryAdvisor(结构化记忆注入)

  • 定位:将ChatMemory中的历史消息按原始角色结构注入Prompt。
  • 工作流程:
    • MessageWindowChatMemory读取指定conversationId的历史消息。
    • 将每条消息作为独立对象(保留user/assistant角色)加入Prompt消息列表。
  • 适用场景:需精准区分角色上下文的场景(如客服对话、多轮追问)。

2.2.4PromptChatMemoryAdvisor(扁平化记忆注入)

  • 定位:将历史消息拼接为纯文本块,插入系统提示词。
  • 工作流程:
    • MessageWindowChatMemory获取历史消息。
    • 拼接消息内容(丢失角色标签),追加到SystemMessage
  • 适用场景:成本敏感型场景或兼容老旧模型(如单文本输入的LLM)。

三、代码

3.1 项目依赖

 4.0.0 com.better spring-ai-parent 1.0-SNAPSHOT pom  models/chat/chat-openai-deepseek   org.springframework.boot spring-boot-starter-parent 3.4.5    17 17 UTF-8    org.springframework.boot spring-boot-starter-test   org.springframework.boot spring-boot-starter-web   org.springframework.ai spring-ai-starter-model-openai   org.projectlombok lombok   com.alibaba.fastjson2 fastjson2 2.0.57       org.springframework.ai spring-ai-bom  1.0.0  pom import   

3.2 配置yml

server: port: 8321spring: ai: openai: base-url: https://api.deepseek.com api-key: ${OPENAI_API_KEY} chat: options: model: deepseek-chat # 可选模型:deepseek-chat/deepseek-reasoner temperature: 0.6 # 响应随机性控制,默认值

3.3 手动组装上下文消息

package com.better.springai.history;import com.alibaba.fastjson2.JSON;import lombok.extern.slf4j.Slf4j;import org.assertj.core.util.Lists;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.messages.AssistantMessage;import org.springframework.ai.chat.messages.Message;import org.springframework.ai.chat.messages.SystemMessage;import org.springframework.ai.chat.messages.UserMessage;import org.springframework.ai.chat.model.ChatResponse;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;/** * 高级封装ChatClient */@RestController@Slf4jclass ChatHistoryController { private final ChatClient chatClient; public ChatHistoryController(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder.build(); } // 系统提示词 private final String SYSTEM_PROMPT = \"你是一位心理疏导助手,需以同理心回应用户情绪。请主动倾听并认可用户感受,引导其识别负面情绪(如压力、焦虑),通过以下步骤提供支持:\\n\" + \"倾听与共情 :先充分理解用户困扰,避免评判;\\n\" + \"情绪疏导 :建议倾诉、书写或呼吸放松法等释放情绪;\\n\" + \"认知调整 :用积极视角重构问题(如‘这是成长机会’);\\n\" + \"行动建议 :推荐具体行动(如运动、分阶段目标设定);\\n\" + \"资源引导 :若问题严重,建议寻求专业心理咨询;\\n\" + \"保持语言自然简洁,避免术语,用提问确认需求(如‘你希望先聊哪部分?’),逐步建立信任。\"; // 消息列表 private List messageList = new ArrayList(Lists.newArrayList(new SystemMessage(SYSTEM_PROMPT))); // 保留最近的N条历史聊天记录 private final static int MAX_HISTORY_SIZE = 2; // 聊天轮数 private int CHAT_ROUNDS = 1; @GetMapping(\"/chatWithHistory\") public String chatWithHistory(String question) { log.info(\"开始进行第{}轮对话,用户消息:{}\", CHAT_ROUNDS,question); // 用户输入的文本是UserMessage messageList.add(new UserMessage(question)); // 聊天长度校验, if (messageList.size() > MAX_HISTORY_SIZE + 1) { // 仅保留最近的N条聊天记录 messageList = messageList.subList(messageList.size() - MAX_HISTORY_SIZE - 1, messageList.size()); // 系统提示词 messageList.add(0,new SystemMessage(SYSTEM_PROMPT)); } log.info(\"第{}轮对话上下文:historyMessageList: {}\", CHAT_ROUNDS,JSON.toJSONString(messageList)); // 获取AssistantMessage ChatResponse chatResponse = chatClient.prompt(new Prompt(messageList)).call().chatResponse(); AssistantMessage assistantMessage = chatResponse.getResult().getOutput(); log.info(\"第{}次轮话AI回复消息{}\", CHAT_ROUNDS,assistantMessage.getText()); // 将AI回复内容存到历史对话记录中 messageList.add(assistantMessage); CHAT_ROUNDS ++; return assistantMessage.getText(); }}

执行结果:

3.4 ChatMemory自动组装上下文消息

3.4.1 MessageWindowChatMemory

package com.better.springai.history;import com.alibaba.fastjson2.JSON;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.memory.MessageWindowChatMemory;import org.springframework.ai.chat.messages.UserMessage;import org.springframework.ai.chat.model.ChatResponse;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;/** * 高级封装ChatClient */@RestController@Slf4jclass MessageWindowChatMemoryController { private final ChatClient chatClient; public MessageWindowChatMemoryController(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder.build(); } /** * 通过MessageWindowChatMemory记忆存储方式进行对话 * return void * @author luchuyan * @time 2025/7/20 19:20 **/ @GetMapping(\"/chatMessageWindowChatMemory\") public void chatMessageWindowChatMemory() { // 消息窗口聊天记忆 ChatMemory chatMemory = MessageWindowChatMemory.builder() .maxMessages(2) // 保存最新对话消息数量 .build(); // 对话ID,业务上唯一对话标识 String conversationId = \"001\"; // 第一次对话 UserMessage userMessage1 = new UserMessage(\"你的名字叫做狗蛋,会讲普通话、潮汕话\"); chatMemory.add(conversationId, userMessage1); ChatResponse response1 = chatClient.prompt(new Prompt(chatMemory.get(conversationId))).call().chatResponse(); chatMemory.add(conversationId, response1.getResult().getOutput()); log.info(\"用户第1次提问:{}\",userMessage1.getText()); log.info(\"AI第1次回复:{}\",response1.getResult().getOutput().getText()); log.info(\"第1次对话后记忆存储消息:{} \\n\\n\",JSON.toJSONString(chatMemory.get(conversationId))); // 第二次对话 UserMessage userMessage2 = new UserMessage(\"你的名字是什么?仅需回答名字即可,不要输出其他无关内容\"); chatMemory.add(conversationId, userMessage2); ChatResponse response2 = chatClient.prompt(new Prompt(chatMemory.get(conversationId))).call().chatResponse(); chatMemory.add(conversationId, response2.getResult().getOutput()); log.info(\"用户第2次提问:{}\",userMessage2.getText()); log.info(\"AI第2次回复:{}\",response2.getResult().getOutput().getText()); log.info(\"第2次对话后记忆存储消息:{}\\n\\n\",JSON.toJSONString(chatMemory.get(conversationId))); // 第二次对话 UserMessage userMessage3 = new UserMessage(\"你会讲什么话?\"); chatMemory.add(conversationId, userMessage3); ChatResponse response3 = chatClient.prompt(new Prompt(chatMemory.get(conversationId))).call().chatResponse(); chatMemory.add(conversationId, response3.getResult().getOutput()); log.info(\"用户第3次提问:{}\",userMessage3.getText()); log.info(\"AI第3次回复:{}\",response3.getResult().getOutput().getText()); log.info(\"第3次对话后记忆存储消息:{}\",JSON.toJSONString(chatMemory.get(conversationId))); }}

执行结果:

3.4.2 MessageChatMemoryAdvisor

package com.better.springai.history;import com.alibaba.fastjson2.JSON;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.memory.MessageWindowChatMemory;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;/** * 高级封装ChatClient */@RestController@Slf4jclass MessageChatMemoryAdvisorController { private final ChatClient chatClient; private ChatMemory chatMemory; public MessageChatMemoryAdvisorController(ChatClient.Builder chatClientBuilder) { chatMemory = MessageWindowChatMemory.builder() .maxMessages(2) // 保留最近的2条消息 .build(); this.chatClient = chatClientBuilder.clone() .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) // 通过MessageChatMemoryAdvisor策略进行组装对话消息 .build(); } @GetMapping(\"/chatMessageChatMemoryAdvisor\") public void chatMessageChatMemoryAdvisor() { // 对话ID,业务上唯一对话标识 String conversationId = \"002\"; String question1 = \"你的名字叫做狗蛋,会讲普通话、潮汕话\"; String responseContent1 = chatClient.prompt() .user(question1) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call() .content(); log.info(\"用户第1次提问:{}\",question1); log.info(\"AI第1次回复:{}\",responseContent1); log.info(\"第1次对话后记忆存储消息:{} \\n\\n\", JSON.toJSONString(chatMemory.get(conversationId))); String question2 = \"你的名字是什么?仅需回答名字即可,不要输出其他无关内容\"; String responseContent2 = chatClient.prompt() .user(question2) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call() .content(); log.info(\"用户第2次提问:{}\",question2); log.info(\"AI第2次回复:{}\",responseContent2); log.info(\"第2次对话后记忆存储消息:{} \\n\\n\",JSON.toJSONString(chatMemory.get(conversationId))); String question3 = \"你会讲什么话?\"; String responseContent3 = chatClient.prompt() .user(question3) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call() .content(); log.info(\"用户第3次提问:{}\",question3); log.info(\"AI第3次回复:{}\",responseContent3); log.info(\"第3次对话后记忆存储消息:{} \\n\\n\",JSON.toJSONString(chatMemory.get(conversationId))); }}

3.4.3 PromptChatMemoryAdvisor

和上面MessageChatMemoryAdvisor代码仅defaultAdvisors 处配置的不一样

 public PromptChatMemoryAdvisorController(ChatClient.Builder chatClientBuilder) { chatMemory = MessageWindowChatMemory.builder() .maxMessages(2) // 保留最近的2条消息 .build(); this.chatClient = chatClientBuilder.clone()// .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) // 通过MessageChatMemoryAdvisor策略进行组装对话消息 .defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) // 通过PromptChatMemoryAdvisor策略进行组装对话消息 .build(); }

完整代码:

package com.better.springai.history;import com.alibaba.fastjson2.JSON;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.memory.MessageWindowChatMemory;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;/** * 高级封装ChatClient */@RestController@Slf4jclass PromptChatMemoryAdvisorController { private final ChatClient chatClient; private ChatMemory chatMemory; public PromptChatMemoryAdvisorController(ChatClient.Builder chatClientBuilder) { chatMemory = MessageWindowChatMemory.builder() .maxMessages(2) // 保留最近的2条消息 .build(); this.chatClient = chatClientBuilder.clone()// .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) // 通过MessageChatMemoryAdvisor策略进行组装对话消息 .defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) // 通过PromptChatMemoryAdvisor策略进行组装对话消息 .build(); } @GetMapping(\"/chatPromptChatMemoryAdvisor\") public void chatPromptChatMemoryAdvisor() { // 对话ID,业务上唯一对话标识 String conversationId = \"002\"; String question1 = \"你的名字叫做狗蛋,会讲普通话、潮汕话\"; String responseContent1 = chatClient.prompt() .user(question1) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call() .content(); log.info(\"用户第1次提问:{}\",question1); log.info(\"AI第1次回复:{}\",responseContent1); log.info(\"第1次对话后记忆存储消息:{} \\n\\n\", JSON.toJSONString(chatMemory.get(conversationId))); String question2 = \"你的名字是什么?仅需回答名字即可,不要输出其他无关内容\"; String responseContent2 = chatClient.prompt() .user(question2) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call() .content(); log.info(\"用户第2次提问:{}\",question2); log.info(\"AI第2次回复:{}\",responseContent2); log.info(\"第2次对话后记忆存储消息:{} \\n\\n\",JSON.toJSONString(chatMemory.get(conversationId))); String question3 = \"你会讲什么话?\"; String responseContent3 = chatClient.prompt() .user(question3) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .call() .content(); log.info(\"用户第3次提问:{}\",question3); log.info(\"AI第3次回复:{}\",responseContent3); log.info(\"第3次对话后记忆存储消息:{} \\n\\n\",JSON.toJSONString(chatMemory.get(conversationId))); }}

四、参考资料

4.1 Spring AI官网文档

  • Chat Memory :: Spring AI Reference
  • Chat Client API :: Spring AI Reference

最后:如果文章对你有帮助,别忘了点赞支持一下,谢谢~