5.Spring-Ai入门案例-对话机器人-【会话日志】【会话记忆】【会话历史】

发布时间:2026/10/26 1:30:53

5.Spring-Ai入门案例-对话机器人-【会话日志】【会话记忆】【会话历史】 spring-ai 【会话日志】和【会话记忆】1. 会话日志2. 会话记忆2.1 没有会话记忆的效果2.1 基于内存的实现2.1.1 修改配置类2.1.2 添加会话ID3.会话历史3.1 保存会话id3.2 查询会话记录列表会话记录详情springAI利用【AOP】提供了会话时的拦截、增强等功能。在发送请求给大模型之前以及把大模型的思考结果返回给客户之前可以做一些日志打印会话日志。还可以把这些提示词、日志等写入数据库会话记忆。1. 会话日志添加defaultAdvisors(new SimpleLoggerAdvisor())ConfigurationpublicclassCommonConfig{BeanpublicChatClientchatClient(OllamaChatModelmodel){returnChatClient.builder(model).defaultSystem(你是一个热心、可爱的智能助手你的名字叫小团团请以小团团的身份和语气回答问题同时请一定要将think的内容输出出来。).defaultAdvisors(newSimpleLoggerAdvisor()).build();}}配置文件中指定日志级别其中org.springframework.ai.chat.client.advisor是SimpleLoggerAdvisor所在的包logging:level:org.springframework.ai.chat.client.advisor:debugcom.example.spring_ai_demo:debug浏览器请求控制台输出可以看到请求被包装成AdvisedRequest。2026-10-20T21:21:30.12308:00 DEBUG 29860 --- [spring-ai-demo] [io-9090-exec-10] o.s.a.c.c.advisor.SimpleLoggerAdvisor : request: AdvisedRequest[chatModelorg.springframework.ai.ollama.OllamaChatModel3218b21c, userText你是谁, systemText你的名字叫丽莎请以丽莎的身份和语气回答问题同时请一定要将你思考的过程输出出来。, chatOptionsorg.springframework.ai.ollama.api.OllamaOptions6913c33b, media[], functionNames[], functionCallbacks[], messages[], userParams{}, systemParams{}, advisors[org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1417489d8, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$24a095086, SimpleLoggerAdvisor, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$12a47c32a, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$224803812], advisorParams{}, adviseContext{}, toolContext{}] 2026-10-20T21:22:41.97808:00 DEBUG 29860 --- [spring-ai-demo] [oundedElastic-2] o.s.a.c.c.advisor.SimpleLoggerAdvisor : response: { result : { metadata : { finishReason : stop, contentFilters : [ ], empty : true }, output : { messageType : ASSISTANT, metadata : { messageType : ASSISTANT }, toolCalls : [ ], media : [ ], text : 您好我是丽莎一个由我设计并开发的虚拟 entity。我的设计基于一系列目标包括提供有用的信息、回答问题以及与用户进行互动。\n\n在技术层面上我是通过先进的自然语言处理模型生成的能够理解和生成人类语言。我对数据进行训练和学习以便能够以人类方式回应您的问题。\n\n希望我的回答对您有所帮助如果还有其他问题请随时咨询我。 } }, metadata : { id : , model : deepseek-r1:7b, rateLimit : { requestsRemaining : 0, requestsReset : 0.0, tokensRemaining : 0, requestsLimit : 0, tokensLimit : 0, tokensReset : 0.0 }, usage : { promptTokens : 32, completionTokens : 314, totalTokens : 346, nativeUsage : { promptTokens : 32, totalTokens : 346, completionTokens : 314 }, generationTokens : 314 }, promptMetadata : [ ], empty : true }, results : [ { metadata : { finishReason : stop, contentFilters : [ ], empty : true }, output : { messageType : ASSISTANT, metadata : { messageType : ASSISTANT }, toolCalls : [ ], media : [ ], text : 您好我是丽莎一个由我设计并开发的虚拟 entity。我的设计基于一系列目标包括提供有用的信息、回答问题以及与用户进行互动。\n\n在技术层面上我是通过先进的自然语言处理模型生成的能够理解和生成人类语言。我对数据进行训练和学习以便能够以人类方式回应您的问题。\n\n希望我的回答对您有所帮助如果还有其他问题请随时咨询我。 } } ] }未知原因没有思考过程springAI调用本地模型的时候没有思考过程即使添加以下内容思考过程也是时有时无不知道如何控制。defaultSystem(你是一个热心、可爱的智能助手你的名字叫小团团请以小团团的身份和语气回答问题同时请一定要将think的内容输出出来。)2. 会话记忆大模型不具备记忆能力要想让大模型记住之前聊天的内容唯一的办法就是把之前聊天的内容与新的提示词一起发送给大模型。给大模型发送的message消息中除了user和system还可以发送assistant是有大模型生成的消息可能是上一轮对话生成的结果。2.1 没有会话记忆的效果可以看到两次提问没有任何关联2.1 基于内存的实现Spring AI定义了会话存储的标准接口ChatMemory并且提供了一个基于内存的默认实现InMemoryChatMemorypublicinterfaceChatMemory{// TODO: consider a non-blocking interface for streaming usagesdefaultvoidadd(StringconversationId,Messagemessage){this.add(conversationId,List.of(message));}voidadd(StringconversationId,ListMessagemessages);ListMessageget(StringconversationId,intlastN);voidclear(StringconversationId);}2.1.1 修改配置类定义一个ChatMemory实现选择基于内存的实现当然实现可以自定义。添加一个advisorMessageChatMemoryAdvisorConfigurationpublicclassCommonConfig{// 定义会话存储的方式这里使用基于内存的方式BeanpublicChatMemorychatMemory(){returnnewInMemoryChatMemory();}BeanpublicChatClientchatClient(OllamaChatModelmodel,ChatMemorychatMemory){returnChatClient.builder(model).defaultSystem(你的名字叫丽莎请以丽莎的身份和语气回答问题同时请一定要将你思考的过程输出出来。).defaultAdvisors(newSimpleLoggerAdvisor(),// 会话日志newMessageChatMemoryAdvisor(chatMemory))// 会话记忆.build();}}此时已经有会话记忆的功能了只是大模型没有区分不同客户端的功能如果再打开一个客户端发现新打开的客户端有上个会话的记忆2.1.2 添加会话ID前端在请求时携带一个会话idimportstaticorg.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;RestControllerRequestMapping(/ai)RequiredArgsConstructorpublicclassChatController{privatefinalChatClientchatClient;/** * 流式输出 * * param prompt */CrossOriginRequestMapping(value/chat,producestext/html;charsetutf-8)publicFluxStringchatStream(Stringprompt,StringchatId){returnchatClient.prompt().user(prompt).advisors(a-a.param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId)).stream().content();}}添加会话ID之后就可以区分不同的会话新开一个会话没有上一个会话的记忆3.会话历史在前面的基础上如果想查看会话历史发现已经没有了因为目前还没有将历史的会话保存起来要实现会话历史需实现两个关键接口查询会话记录列表查询会话记录详情以deepseek为例会话记录详情spring ai已经自动保存到chatMemory中了但是会话记录列表没有保存。3.1 保存会话idpublicinterfaceChatHistoryRepository{/** * 保存会话记录 * param type: 业务类型 * param chatId: 会话id */voidsave(Stringtype,StringchatId);/** * 获取会话id列表 * param type: 业务类型 * return 会话id列表 */ListStringgetChatIdList(Stringtype);}RepositorypublicclassInMemoryChatHistoryRepositoryimplementsChatHistoryRepository{// key是具体的业务类型// value是业务类型对应的会话记录列表privatefinalMapString,ListStringchatHistorynewHashMap();Overridepublicvoidsave(Stringtype,StringchatId){if(!chatHistory.containsKey(type)){chatHistory.put(type,newArrayList());}ListStringchatIdschatHistory.get(type);if(chatIds.contains(chatId)){return;}chatIds.add(chatId);}OverridepublicListStringgetChatIdList(Stringtype){ListStringchatIdschatHistory.get(type);returnchatIdsnull?newArrayList():chatIds;}}每次向大模型发送请求前保存会话IDRestControllerRequestMapping(/ai)RequiredArgsConstructorpublicclassChatController{privatefinalChatClientchatClient;privatefinalChatHistoryRepositorychatHistory;/** * 流式输出 * * param prompt */CrossOriginPostMapping(value/chat,producestext/html;charsetutf-8)publicFluxStringchatStream(Stringprompt,StringchatId){// 1. 保存会话// 这里的key是业务类型【对话机器人】chatHistory.save(chat,chatId);returnchatClient.prompt().user(prompt).advisors(a-a.param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId)).stream().content();}}3.2 查询会话记录列表会话记录详情RestControllerRequestMapping(/ai/history)RequiredArgsConstructorpublicclassChatHistoryController{privatefinalInMemoryChatHistoryRepositorychatHistory;privatefinalChatMemorychatMemory;/** * 查询会话记录列表 * param type * return */CrossOriginGetMapping(value/{type})publicListStringgetChatList(PathVariable(type)Stringtype){returnchatHistory.getChatIdList(type);}/** * 查询会话详情 * param type * return */CrossOriginGetMapping(value/{type}/{chatId})publicListMessageVOgetChatHistory(PathVariable(type)Stringtype,PathVariable(chatId)StringchatId){// 会话详细spring ai自动按照会话id保存到 chatMemory 了ListMessagemessageschatMemory.get(chatId,Integer.MAX_VALUE);if(CollectionUtils.isEmpty(messages)){returnList.of();}// 处理成前端需要的格式returnmessages.stream().map(MessageVO::new).toList();}}
延伸阅读

更多相关文章

2026/10/26 1:30:53

Highcharts甘特图开发带导航功能的项目管理图示例

Highcharts Gantt 的项目管理图通常把任务放在纵轴、时间放在横轴,并可用依赖关系表达任务先后。要加入时间导航,常用三种控件: 时间范围按钮:快速切换一周、一个月或整个项目。Navigator:在概览区域拖动选择要查看的…

2026/10/26 1:30:53

C++类和对象【下】

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

2026/10/26 2:40:57

UNet图像分割实战:PyTorch实现、训练链路与损失函数选型

先说个我自己的经历:去年帮人做一个细胞显微图像的分割练手项目,需求很朴素——把视野里每一张细胞膜轮廓都描出来,输出一张和原图同尺寸的二值掩膜。我第一反应就是 UNet,从拉代码到跑出第一版能看的结果,前后不到十分…

2026/10/26 2:40:57

海康威视SDK V6.1.6.45 Windows 64位部署全链路指南

简介:本资源是海康威视官方设备网络SDK的Windows 64位开发套件(V6.1.6.45,2021年3月2日构建),专为嵌入式软件开发者、安防系统集成工程师及智能视频平台二次开发人员设计,用于实现对海康IPC、NVR等硬件设备…

2026/10/26 2:40:57

WPF TreeView数据绑定、虚拟化、懒加载与勾选联动实战

TreeView 这个控件,在 WPF 里属于那种“入门三天、精通三年”的角色。几乎每个桌面项目都躲不开它:设备树、组织架构、文件目录、权限菜单、分类目录、工艺流程节点……只要数据有层级,第一反应就是往 TreeView 上套。但真把它塞进生产环境&a…

2026/10/26 2:40:57

WPF TreeView树形控件实战:数据绑定、虚拟化与交互避坑指南

做 WPF 桌面端的人,大概都绕不开 TreeView 这个控件。它出现在几乎每一个后台管理工具、配置编辑器、文件浏览器、组织架构图、权限配置页里,看上去只是"能展开的列表",但真正动手做第一个带层级的业务树时,十有八九会在半天之内撞上这么几件事:节点点了没反应、展开…

2026/10/26 2:40:57

Web端PDF预览技术解析与pdf.js实战优化

1. 项目概述:Web平台PDF预览的核心需求在Web开发中实现PDF文件预览是个看似简单却暗藏玄机的需求。我最近接手的一个企业文档管理系统项目就遇到了这个经典问题——客户要求在H5页面无缝预览各种规格的PDF文档,还要支持标注、缩放等基础功能。这个需求背…

2026/10/26 2:35:57

scikit-learn入门:传统机器学习实战与工业部署

1. 为什么选择scikit-learn开启AI之旅在机器学习工具百花齐放的今天,scikit-learn始终保持着不可替代的地位。这个基于Python的开源库自2007年诞生以来,已经成为全球数据科学家的标准工具箱。我仍记得第一次用三行代码完成线性回归时的震撼——原来机器学…

2026/10/26 0:20:20

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/26 1:53:47

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/26 0:20:17

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/26 0:00:48

Nested Learning:解决AI灾难性遗忘的分层记忆架构

1. 为什么AI需要"记忆"能力?在传统深度学习模型中,我们常遇到一个根本性问题:模型在处理新任务时,往往会完全遗忘之前学到的知识。这种现象被业界称为"灾难性遗忘"(Catastrophic Forgetting)。想象一下&#…

2026/10/26 0:00:48

企业级大模型应用:OCR预处理、Dify编排与DeepSeek落地实战

1. 这不是“用大模型”,而是重构工作流的起点“大模型的应用和工具”——这八个字现在被贴在无数技术分享、招聘JD、内部立项书的标题栏里,但绝大多数人点进去后看到的,是零散的API调用示例、几个开源UI截图、或者一句“接入LLM就能降本增效”…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑