当前位置: 首页 > news >正文

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

导读:在前面的章节中,我们成功实现了多模型共存与丝滑的流式输出。但在实际测试中,如果你尝试连续追问(例如:你告诉AI你叫小明” -> 再问Ai“你叫什么名字”),你会发现 AI 在第二轮直接“失忆”了。这是因为大模型(LLM)本质上是无状态的。
为了让 AI 拥有连贯的上下文记忆,Spring AI 提供了两个核心机制:
1.ChatMemory(对话记忆):负责对话历史的存储与管理。
2.Advisor(顾问机制):基于 AOP 思想,在请求发送前自动注入历史,在响应后自动保存新对话。
本章我们将以通义千问(Qwen)为例,彻底打通 AI 的多轮对话能力,并配合网页端进行可视化验证。

一、环境前置说明

运行前提:电脑安装 Ollama客户端,提前拉取开源模型文件

  1. JDK:21
  2. Gradle:8.8
  3. SpringBoot:3.5.14
  4. SpringAI:1.1.7
  5. IDEA:2023 社区版
    (本章代码是在上一篇的基础上新增/修改的)

二、 核心配置:装配 ChatMemory 与 Advisor

packagecom.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(){OpenAiApiqwenApi=OpenAiApi.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(// 明确指定注入通义千问的模型 Bean@Qualifier("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;@RestController@RequestMapping("/ai")publicclassChatMemoryController{@Qualifier("qwenMemoryChatClient")privatefinalChatClientqwenMemoryChatClient;publicChatMemoryController(@Qualifier("qwenMemoryChatClient")ChatClientqwenMemoryChatClient){this.qwenMemoryChatClient=qwenMemoryChatClient;}/** * 通义千问多轮对话流式接口 * @param conversationId 会话ID(前端生成或从 Session/Token 中获取) * @param msg 用户输入 */@GetMapping(value="/chat/stream-memory",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<String>chatWithMemory(@RequestParam(defaultValue="default-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:

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>通义千问对话记忆测试</title><style>body{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></head><body><h2>通义千问多轮对话记忆测试</h2><divclass="session-info">当前会话 ID:<strongid="sessionIdDisplay"></strong><buttononclick="resetSession()"style="padding:2px 8px;font-size:12px;">重置会话</button></div><divid="output">等待输入...</div><divclass="input-area"><inputtype="text"id="msgInput"placeholder="试着连续提问,例如:'我叫小明' -> '你还记得我的名字吗?'"autofocus><buttononclick="startStream()">发送</button></div><script>letcurrentEventSource=null;// 为当前浏览器标签页生成一个唯一的会话 IDletcurrentSessionId='session-'+Math.random().toString(36).substr(2,9);document.getElementById('sessionIdDisplay').textContent=currentSessionId;functionresetSession(){if(currentEventSource)currentEventSource.close();currentSessionId='session-'+Math.random().toString(36).substr(2,9);document.getElementById('sessionIdDisplay').textContent=currentSessionId;document.getElementById('output').innerHTML='--- 会话已重置 ---\n';}functionstartStream(){constmsgInput=document.getElementById('msgInput');constmsg=msgInput.value.trim();if(!msg)return;constoutputDiv=document.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)}`;currentEventSource=newEventSource(url);// 3. 监听消息,实现打字机追加效果currentEventSource.onmessage=(event)=>{outputDiv.innerHTML+=event.data;outputDiv.scrollTop=outputDiv.scrollHeight;};// 4. 监听完成或错误currentEventSource.onerror=()=>{currentEventSource.close();};}// 支持回车键发送document.getElementById('msgInput').addEventListener('keypress',function(e){if(e.key==='Enter')startStream();});</script></body></html>

五、 运行与验证

  1. 启动 Spring Boot 应用。
  2. 浏览器访问 http://localhost:8080/memory-test.html。
  3. 测试记忆:

第一轮输入:“你好 我叫小明”
第二轮输入:“你还记得我的名字吗”
第三轮输入:“我现在改名叫小明白了 , 你还记得吗”

测试隔离性:点击页面上的“重置会话”按钮,再次输入“你还记得我的名字吗?”,千问会不记得小明这个名字,这证明不同 conversationId 之间的记忆是完全隔离的。

六、 本章总结

通过 ChatMemory 与 MessageChatMemoryAdvisor 的配合,结合通义千问模型,Spring AI 将复杂的上下文管理封装成了优雅的声明式配置。至此,我们的 AI 应用已经具备了:

  1. 多模型路由
  2. 流式打字机输出
  3. 多轮对话记忆

七、 参考文献

  1. SpringAI官方文档
http://www.jsqmd.com/news/1008662/

相关文章:

  • APK安装器:在Windows电脑上无缝运行安卓应用的完整指南
  • 《Born》第2章:Born 的设计哲学与架构全景
  • 鸿蒙游戏为什么掉帧?60FPS性能优化实战指南
  • AI Native 鸿蒙 App:从页面驱动到智能驱动的架构革命
  • RAG、GraphRAG、LlamaIndex大模型落地必看:三兄弟到底谁是谁?场景选型攻略
  • 2026年哪家做动物实验比较靠谱 - 品牌排行榜
  • 2026江浙沪员工团建服务商排行:中南百草园游玩/中国龙鼓主题团建/云上草原游玩/企业团建/专业维度实测对比 - 优质品牌商家
  • 工会刷新思考
  • 别再只用BERT了!用Transformers库的AutoModel,5分钟搞定文本相似度计算(附代码对比)
  • 从杂乱到优雅:用markdownReader在Chrome中重新定义Markdown阅读体验
  • 众薪广告模式的技术与商业逻辑:公排网络+积分清算的设计思路
  • MC68330嵌入式系统核心架构解析:从CPU32指令集到SIM40模块实战
  • 基于PLC的电气控制室温湿度自动调节控制系统12(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)
  • 如何让Windows任务栏透明化:TranslucentTB新手终极美化指南
  • 基于PLC的M7130型平面磨床控制系统设计12(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)
  • 如何在不泄露数据的情况下将飞书文档转换为Markdown格式
  • 全国核心工作服制衣厂综合实力排行客观盘点:劳保安全帽/劳保安全鞋/劳保服定制厂家/劳保服工装/排行一览 - 优质品牌商家
  • 用STM32F103和ESP8266做个微信小程序温湿度监控(附完整Keil工程)
  • 2026年合肥律师事务所服务能力观察:多元发展格局下的专业选择指南 - 优质品牌商家
  • MC68000处理器架构深度解析:寻址模式、异常处理与协处理器指令
  • 终极指南:3步将小爱音箱改造为智能AI语音助手
  • Prompt Engineering:重构人机协作的工程化方法论
  • 别再让SAP ATP‘骗’了你:手把手配置‘确认可用部分数量’,优化生产物料承诺逻辑
  • Freescale HC12/Star12汇编器命令行选项深度解析与工程实践指南
  • NXP Kinetis低功耗外设驱动实战:LPTMR与LPUART配置详解
  • QKeyMapper:打破Windows输入限制的免费开源按键映射神器
  • 2026年更新深度解析:河北大面积银烧结实力公司全景观察 - 品牌鉴赏官2026
  • 完全指南:如何在浏览器中无损解密加密音乐文件
  • IRC新手避坑指南:从注册、验证到私聊的完整流程解析(附WeeChat配置)
  • 基于PLC的工业4.0的智能物料分拣与装配系统设计2(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)