spring-ai 【会话日志】和【会话记忆】
- 1. 会话日志
- 2. 会话记忆
- 2.1 没有会话记忆的效果
- 2.1 基于内存的实现
- 2.1.1 修改配置类
- 2.1.2 添加会话ID
- 3.会话历史
- 3.1 保存会话id
- 3.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.123+08:00 DEBUG 29860 --- [spring-ai-demo] [io-9090-exec-10] o.s.a.c.c.advisor.SimpleLoggerAdvisor : request: AdvisedRequest[chatModel=org.springframework.ai.ollama.OllamaChatModel@3218b21c, userText=你是谁, systemText=你的名字叫丽莎,请以丽莎的身份和语气回答问题,同时请一定要将你思考的过程输出出来。, chatOptions=org.springframework.ai.ollama.api.OllamaOptions@6913c33b, media=[], functionNames=[], functionCallbacks=[], messages=[], userParams={}, systemParams={}, advisors=[org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1@417489d8, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$2@4a095086, SimpleLoggerAdvisor, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1@2a47c32a, org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$2@24803812], advisorParams={}, adviseContext={}, toolContext={}] 2026-10-20T21:22:41.978+08: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,并且提供了一个基于内存的默认实现InMemoryChatMemory
publicinterfaceChatMemory{// TODO: consider a non-blocking interface for streaming usagesdefaultvoidadd(StringconversationId,Messagemessage){this.add(conversationId,List.of(message));}voidadd(StringconversationId,List<Message>messages);List<Message>get(StringconversationId,intlastN);voidclear(StringconversationId);}2.1.1 修改配置类
- 定义一个ChatMemory,实现选择基于内存的实现;当然,实现可以自定义。
- 添加一个advisor:MessageChatMemoryAdvisor
@ConfigurationpublicclassCommonConfig{// 定义会话存储的方式,这里使用基于内存的方式@BeanpublicChatMemorychatMemory(){returnnewInMemoryChatMemory();}@BeanpublicChatClientchatClient(OllamaChatModelmodel,ChatMemorychatMemory){returnChatClient.builder(model).defaultSystem("你的名字叫丽莎,请以丽莎的身份和语气回答问题,同时请一定要将你思考的过程输出出来。").defaultAdvisors(newSimpleLoggerAdvisor(),// 会话日志newMessageChatMemoryAdvisor(chatMemory))// 会话记忆.build();}}此时已经有会话记忆的功能了,只是大模型没有区分不同客户端的功能;
如果再打开一个客户端,发现新打开的客户端有上个会话的记忆
2.1.2 添加会话ID
前端在请求时,携带一个会话id
importstaticorg.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;@RestController@RequestMapping("/ai")@RequiredArgsConstructorpublicclassChatController{privatefinalChatClientchatClient;/** * 流式输出 * * @param prompt */@CrossOrigin@RequestMapping(value="/chat",produces="text/html;charset=utf-8")publicFlux<String>chatStream(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 保存会话id
publicinterfaceChatHistoryRepository{/** * 保存会话记录 * @param type: 业务类型 * @param chatId: 会话id */voidsave(Stringtype,StringchatId);/** * 获取会话id列表 * @param type: 业务类型 * @return 会话id列表 */List<String>getChatIdList(Stringtype);}@RepositorypublicclassInMemoryChatHistoryRepositoryimplementsChatHistoryRepository{// key是具体的业务类型// value是业务类型对应的会话记录列表privatefinalMap<String,List<String>>chatHistory=newHashMap<>();@Overridepublicvoidsave(Stringtype,StringchatId){if(!chatHistory.containsKey(type)){chatHistory.put(type,newArrayList<>());}List<String>chatIds=chatHistory.get(type);if(chatIds.contains(chatId)){return;}chatIds.add(chatId);}@OverridepublicList<String>getChatIdList(Stringtype){List<String>chatIds=chatHistory.get(type);returnchatIds==null?newArrayList<>():chatIds;}}每次向大模型发送请求前,保存会话ID
@RestController@RequestMapping("/ai")@RequiredArgsConstructorpublicclassChatController{privatefinalChatClientchatClient;privatefinalChatHistoryRepositorychatHistory;/** * 流式输出 * * @param prompt */@CrossOrigin@PostMapping(value="/chat",produces="text/html;charset=utf-8")publicFlux<String>chatStream(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 查询会话记录列表,会话记录详情
@RestController@RequestMapping("/ai/history")@RequiredArgsConstructorpublicclassChatHistoryController{privatefinalInMemoryChatHistoryRepositorychatHistory;privatefinalChatMemorychatMemory;/** * 查询会话记录列表 * @param type * @return */@CrossOrigin@GetMapping(value="/{type}")publicList<String>getChatList(@PathVariable("type")Stringtype){returnchatHistory.getChatIdList(type);}/** * 查询会话详情 * @param type * @return */@CrossOrigin@GetMapping(value="/{type}/{chatId}")publicList<MessageVO>getChatHistory(@PathVariable("type")Stringtype,@PathVariable("chatId")StringchatId){// 会话详细,spring ai自动按照会话id保存到 chatMemory 了List<Message>messages=chatMemory.get(chatId,Integer.MAX_VALUE);if(CollectionUtils.isEmpty(messages)){returnList.of();}// 处理成前端需要的格式returnmessages.stream().map(MessageVO::new).toList();}}