Langchain4J 实战教程(六):Chat Memory 对话记忆
2025-06-03·4 分钟阅读
Langchain4J 实战教程(六):Chat Memory 对话记忆
前言
对话记忆(Chat Memory)是构建智能对话系统的关键组件。它让 AI 能够"记住"之前的对话内容,实现连贯的多轮对话。本章将深入探索 Langchain4J 的 Chat Memory 机制,掌握各种记忆策略的实现与应用。
Chat Memory 概述
为什么需要对话记忆?
┌─────────────────────────────────────────────────────────────────┐
│ 对话记忆的重要性 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 无记忆对话: │
│ ────────── │
│ 用户:我叫张三 │
│ AI:你好,张三!有什么可以帮你的? │
│ │
│ 用户:我叫什么名字? │
│ AI:抱歉,我不知道你的名字。 ← 无法记住之前的信息 │
│ │
│ 有记忆对话: │
│ ────────── │
│ 用户:我叫张三 │
│ AI:你好,张三!有什么可以帮你的? │
│ │
│ 用户:我叫什么名字? │
│ AI:你叫张三。 ← 记住了之前的对话 │
│ │
└─────────────────────────────────────────────────────────────────┘
Chat Memory 架构
┌─────────────────────────────────────────────────────────────────┐
│ Chat Memory 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户消息 ──→ AI Services ──→ Chat Memory ──→ 构建完整提示词 │
│ │ │ │
│ │ ├── 消息存储 │
│ │ ├── 驱逐策略 │
│ │ └── 持久化 │
│ │ │
│ ▼ │
│ LLM 调用 │
│ │ │
│ ▼ │
│ AI 响应 ──→ 存入 Chat Memory │
│ │
└─────────────────────────────────────────────────────────────────┘
核心接口
ChatMemory 接口
public interface ChatMemory {
// 添加消息
void add(ChatMessage message);
// 获取所有消息
List<ChatMessage> messages();
// 清空记忆
void clear();
// 获取记忆 ID
Object id();
}
消息类型
// 系统消息 - 定义 AI 角色
SystemMessage system = SystemMessage.from("你是一个助手");
// 用户消息 - 用户的输入
UserMessage user = UserMessage.from("你好");
// AI 消息 - AI 的响应
AiMessage ai = AiMessage.from("你好!有什么可以帮你的?");
// 工具执行消息 - 工具调用结果
ToolExecutionResultMessage toolResult = ToolExecutionResultMessage.from(
toolExecutionId,
"tool-name",
"result"
);
记忆策略
1. 消息窗口记忆(MessageWindowChatMemory)
保留最近 N 条消息,适合大多数场景:
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
// 保留最近 10 条消息
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
// 使用
memory.add(SystemMessage.from("你是一个助手"));
memory.add(UserMessage.from("你好"));
memory.add(AiMessage.from("你好!"));
List<ChatMessage> messages = memory.messages(); // 获取所有消息
2. Token 窗口记忆(TokenWindowChatMemory)
保留最近 N 个 Token,精确控制上下文大小:
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
// 保留最近 4000 个 Token
ChatMemory memory = TokenWindowChatMemory.withMaxTokens(4000);
// 使用自定义的 Token 计数器
ChatMemory memory = TokenWindowChatMemory.builder()
.maxTokens(4000)
.tokenCountEstimator(tokenizer) // 自定义 Token 计算器
.build();
3. 混合策略
// 组合多种策略
public class HybridChatMemory implements ChatMemory {
private final int maxMessages;
private final int maxTokens;
private final List<ChatMessage> messages = new ArrayList<>();
@Override
public void add(ChatMessage message) {
messages.add(message);
// 先按消息数量裁剪
while (messages.size() > maxMessages) {
messages.remove(0);
}
// 再按 Token 数量裁剪
while (estimateTokens() > maxTokens) {
messages.remove(0);
}
}
private int estimateTokens() {
// 估算 Token 数量
return messages.stream()
.mapToInt(m -> m.text().length() / 4) // 粗略估算
.sum();
}
}
AI Services 集成
单一会话记忆
interface Assistant {
String chat(String message);
}
// 单一会话,所有用户共享记忆
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
多会话记忆
interface MultiSessionAssistant {
String chat(@MemoryId String sessionId, String message);
}
// 每个会话独立记忆
MultiSessionAssistant assistant = AiServices.builder(MultiSessionAssistant.class)
.chatLanguageModel(chatModel)
.chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(10))
.build();
// 使用 - 不同会话有独立的记忆
assistant.chat("session-1", "我叫张三");
assistant.chat("session-2", "我叫李四");
assistant.chat("session-1", "我叫什么?"); // AI 知道是张三
assistant.chat("session-2", "我叫什么?"); // AI 知道是李四
自定义记忆提供者
public class CustomChatMemoryProvider implements ChatMemoryProvider {
private final ChatMemoryStore store;
private final int maxMessages;
@Override
public ChatMemory get(Object memoryId) {
// 从存储加载历史消息
List<ChatMessage> history = store.getMessages(memoryId);
ChatMemory memory = MessageWindowChatMemory.builder()
.maxMessages(maxMessages)
.id(memoryId)
.build();
// 恢复历史
history.forEach(memory::add);
return memory;
}
}
// 使用
MultiSessionAssistant assistant = AiServices.builder(MultiSessionAssistant.class)
.chatLanguageModel(chatModel)
.chatMemoryProvider(new CustomChatMemoryProvider(store, 20))
.build();
持久化存储
内存存储
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;
ChatMemoryStore store = new InMemoryChatMemoryStore();
数据库存储
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
public class JpaChatMemoryStore implements ChatMemoryStore {
private final ChatMessageRepository repository;
@Override
public List<ChatMessage> getMessages(Object memoryId) {
List<ChatMessageEntity> entities = repository.findBySessionIdOrderByCreatedAt(
memoryId.toString()
);
return entities.stream()
.map(this::toChatMessage)
.collect(Collectors.toList());
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
// 删除旧消息
repository.deleteBySessionId(memoryId.toString());
// 保存新消息
List<ChatMessageEntity> entities = messages.stream()
.map(msg -> toEntity(memoryId.toString(), msg))
.collect(Collectors.toList());
repository.saveAll(entities);
}
@Override
public void deleteMessages(Object memoryId) {
repository.deleteBySessionId(memoryId.toString());
}
private ChatMessage toChatMessage(ChatMessageEntity entity) {
return switch (entity.getType()) {
case "SYSTEM" -> SystemMessage.from(entity.getContent());
case "USER" -> UserMessage.from(entity.getContent());
case "AI" -> AiMessage.from(entity.getContent());
default -> throw new IllegalArgumentException("Unknown type: " + entity.getType());
};
}
private ChatMessageEntity toEntity(String sessionId, ChatMessage message) {
ChatMessageEntity entity = new ChatMessageEntity();
entity.setSessionId(sessionId);
entity.setType(message.type().name());
entity.setContent(message.text());
entity.setCreatedAt(LocalDateTime.now());
return entity;
}
}
Redis 存储
public class RedisChatMemoryStore implements ChatMemoryStore {
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private final Duration ttl;
public RedisChatMemoryStore(RedisTemplate<String, String> redisTemplate,
Duration ttl) {
this.redisTemplate = redisTemplate;
this.objectMapper = new ObjectMapper();
this.ttl = ttl;
}
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String key = getKey(memoryId);
String json = redisTemplate.opsForValue().get(key);
if (json == null) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(json,
new TypeReference<List<ChatMessageDto>>() {})
.stream()
.map(this::toChatMessage)
.collect(Collectors.toList());
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to deserialize messages", e);
}
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String key = getKey(memoryId);
List<ChatMessageDto> dtos = messages.stream()
.map(this::toDto)
.collect(Collectors.toList());
try {
String json = objectMapper.writeValueAsString(dtos);
redisTemplate.opsForValue().set(key, json, ttl);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize messages", e);
}
}
@Override
public void deleteMessages(Object memoryId) {
redisTemplate.delete(getKey(memoryId));
}
private String getKey(Object memoryId) {
return "chat:memory:" + memoryId;
}
}
记忆优化技巧
1. 智能摘要
public class SummarizingChatMemory implements ChatMemory {
private final ChatLanguageModel summaryModel;
private final ChatMemory delegate;
private final int summaryThreshold;
private String summary = "";
@Override
public void add(ChatMessage message) {
delegate.add(message);
// 当消息数量超过阈值时,进行摘要
if (delegate.messages().size() > summaryThreshold) {
summarizeOldMessages();
}
}
private void summarizeOldMessages() {
List<ChatMessage> oldMessages = delegate.messages()
.subList(0, delegate.messages().size() - summaryThreshold / 2);
String summaryPrompt = """
请总结以下对话内容,保留关键信息:
%s
当前进度:%s
""".formatted(
oldMessages.stream().map(ChatMessage::text).collect(Collectors.joining("\n")),
summary
);
summary = summaryModel.generate(summaryPrompt);
// 清空旧消息,添加摘要作为系统消息
delegate.clear();
delegate.add(SystemMessage.from("之前对话摘要:" + summary));
}
}
2. 关键信息提取
public class KeyInfoExtractor {
interface InfoExtractor {
@UserMessage("""
从以下对话中提取关键信息(如姓名、偏好、上下文):
{{conversation}}
以 JSON 格式返回。
""")
KeyInfo extract(@V("conversation") String conversation);
}
record KeyInfo(
String userName,
List<String> preferences,
Map<String, String> context
) {}
private final InfoExtractor extractor;
public KeyInfo extractKeyInfo(List<ChatMessage> messages) {
String conversation = messages.stream()
.map(m -> m.type() + ": " + m.text())
.collect(Collectors.joining("\n"));
return extractor.extract(conversation);
}
}
3. 滑动窗口 + 重要消息保留
public class SmartChatMemory implements ChatMemory {
private final int maxMessages;
private final List<ChatMessage> messages = new ArrayList<>();
private final List<ChatMessage> importantMessages = new ArrayList<>();
@Override
public void add(ChatMessage message) {
// 检查是否是重要消息(包含关键信息)
if (isImportant(message)) {
importantMessages.add(message);
}
messages.add(message);
// 裁剪,但保留重要消息
while (messages.size() > maxMessages) {
ChatMessage toRemove = messages.get(0);
if (!importantMessages.contains(toRemove)) {
messages.remove(0);
} else {
// 重要消息不删除,跳过
break;
}
}
}
private boolean isImportant(ChatMessage message) {
String text = message.text().toLowerCase();
// 检查是否包含关键信息
return text.contains("我叫") ||
text.contains("我的") ||
text.contains("记住") ||
text.contains("偏好");
}
}
完整示例
带持久化的多会话聊天
// 1. 定义接口
interface PersistentAssistant {
@SystemMessage("你是一个友好的助手,能够记住用户的偏好和历史对话。")
String chat(@MemoryId String userId, @UserMessage String message);
}
// 2. 配置
@Configuration
public class ChatConfig {
@Bean
public ChatMemoryStore chatMemoryStore(RedisTemplate<String, String> redisTemplate) {
return new RedisChatMemoryStore(redisTemplate, Duration.ofDays(7));
}
@Bean
public PersistentAssistant assistant(
ChatLanguageModel chatModel,
ChatMemoryStore chatMemoryStore) {
return AiServices.builder(PersistentAssistant.class)
.chatLanguageModel(chatModel)
.chatMemoryProvider(memoryId -> {
ChatMemory memory = MessageWindowChatMemory.builder()
.maxMessages(20)
.id(memoryId)
.chatMemoryStore(chatMemoryStore)
.build();
return memory;
})
.build();
}
}
// 3. 控制器
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final PersistentAssistant assistant;
@PostMapping
public ChatResponse chat(@RequestBody ChatRequest request,
@RequestHeader("X-User-Id") String userId) {
String response = assistant.chat(userId, request.message());
return new ChatResponse(response);
}
@DeleteMapping("/memory")
public void clearMemory(@RequestHeader("X-User-Id") String userId,
ChatMemoryStore store) {
store.deleteMessages(userId);
}
public record ChatRequest(String message) {}
public record ChatResponse(String response) {}
}
记忆管理 API
@RestController
@RequestMapping("/api/memory")
public class MemoryController {
private final ChatMemoryStore chatMemoryStore;
@GetMapping("/{sessionId}")
public List<MessageDto> getMemory(@PathVariable String sessionId) {
List<ChatMessage> messages = chatMemoryStore.getMessages(sessionId);
return messages.stream()
.map(this::toDto)
.collect(Collectors.toList());
}
@DeleteMapping("/{sessionId}")
public void clearMemory(@PathVariable String sessionId) {
chatMemoryStore.deleteMessages(sessionId);
}
@GetMapping("/{sessionId}/export")
public String exportMemory(@PathVariable String sessionId) {
List<ChatMessage> messages = chatMemoryStore.getMessages(sessionId);
return messages.stream()
.map(m -> "[%s] %s".formatted(m.type(), m.text()))
.collect(Collectors.joining("\n\n"));
}
private MessageDto toDto(ChatMessage message) {
return new MessageDto(
message.type().name(),
message.text(),
LocalDateTime.now()
);
}
record MessageDto(String type, String content, LocalDateTime timestamp) {}
}
小结
本章我们学习了:
- Chat Memory 概念:对话记忆的重要性和架构
- 核心接口:ChatMemory、消息类型
- 记忆策略:消息窗口、Token 窗口、混合策略
- AI Services 集成:单一会话、多会话、自定义提供者
- 持久化存储:内存、数据库、Redis
- 优化技巧:智能摘要、关键信息提取、重要消息保留
练习
- 实现一个基于 Redis 的持久化聊天记忆
- 创建一个带摘要功能的长期记忆系统
- 构建一个支持记忆管理的 REST API
参考资料
下一章预告
在下一章《RAG 检索增强生成》中,我们将深入探索:
- RAG 核心原理
- 文档加载与处理
- 向量嵌入与存储
- 相似度检索
- 完整 RAG 系统实现
敬请期待!
教程系列持续更新中,欢迎关注!
相关文章
Langchain4J 实战教程(九):可观测性与生产部署
2025-07-06·4 分钟阅读
深入掌握 Langchain4J 应用的可观测性建设、性能优化、安全实践及生产环境部署策略,构建企业级 AI 应用。
Langchain4J 实战教程(八):Tools 与 Agent 开发
2025-06-23·4 分钟阅读
深入掌握 Function Calling 和 Agent 开发的核心技术,学习工具定义、多工具协作及复杂 Agent 系统的设计与实现。
Langchain4J 实战教程(七):RAG 检索增强生成
2025-06-11·4 分钟阅读
深入掌握 RAG 检索增强生成的核心技术,学习文档处理、向量嵌入、相似度检索及完整 RAG 系统的实现方法。