我来为你优化这篇关于 Spring AI 对话记忆与工具调用的博客,补充 Redis 缓存方案,并让它更加清晰易懂。
首先让我搜索一些相关的图片素材来丰富博客内容:
现在我来为你呈现优化后的博客内容,图文并茂,结构清晰:
🧠 Spring AI 对话记忆与工具调用完全指南
让大语言模型拥有"记忆力",并学会使用工具
一、为什么需要对话记忆?
1.1 大模型的"失忆症"
大语言模型(LLM)本质上是无状态的,每次请求都是独立的。就像金鱼只有7秒记忆,模型不会记住你上一秒说了什么。

场景对比:
| 无记忆对话 | 有记忆对话 |
|---|---|
| 用户:我叫张三 AI:你好张三! 用户:我叫什么? AI:抱歉,我不知道你的名字。 | 用户:我叫张三 AI:你好张三! 用户:我叫什么? AI:你刚才告诉我你叫张三。 |
二、Spring AI 记忆架构

Spring AI 通过 Advisor 机制 实现记忆功能,核心组件:
┌─────────────────────────────────────────┐
│ ChatClient (客户端) │
├─────────────────────────────────────────┤
│ ┌─────────────────────────────────┐ │
│ │ MessageChatMemoryAdvisor │ │
│ │ (记忆顾问) │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ ┌─────────────────────────────────┐ │
│ │ ChatMemory (接口) │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ ChatMemoryRepository │ │ │
│ │ │ (存储仓库) │ │ │
│ │ └─────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
三、基础实现:内存记忆
3.1 快速开始
使用 MessageWindowChatMemory 实现基于内存的对话记忆:
java
@Autowired
private OpenAiChatModel chatModel;
@Test
void testMemory() {
// 1. 创建记忆容器:最多保留10条消息
ChatMemory memory = MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
// 2. 构建客户端并添加记忆顾问
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(memory))
.build();
// 3. 第一轮对话
String response1 = client.prompt()
.user("我叫张三,今年28岁")
.call()
.content();
System.out.println("AI: " + response1);
// 输出:你好张三,很高兴认识你!
// 4. 第二轮对话(测试记忆)
String response2 = client.prompt()
.user("我今年多大了?")
.call()
.content();
System.out.println("AI: " + response2);
// 输出:你今年28岁。
}
3.2 原理解析
MessageChatMemoryAdvisor 工作流程:
java
// 伪代码展示Advisor的工作流程
public class MessageChatMemoryAdvisor implements CallAroundAdvisor {
@Override
public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
// 1. 从记忆中读取历史消息
List<Message> history = chatMemory.get(conversationId);
// 2. 将历史消息添加到当前请求
request.messages().addAll(0, history);
// 3. 调用模型获取响应
AdvisedResponse response = chain.nextAroundCall(request);
// 4. 将新对话存入记忆
chatMemory.add(conversationId, request.userMessage());
chatMemory.add(conversationId, response.response());
return response;
}
}
四、生产级方案:持久化存储
内存存储在应用重启后会丢失,生产环境需要持久化方案。
4.1 JDBC 数据库存储
4.1.1 添加依赖
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-jdbc-memory</artifactId>
</dependency>
4.1.2 配置数据源
java
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatMemoryRepository chatMemoryRepository(
JdbcTemplate jdbcTemplate) {
return JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new MysqlChatMemoryRepositoryDialect())
.build();
}
@Bean
public ChatMemory chatMemory(ChatMemoryRepository repository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.maxMessages(100) // 保留最近100条
.build();
}
}
4.1.3 数据库表结构
sql
-- MySQL 表结构(自动创建)
CREATE TABLE chat_memory (
conversation_id VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
type VARCHAR(50) NOT NULL, -- USER / ASSISTANT / SYSTEM
timestamp BIGINT NOT NULL,
INDEX idx_conversation (conversation_id, timestamp)
);
4.2 🚀 Redis 缓存存储(高性能方案)
对于高并发场景,Redis 是更好的选择,提供亚毫秒级响应。

4.2.1 为什么选 Redis?
| 特性 | JDBC | Redis |
|---|---|---|
| 读取速度 | ~10ms | ~1ms |
| 支持过期策略 | ❌ | ✅ |
| 分布式共享 | ✅ | ✅ |
| 内存占用 | 低 | 中等 |
| 适用场景 | 中小规模 | 高并发/实时 |
4.2.2 自定义 RedisChatMemory
Spring AI 官方暂未提供 Redis 实现,我们可以自定义:
java
@Component
public class RedisChatMemory implements ChatMemory {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String KEY_PREFIX = "chat:memory:";
private static final long TTL_HOURS = 24; // 24小时过期
@Override
public void add(String conversationId, List<Message> messages) {
String key = KEY_PREFIX + conversationId;
// 使用 Redis List 结构存储消息
for (Message message : messages) {
String json = serializeMessage(message);
redisTemplate.opsForList().rightPush(key, json);
}
// 设置过期时间
redisTemplate.expire(key, TTL_HOURS, TimeUnit.HOURS);
// 保留最近 N 条(滑动窗口)
trimMemory(key, 50);
}
@Override
public List<Message> get(String conversationId, int lastN) {
String key = KEY_PREFIX + conversationId;
// 从 Redis 读取最近 N 条
List<String> jsonList = redisTemplate.opsForList()
.range(key, -lastN, -1);
return jsonList.stream()
.map(this::deserializeMessage)
.collect(Collectors.toList());
}
@Override
public void clear(String conversationId) {
redisTemplate.delete(KEY_PREFIX + conversationId);
}
// 序列化/反序列化方法
private String serializeMessage(Message message) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(message);
} catch (JsonProcessingException e) {
throw new RuntimeException("消息序列化失败", e);
}
}
private Message deserializeMessage(String json) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, Message.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("消息反序列化失败", e);
}
}
private void trimMemory(String key, int maxSize) {
Long size = redisTemplate.opsForList().size(key);
if (size != null && size > maxSize) {
// 保留后 maxSize 条,删除前面的
redisTemplate.opsForList().trim(key, size - maxSize, -1);
}
}
}
4.2.3 Redis 配置类
java
@Configuration
public class RedisChatMemoryConfig {
@Bean
public RedisChatMemory redisChatMemory() {
return new RedisChatMemory();
}
@Bean
public ChatClient chatClient(
OpenAiChatModel chatModel,
RedisChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
}
}