5.Spring-Ai入门案例-对话机器人-【会话日志】【会话记忆】【会话历史】
spring-ai 【会话日志】和【会话记忆】
springAI利用【AOP】提供了会话时的拦截、增强等功能。
在发送请求给大模型之前,以及把大模型的思考结果返回给客户之前,可以做一些日志打印(会话日志)。还可以把这些提示词、日志等写入数据库(会话记忆)。
1. 会话日志
添加 defaultAdvisors(new SimpleLoggerAdvisor())
@Configuration
public class CommonConfig {
@Bean
public ChatClient chatClient(OllamaChatModel model){
return ChatClient
.builder(model)
.defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小团团,请以小团团的身份和语气回答问题,同时请一定要将<think>的内容输出出来。")
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
}
配置文件中,指定日志级别
其中 org.springframework.ai.chat.client.advisor是 SimpleLoggerAdvisor所在的包;
logging:
level:
org.springframework.ai.chat.client.advisor: debug
com.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
public interface ChatMemory {
// TODO: consider a non-blocking interface for streaming usages
default void add(String conversationId, Message message) {
this.add(conversationId, List.of(message));
}
void add(String conversationId, List<Message> messages);
List<Message> get(String conversationId, int lastN);
void clear(String conversationId);
}
2.1.1 修改配置类
- 定义一个ChatMemory,实现选择基于内存的实现;当然,实现可以自定义。
- 添加一个advisor:MessageChatMemoryAdvisor
@Configuration
public class CommonConfig {
// 定义会话存储的方式,这里使用基于内存的方式
@Bean
public ChatMemory chatMemory(){
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory){
return ChatClient
.builder(model)
.defaultSystem("你的名字叫丽莎,请以丽莎的身份和语气回答问题,同时请一定要将你思考的过程输出出来。")
.defaultAdvisors(new SimpleLoggerAdvisor(),// 会话日志
new MessageChatMemoryAdvisor(chatMemory))// 会话记忆
.build();
}
}
此时已经有会话记忆的功能了,只是大模型没有区分不同客户端的功能;

如果再打开一个客户端,发现新打开的客户端有上个会话的记忆

2.1.2 添加会话ID
前端在请求时,携带一个会话id

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
@RestController
@RequestMapping("/ai")
@RequiredArgsConstructor
public class ChatController {
private final ChatClient chatClient;
/**
* 流式输出
*
* @param prompt
*/
@CrossOrigin
@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chatStream(String prompt, String chatId) {
return chatClient.prompt()
.user(prompt)
.advisors(a ->a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
.stream()
.content();
}
}
添加会话ID之后,就可以区分不同的会话

新开一个会话,没有上一个会话的记忆:

3.会话历史
在前面的基础上,如果想查看会话历史,发现已经没有了,因为目前还没有将历史的会话保存起来

要实现会话历史,需实现两个关键接口
- 查询会话记录列表
- 查询会话记录详情
以deepseek为例:

会话记录详情,spring ai已经自动保存到chatMemory中了,但是会话记录列表没有保存。
3.1 保存会话id
public interface ChatHistoryRepository {
/**
* 保存会话记录
* @param type: 业务类型
* @param chatId: 会话id
*/
void save(String type, String chatId);
/**
* 获取会话id列表
* @param type: 业务类型
* @return 会话id列表
*/
List<String> getChatIdList(String type);
}
@Repository
public class InMemoryChatHistoryRepository implements ChatHistoryRepository {
// key是具体的业务类型
// value是业务类型对应的会话记录列表
private final Map<String, List<String>> chatHistory = new HashMap<>();
@Override
public void save(String type, String chatId) {
if (!chatHistory.containsKey(type)) {
chatHistory.put(type, new ArrayList<>());
}
List<String> chatIds = chatHistory.get(type);
if (chatIds.contains(chatId)) {
return;
}
chatIds.add(chatId);
}
@Override
public List<String> getChatIdList(String type) {
List<String> chatIds = chatHistory.get(type);
return chatIds == null ? new ArrayList<>() : chatIds;
}
}
每次向大模型发送请求前,保存会话ID
@RestController
@RequestMapping("/ai")
@RequiredArgsConstructor
public class ChatController {
private final ChatClient chatClient;
private final ChatHistoryRepository chatHistory;
/**
* 流式输出
*
* @param prompt
*/
@CrossOrigin
@PostMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chatStream(String prompt, String chatId) {
// 1. 保存会话
// 这里的key是业务类型,【对话机器人】
chatHistory.save("chat",chatId);
return chatClient.prompt()
.user(prompt)
.advisors(a ->a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
.stream()
.content();
}
}
3.2 查询会话记录列表,会话记录详情
@RestController
@RequestMapping("/ai/history")
@RequiredArgsConstructor
public class ChatHistoryController {
private final InMemoryChatHistoryRepository chatHistory;
private final ChatMemory chatMemory;
/**
* 查询会话记录列表
* @param type
* @return
*/
@CrossOrigin
@GetMapping(value = "/{type}")
public List<String> getChatList(@PathVariable("type") String type) {
return chatHistory.getChatIdList(type);
}
/**
* 查询会话详情
* @param type
* @return
*/
@CrossOrigin
@GetMapping(value = "/{type}/{chatId}")
public List<MessageVO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {
// 会话详细,spring ai自动按照会话id保存到 chatMemory 了
List<Message> messages = chatMemory.get(chatId, Integer.MAX_VALUE);
if (CollectionUtils.isEmpty(messages)){
return List.of();
}
// 处理成前端需要的格式
return messages.stream().map(MessageVO::new).toList();
}
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)