第十一篇:SpringAI 实战 11|Advisor 机制与对话记忆(ChatMemory):让 AI 拥有“记忆力”

发布时间:2026/6/14 0:54:16

第十一篇:SpringAI 实战 11|Advisor 机制与对话记忆(ChatMemory):让 AI 拥有“记忆力” 导读在前面的章节中我们成功实现了多模型共存与丝滑的流式输出。但在实际测试中如果你尝试连续追问例如你告诉AI你叫小明” - 再问Ai“你叫什么名字”你会发现 AI 在第二轮直接“失忆”了。这是因为大模型LLM本质上是无状态的。为了让 AI 拥有连贯的上下文记忆Spring AI 提供了两个核心机制1.ChatMemory对话记忆负责对话历史的存储与管理。2.Advisor顾问机制基于 AOP 思想在请求发送前自动注入历史在响应后自动保存新对话。本章我们将以通义千问Qwen为例彻底打通 AI 的多轮对话能力并配合网页端进行可视化验证。一、环境前置说明运行前提电脑安装 Ollama客户端提前拉取开源模型文件JDK21Gradle8.8SpringBoot3.5.14SpringAI1.1.7IDEA2023 社区版本章代码是在上一篇的基础上新增/修改的二、 核心配置装配 ChatMemory 与 Advisorpackagecom.example.demo.config;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;importorg.springframework.ai.chat.memory.ChatMemory;importorg.springframework.ai.chat.memory.InMemoryChatMemoryRepository;importorg.springframework.ai.chat.memory.MessageWindowChatMemory;importorg.springframework.ai.chat.model.ChatModel;importorg.springframework.ai.openai.OpenAiChatModel;importorg.springframework.ai.openai.OpenAiChatOptions;importorg.springframework.ai.openai.api.OpenAiApi;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassChatMemoryConfig{Value(${spring.ai.qwen.api-key})privateStringapiKey;Value(${spring.ai.qwen.base-url})privateStringbaseUrl;/** * 1. 将手动构建的 Qwen 模型注册为 Spring Bean */Bean(qwenChatModel)publicOpenAiChatModelqwenChatModel(){OpenAiApiqwenApiOpenAiApi.builder().apiKey(apiKey).baseUrl(baseUrl).build();returnOpenAiChatModel.builder().openAiApi(qwenApi).defaultOptions(OpenAiChatOptions.builder().model(qwen-turbo).build()).build();}/** * 1. 声明内存级的 ChatMemory生产环境可替换为 JdbcChatMemory * maxMessages 表示滑动窗口大小默认保留最近 20 条消息 */BeanpublicChatMemorychatMemory(){returnMessageWindowChatMemory.builder().maxMessages(20).chatMemoryRepository(newInMemoryChatMemoryRepository()).build();}/** * 2. 构建带有记忆能力的通义千问 ChatClient */Bean(qwenMemoryChatClient)publicChatClientqwenMemoryChatClient(// 明确指定注入通义千问的模型 BeanQualifier(qwenChatModel)ChatModelqwenChatModel,ChatMemorychatMemory){returnChatClient.builder(qwenChatModel)// 使用 Builder 模式挂载对话记忆 Advisor.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).build();}}三、 Controller 流式多轮对话接口为了在网页端实现打字机效果Controller 需要返回 Flux。关键在于必须通过参数传递 conversationId以实现不同用户/会话的记忆隔离。packagecom.example.demo.controller;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.ai.chat.memory.ChatMemory;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.http.MediaType;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importreactor.core.publisher.Flux;RestControllerRequestMapping(/ai)publicclassChatMemoryController{Qualifier(qwenMemoryChatClient)privatefinalChatClientqwenMemoryChatClient;publicChatMemoryController(Qualifier(qwenMemoryChatClient)ChatClientqwenMemoryChatClient){this.qwenMemoryChatClientqwenMemoryChatClient;}/** * 通义千问多轮对话流式接口 * param conversationId 会话ID前端生成或从 Session/Token 中获取 * param msg 用户输入 */GetMapping(value/chat/stream-memory,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchatWithMemory(RequestParam(defaultValuedefault-session)StringconversationId,RequestParamStringmsg){returnqwenMemoryChatClient.prompt().user(msg)// 核心指定当前对话的会话 ID.advisors(a-a.param(ChatMemory.CONVERSATION_ID,conversationId)).stream().content();}}四、 网页端极简 HTML 验证对话记忆为了验证通义千问的记忆能力我们用一个原生的 HTML 文件即可完美对接 SSE 流。在src/main/resources/static 目录下创建 memory-test.html!DOCTYPEhtmlhtmllangzh-CNheadmetacharsetUTF-8title通义千问对话记忆测试/titlestylebody{font-family:sans-serif;padding:20px;}#output{border:1px solid #ccc;padding:15px;min-height:200px;white-space:pre-wrap;margin-bottom:10px;border-radius:8px;background:#f9f9f9;}.input-area{display:flex;gap:10px;}input{flex:1;padding:10px;font-size:16px;}button{padding:10px 20px;cursor:pointer;}.session-info{font-size:12px;color:#666;margin-bottom:5px;}/style/headbodyh2通义千问多轮对话记忆测试/h2divclasssession-info当前会话 ID:strongidsessionIdDisplay/strongbuttononclickresetSession()stylepadding:2px 8px;font-size:12px;重置会话/button/divdividoutput等待输入.../divdivclassinput-areainputtypetextidmsgInputplaceholder试着连续提问例如我叫小明 - 你还记得我的名字吗autofocusbuttononclickstartStream()发送/button/divscriptletcurrentEventSourcenull;// 为当前浏览器标签页生成一个唯一的会话 IDletcurrentSessionIdsession-Math.random().toString(36).substr(2,9);document.getElementById(sessionIdDisplay).textContentcurrentSessionId;functionresetSession(){if(currentEventSource)currentEventSource.close();currentSessionIdsession-Math.random().toString(36).substr(2,9);document.getElementById(sessionIdDisplay).textContentcurrentSessionId;document.getElementById(output).innerHTML--- 会话已重置 ---\n;}functionstartStream(){constmsgInputdocument.getElementById(msgInput);constmsgmsgInput.value.trim();if(!msg)return;constoutputDivdocument.getElementById(output);outputDiv.innerHTML\n\n 你:${msg}\n 千问:;msgInput.value;// 1. 关闭上一次的连接if(currentEventSource)currentEventSource.close();// 2. 建立 SSE 连接传递 conversationId 和 msgconsturl/ai/chat/stream-memory?conversationId${currentSessionId}msg${encodeURIComponent(msg)};currentEventSourcenewEventSource(url);// 3. 监听消息实现打字机追加效果currentEventSource.onmessage(event){outputDiv.innerHTMLevent.data;outputDiv.scrollTopoutputDiv.scrollHeight;};// 4. 监听完成或错误currentEventSource.onerror(){currentEventSource.close();};}// 支持回车键发送document.getElementById(msgInput).addEventListener(keypress,function(e){if(e.keyEnter)startStream();});/script/body/html五、 运行与验证启动 Spring Boot 应用。浏览器访问 http://localhost:8080/memory-test.html。测试记忆第一轮输入“你好 我叫小明”第二轮输入“你还记得我的名字吗”第三轮输入“我现在改名叫小明白了 你还记得吗”测试隔离性点击页面上的“重置会话”按钮再次输入“你还记得我的名字吗”千问会不记得小明这个名字这证明不同 conversationId 之间的记忆是完全隔离的。六、 本章总结通过 ChatMemory 与 MessageChatMemoryAdvisor 的配合结合通义千问模型Spring AI 将复杂的上下文管理封装成了优雅的声明式配置。至此我们的 AI 应用已经具备了多模型路由流式打字机输出多轮对话记忆七、 参考文献SpringAI官方文档

相关新闻