目前我们已经实现了和 DeepSeek 大模型进行聊天,但是存在一个问题!没有聊天记忆功能!
相关文章:
Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 ------ 实现推理效果
Spring AI:ChatClient实现对话效果
Spring AI:使用 Advisor 组件 - 打印请求大模型出入参日志
大模型具备记忆功能吗?
大模型是 "无状态" 的,每次请求(如 API 调用)都是独立的。模型在生成回复时,只会关注当前输入中包含的上下文信息,不会主动保留之前的对话记录。
如果要让模型 "记住" 之前的对话,需要开发者或用户显式地将历史对话内容附加到当前输入中。例如,在聊天机器人应用中,每次请求需附带完整的对话历史,模型才能基于完整上下文生成连贯回复。
动手实现记忆功能
所以,为了实现记忆功能,需要在调用 DeepSeek 时,主动将之前的历史会话再次发送给它。接下来,我们来看看,在 Spring AI 中,要如何实现记忆功能!
配置 ChatMemory
在项目中的 /config 包下,新增一个 ChatMemoryConfig 配置类:
java
@Configuration
public class ChatMemoryConfig {
/**
* 记忆存储
*/
@Resource
private ChatMemoryRepository chatMemoryRepository;
/**
* 初始化一个 ChatMemory 实例,并注入到 Spring 容器中
* @return
*/
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(50) // 最大消息窗口为 50,默认值为 20
.chatMemoryRepository(chatMemoryRepository) // 记忆存储
.build();
}
}
解释一下上述代码:
- 通过 @Resource 注解注入一个 ChatMemoryRepository 的实例。它用于存储聊天会话,当我们没有自定义外部存储时(如 Cassandra、MySQL),默认情况下,Spring AI 使用的是 InMemoryChatMemoryRepository 实现类,会将消息存储在内存中的 ConcurrentHashMap 中。
- MessageWindowChatMemory:它是具体的 ChatMemory 实现类,使用 "滑动窗口" 策略管理聊天记录。
滑动窗口策略:保留最近的 N 条消息(这里是50条,默认值20),超出数量限制时自动移除旧消息。因为在 AI 对话中,限制上下文长度,可以避免超出模型的 Token 限制。
与 ChatClient 整合
完成 ChatMemory 的初始化工作后,我们需要和 ChatClient 整合一下,依然是使用上小节的 Advisor 组件,来实现调用大模型时,将之前的聊天内容,设置到入参中,代码如下:
java
@Configuration
public class ChatClientConfig {
@Resource
private ChatMemory chatMemory;
/**
* 初始化 ChatClient 客户端
* @param chatModel
* @return
*/
@Bean
public ChatClient chatClient(DeepSeekChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("请你扮演一个智能客服")
.defaultAdvisors(new SimpleLoggerAdvisor(), // 添加 Spring AI 内置的日志记录功能
// new MyLoggerAdvisor(), // 添加自定义的日志打印 Advisor
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
}
重启项目,在第一个问题后,再问相关问题,会发现已经有了记忆功能。会携带上之前的聊天
会话隔离问题
看似完美了,但是依然存在问题,我们在浏览器中,重新打开一个页面,再次请求相关接口, 按道理这是开启了一个 "新对话",它应该忘记了之前的记忆。但是事实上还是被记住了。
如何解决会话隔离的问题呢? 我们可以为每个对话定义一个 ID 唯一标识, 后端在存储聊天内容到 ConcurrentHashMap 中时,可以使用这个 ID 来作为键,从而实现每个对话的隔离:

回到 ChatClientController 控制器中,为 /v2/ai/generateStream 接口,添加参数 chatId, 表示当前对话 ID, 代码如下:
java
@RestController
@RequestMapping("/v2/ai")
public class ChatClientController {
@Resource
private ChatClient chatClient;
// 省略...
/**
* 流式对话
* @param message
* @return
*/
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
@RequestParam(value = "chatId") String chatId) {
// 流式输出
return chatClient.prompt()
.user(message) // 提示词
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId))
.stream()
.content();
}
}
并且在调用 DeepSeek 时,通过 .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId)) 为当前聊天请求绑定一个会话 ID(chatId), 以便实现对话隔离功能。
重启项目进行测试。