阅读时长:6分钟 | 适合已接入DeepSeek但想做多轮对话的开发者
先说说你大概率遇到过的情况
写了个ChatBot,第一句问“我叫小明”,AI记住了。
第二句问“我叫什么”,AI回答“你叫小明”。完美。
第三句问“我今年18岁”,AI记住了。
第四句问“我多大了”,AI回答……
“抱歉,我没有上下文信息,请告诉我你的年龄。”
我当场裂开。
不是刚跟你说完吗?你转头就忘了?
后来我查了源码才发现,每次调用API都是独立的,默认不带记忆。你用ChatClient.create()发请求,它只认当前这轮对话,前面聊了什么一概不记得。
所以就有了今天这篇:怎么用Spring AI 1.x给AI加上“记忆”功能,让多轮对话真正连贯起来。
第一步:搞清楚Spring AI的记忆机制
Spring AI 1.x提供了ChatMemory接口,专门用来存储对话历史。
它是这么工作的:
| 组件 | 作用 |
|---|---|
ChatMemory | 存储用户的历史消息列表 |
ChatMemoryStore | 持久化接口(可自定义) |
InMemoryChatMemory | 默认实现(存在内存里) |
ChatClient | 调用API时自动从ChatMemory读取历史 |
关键点是:每次调用chatClient.call()之前,Spring AI会自动把之前的历史消息拼到请求里,然后一起发给大模型。这样AI就能“记得”之前说过什么。
默认是内存存储,应用重启就丢数据。生产环境建议换成Redis持久化,但本地测试先用内存版就行。
下面是Spring AI记忆机制的工作流程图:
第二步:写个带记忆的Service
废话不多说,直接上代码。
@Service
public class ChatService {
private final ChatClient chatClient;
private final ChatMemory chatMemory;
public ChatService(ChatClient.Builder chatClientBuilder) {
// 创建内存存储
ChatMemoryStore store = new InMemoryChatMemoryStore();
// 设置记忆窗口:保留最近10条消息
this.chatMemory = new InMemoryChatMemory(store, 10);
this.chatClient = chatClientBuilder
.withChatMemory(chatMemory)
.build();
}
public String chat(String userId, String userMessage) {
// 把用户消息存入记忆
chatMemory.addUserMessage(userId, userMessage);
// 调用API(会自动带上历史消息)
ChatResponse response = chatClient.call(
new Prompt(chatMemory.get(userId))
);
String aiMessage = response.getResult().getOutput().getContent();
// 把AI回复也存入记忆
chatMemory.addAssistantMessage(userId, aiMessage);
return aiMessage;
}
}
关键点注释:
- InMemoryChatMemoryStore:存内存,重启就丢了,适合测试
- 构造方法里的10:保留最近10条历史消息(再多容易超Token上限)
- addUserMessage() / addAssistantMessage():手动把用户和AI的消息加入记忆库
- chatMemory.get(userId):获取该用户的所有历史消息
下面是带记忆的ChatService类结构图:
第三步:写个Controller测试
@RestController
@RequestMapping("/chat")
public class ChatController {
@Autowired
private ChatService chatService;
@PostMapping("/memory")
public Map<String, String> chatWithMemory(@RequestBody Map<String, String> request) {
String userId = request.get("userId");
String message = request.get("message");
String reply = chatService.chat(userId, message);
return Map.of("reply", reply);
}
}
测试请求:
POST /chat/memory
{
"userId": "zhangsan",
"message": "我叫张三,今年18岁"
}
返回:
好的,张三,我记住了你今年18岁。
再问一句:
POST /chat/memory
{
"userId": "zhangsan",
"message": "我多大了?"
}
返回:
你今年18岁。
这次它真的记住了。
下面是多轮对话的时序图:
我踩过的两个坑
坑一:历史消息太长导致超时
记忆窗口设了10条消息,但每条消息本身可能很长(比如你丢了一大段代码进去),10条加起来可能就超Token了。
解决方案:结合max_tokens限制输出长度,同时在存入消息前做截断或摘要。如果消息超过2000字,先让模型做摘要再存。
坑二:忘记存AI的回复
很多人只存了用户消息,忘了存AI的回复。结果下一轮对话时,AI能听到你说过的所有话,但听不到自己说过什么。
结果就是:它知道张三18岁,但不记得自己确认过“张三18岁”这件事。对话依然不连贯。
解决方案:addAssistantMessage()这一步不能漏。
下面是消息存储策略对比图:
进阶玩法:换成Redis持久化
内存存储重启就丢数据,生产环境肯定不行。
Spring AI支持自定义ChatMemoryStore,自己写个Redis实现就行。框架层面的示例代码官方仓库有,按需取用即可。
完整配置(抄作业版)
pom.xml依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- 如果要用Redis持久化 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml:
spring:
ai:
openai:
api-key: ${DEEPSEEK_API_KEY}
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
timeout: 60000
说点实在的
多轮对话的记忆功能,本质是个“消息管理”问题。不是说AI记不住,而是你需要告诉它哪些是历史消息,然后把历史消息和当前问题一起发过去。
Spring AI的ChatMemory把这层封装好了,开发者只需要关注两件事:
- 存储策略:用内存还是Redis?
- 窗口大小:保留多少条历史消息?
这两个定下来,剩下的就是调参和业务逻辑的事了。
明天准备发一篇 CSDN 8月技术热榜复盘,聊聊 Agent 战争、Java 生态、K8s 十周年这些有意思的事。
评论区告诉我:你现在的多轮对话,是存在哪里的?

448

被折叠的 条评论
为什么被折叠?



