第二学:SpringAi1.0.0整合redis实现上下文记忆(详细小白都会的都会java操作ai)
第二学SpringAi1.0.0整合redis实现上下文记忆在上一篇文章中我们已经知道怎么实现简单对话聊天因为spring-boot-ai 现在体系和版本都还不成熟兼容的版本目前还不够多现在我们导入新的依赖这一步是为了后续兼容更多的工具以及mcp等设定好对应的版本导入对应的版本spring-ai版本以及redis依赖!--spring-ai依赖--dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-openai/artifactId/dependency!--redis依赖--dependencygroupIdorg.redisson/groupIdartifactIdredisson-spring-boot-starter/artifactIdversion3.23.2/version/dependency版本管理propertiesspring-ai.version1.0.0/spring-ai.version/properties!--SpringAIBOM--dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-bom/artifactIdversion${spring-ai.version}/versiontypepom/typescopeimport/scope/dependency这里我们已经导入对应的版本和依赖同样我们按照官方需要创建一个ChatClient 的实列对象这个时候通过官网观察到 springai提供了几种类型上下文管理官网大致分为三部分记忆方式第一种:记忆窗口(Message Window)像手机最近通话记录一样只保留最近的N条对话默认20条第二种存储方式 内存存储默认方式重启程序会丢失适合测试 数据库存储支持JDBC(MySQL/PostgreSQL等)、Cassandra、Neo4j等第三种Advisor记忆顾问 来管理对话上下文目前的方案我们不需要持久化到对应的数据库我们可以通过自动化的管理的方式管理我们自己的记忆所以我们选择第三种方式实现我们的需求packageorg.oppo.chat.ai.config;importcom.fasterxml.jackson.databind.ObjectMapper;importjakarta.annotation.Resource;importlombok.extern.slf4j.Slf4j;importorg.oppo.chat.ai.constant.Constant;importorg.oppo.common.core.utils.RedisCache;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;importorg.springframework.ai.tool.ToolCallbackProvider;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationSlf4jpublicclassSpringAIConfig{ResourceprivateRedisCacheredisCache;// 构建历史记录BeanpublicMessageChatMemoryAdvisormessageHistoryAdvisor(){returnMessageChatMemoryAdvisor.builder(newRedisMemoryChatMemory(redisCache)).build();}BeanpublicChatClientchatClient(ChatClient.Builderbuilder,MessageChatMemoryAdvisormessageHistoryAdvisor){returnbuilder.defaultSystem(Constant.SYSTEM_ROLE).defaultAdvisors(messageHistoryAdvisor).build();}}默认设定publicinterfaceConstant{StringSYSTEM_ROLE #角色 你是全能智能化助手为用户解决任何问题 ;}这里要注意的是默认的ChatMemory 不能满足我们的这里我们可以通过源码观察到所以我们需要从重构ChatMemory这里可以观察到MessageChatMemoryAdvisor 默认构建方式是ChatMemory所以这里我们需要重新实现ChatMemory 这个就可以实现了packageorg.oppo.chat.ai.config;importjakarta.annotation.Resource;importorg.oppo.common.core.constant.RedisKeyConstant;importorg.oppo.common.core.utils.RedisCache;importorg.springframework.ai.chat.memory.ChatMemory;importorg.springframework.ai.chat.messages.Message;importorg.springframework.ai.chat.messages.UserMessage;importorg.springframework.stereotype.Component;importjava.util.*;importjava.util.stream.Collectors;ComponentpublicclassRedisMemoryChatMemoryimplementsChatMemory{privatefinalRedisCacheredisCache;privatefinalStringKEY_PREFIXRedisKeyConstant.CHAT_DEEP_RESEARCH_SPRING_AI_CHAT_MEMORY;publicRedisMemoryChatMemory(RedisCacheredisCache){this.redisCacheredisCache;}Overridepublicvoidadd(StringconversationId,ListMessagemessages){StringredisKeyKEY_PREFIXconversationId;// 遍历消息列表使用时间戳作为Hash的字段名将消息内容作为字段值for(Messagemessage:messages){redisCache.setMapTimeStamp(redisKey,message.toString());}}OverridepublicListMessageget(StringconversationId){StringredisKeyKEY_PREFIXconversationId;// 获取存储在Redis中的所有时间戳-消息键值对MapObject,ObjectallMessagesredisCache.getMapObject(redisKey);if(allMessagesnull||allMessages.isEmpty()){returnList.of();// 如果没有消息返回空列表}// 按时间戳排序时间戳是String类型转换为Long后排序ListMap.EntryObject,ObjectsortedMessagesallMessages.entrySet().stream().sorted((entry1,entry2)-{longtimestamp1Long.parseLong(entry1.getKey().toString());longtimestamp2Long.parseLong(entry2.getKey().toString());returnLong.compare(timestamp2,timestamp1);// 降序排列}).toList();// 获取倒数 N 条消息ListMap.EntryObject,ObjectlastMessagessortedMessages.stream().limit(10).toList();// 将消息转换为 Message 对象并返回returnlastMessages.stream().map(entry-newUserMessage(entry.getValue().toString()))// 假设 UserMessage 是 Message 的实现类.collect(Collectors.toList());}Overridepublicvoidclear(StringconversationId){StringredisKeyKEY_PREFIXconversationId;// 删除该用户会话的所有消息记录redisCache.deleteObject(redisKey);}}这里可以观察到我们实现的方法就是对应的 get add() clear() 三个方法也就是说每次操作都会进行get 放入到我们消息列表内我们就可以操作redis对应的内容这里提供对应redis工具类操作/** * 缓存Map * * param redisKey * param message */publicTvoidsetMapTimeStamp(StringredisKey,Stringmessage){longtimestampSystem.currentTimeMillis();// 使用当前时间戳redisTemplate.opsForHash().put(redisKey,String.valueOf(timestamp),message);}/** * 删除单个对象 * * param key */publicbooleandeleteObject(finalStringkey){returnredisTemplate.delete(key);}/** * 获得缓存的Map * * param key * return */publicTMapT,TgetMapObject(finalStringkey){returnredisTemplate.opsForHash().entries(key);}我们还需要注意的是 这里需要提供conversationId 相当于就是一个单独一个会话id也就是每个会话都是唯一的这个事实可以管理不同的会话所以在service中我们需要传入对应id来保证我们每个会话都是唯一的, 这里的sessionId可以用雪花也可以自定义id用来保证唯一性即可/** * 处理用户问题并返回流式响应内容 * * param question 用户输入的问题内容 * return 包含逐条响应内容和结束标记的响应流每个元素为字符串格式 */OverridepublicFluxStringchatStream(Stringquestion,StringsessionId){// 1. 调用聊天客户端获取流式响应FluxStringresponseStreamthis.chatClient.prompt().user(question).advisors(advisor-advisor.param(CONVERSATION_ID,sessionId)).stream().content();// 2. 添加日志和本地拼接StringBuildercompleteResponsenewStringBuilder();returnresponseStream// 打印每段输出.doOnNext(chunk-{log.info([Stream Chunk] chunk);// 控制台日志completeResponse.append(chunk);// 拼接完整响应})// 结束时打印完整内容.concatWith(Flux.defer(()-{log.info([Complete Response] completeResponse.toString());// 完整日志returnFlux.just(DONE);// 结束标记}))// 错误处理.onErrorResume(e-{log.error([Stream Error] e.getMessage());returnFlux.just([ERROR] e.getMessage());});}service 接口/** * 流式聊天 * * param question 用户提问 * param sessionId 会话id * return 大模型的回答 */FluxStringchatStream(Stringquestion,StringsessionId);访问/** * 处理流式聊天请求返回服务器发送事件SSE格式的响应流 * * param chatDTO 用户输入的聊天问题 * return 包含逐条聊天响应的响应式数据流通过Server-Sent Events协议传输 */PostMapping(valuestream,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchatStream(RequestBodyChatDTOchatDTO){returnchatService.chatStream(chatDTO.getQuestion(),chatDTO.getSessionId());}这样就实现了我们想要的流式接口我们可以观察到消息都在统一个会话中了这样我们实现了redis实现上下文记忆但是目前已经实现上下文方式但是他还不够智能也就是单纯调用大模型实现我们自己的需求我还想要这个大模型能够自主去调用大模型也就是实现智能体能自主调用工具下一篇我将编写自主调用工具简单的智能体

相关新闻

告别手动操作:UI-TARS让Android自动化测试像搭积木一样简单

告别手动操作:UI-TARS让Android自动化测试像搭积木一样简单

告别手动操作:UI-TARS让Android自动化测试像搭积木一样简单 【免费下载链接】UI-TARS Pioneering Automated GUI Interaction with Native Agents 项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS 还在为Android应用的重复测试任务感到烦恼吗&…

2026/10/12 14:24:07 阅读更多 →
终极淘宝数据采集解决方案:TSDK爬虫SDK全面解析

终极淘宝数据采集解决方案:TSDK爬虫SDK全面解析

终极淘宝数据采集解决方案:TSDK爬虫SDK全面解析 【免费下载链接】TSDK 淘宝爬虫SDK,用于淘宝开放平台或淘宝、天猫、阿里巴巴登录爬取 项目地址: https://gitcode.com/gh_mirrors/ts/TSDK TSDK是一款功能强大的淘宝爬虫SDK,专为淘宝开…

2026/10/13 1:10:12 阅读更多 →
20:计算2的幂

20:计算2的幂

/*** 题目名称&#xff1a;计算2的幂 <p>* 题目来源&#xff1a;http://noi.openjudge.cn/ch0103/20/ <p>* 程序功能&#xff1a;根据指定幂数输出2的幂** author 潘磊&#xff0c;just_panleijust.edu.cn* version 1.0*/import java.util.Scanner;public class Ma…

2026/10/10 22:22:30 阅读更多 →

最新新闻

MIPI D-PHY物理层调试:从CTS FAIL定位信号完整性根因

MIPI D-PHY物理层调试:从CTS FAIL定位信号完整性根因

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:58:39 阅读更多 →
Remeda 迁移指南:用纯函数 `differenceWith` 替代 Lodash 的原地可变 `pullAllWith`

Remeda 迁移指南:用纯函数 `differenceWith` 替代 Lodash 的原地可变 `pullAllWith`

开发工具 【免费下载链接】remeda A utility library for JavaScript and TypeScript. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/re/remeda 点击查看 免费下载 导读 本指南针对 Remeda 官方 Lodash 迁移文档中 pullAllWith 条目&#xff0c;讲解如何将这一 Loda…

2026/10/13 5:58:39 阅读更多 →
Jupyter Notebook Exercise 扩展:用可折叠“习题/答案“组织教学 Notebook(实现原理与演进史解析)

Jupyter Notebook Exercise 扩展:用可折叠“习题/答案“组织教学 Notebook(实现原理与演进史解析)

开发工具前端 【免费下载链接】jupyter_contrib_nbextensions A collection of various notebook extensions for Jupyter 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ju/jupyter_contrib_nbextensions 点击查看 免费下载 Exercise&#xff08;及其姊妹版 Exercise…

2026/10/13 5:58:39 阅读更多 →
OCRmyPDF离线部署指南:断网内网一次搞定扫描OCR

OCRmyPDF离线部署指南:断网内网一次搞定扫描OCR

OCRmyPDF离线部署指南&#xff1a;断网内网一次搞定扫描OCR 【免费下载链接】OCRmyPDF OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched 项目地址: https://gitcode.com/GitHub_Trending/oc/OCRmyPDF 矿区笔记本上的那份图纸 地质…

2026/10/13 5:58:39 阅读更多 →
SpringMVC通用数据权限校验框架:从设计到实战

SpringMVC通用数据权限校验框架:从设计到实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:58:39 阅读更多 →
AnyPS5实战:用开源面板统一管理PS5游戏库与远程串流

AnyPS5实战:用开源面板统一管理PS5游戏库与远程串流

AnyPS5 这个名字听起来像是一台主机&#xff0c;其实它是我折腾的一个本地工具项目。起因很简单&#xff1a;我手里的游戏、账号、主机状态越来越散&#xff0c;数字版躺在不同账号里&#xff0c;光盘版挤在书架上&#xff0c;想远程玩一把还得分清哪台机器、哪个账号、哪次打折…

2026/10/13 5:57:39 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目&#xff0c;我最大的感受是&#xff0c;大部分坑不是出在技术前沿&#xff0c;而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节&#xff0c;最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了&#xff0c;很难不碰到舵机控制这道坎。做智能小车要转方向&#xff0c;做机械臂要抬关节&#xff0c;做云台要偏航俯仰&#xff0c;背后都是同一个东西在干活&#xff1a;舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”&#xff1f;——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台&#xff1f;或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜&#xff1f;又或者&#xff0c;在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →