聊天记忆的简单实现-ChatMemory实现-aisevice
核心原理:聊天记忆的本质是将历史对话(结构化消息列表)作为上下文,与新问题一起发送给无状态的 LLM,让模型基于完整上下文生成回复;实现方式基础版:手动拼接消息列表,适合学习原理;优化版:封装,适合自定义场景;高级版:使用 LangChain4J 内置ChatMemory,适合快速落地;关键注意点:需控制上下文长度,避免超出模型窗口限制,同时做好异常处理保证稳定性。
一、核心原理深度解析
聊天记忆的本质是弥补大语言模型的无状态特性,核心逻辑可以总结为:
- 模型无状态性:LLM(大语言模型)本身不会保留任何对话信息,每次请求都是独立的,这是需要手动实现记忆的根本原因。
- 上下文传递:将历史对话(用户消息 + AI 回复)结构化后,作为上下文和新问题一起发送给模型,让模型 “看到” 完整对话脉络。
- 结构化消息:LangChain4J 通过
UserMessage/AiMessage等类标准化消息格式,确保模型能正确识别对话角色和顺序。
二、完整实现流程(从基础到优化)
1. 基础实现流程(手动管理记忆)
java
运行
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.output.ChatResponse;
import dev.langchain4j.model.qwen.QwenChatModel;
import java.util.Arrays;
public class BasicChatMemoryDemo {
public static void main(String[] args) {
// 1. 初始化通义千问模型(需配置API Key等信息)
ChatLanguageModel qwenChatModel = QwenChatModel.builder()
.apiKey("your-api-key")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.modelName("qwen-turbo")
.build();
// 2. 第一轮对话:无历史,仅发送当前消息
UserMessage userMessage1 = UserMessage.userMessage("我是环环");
ChatResponse chatResponse1 = qwenChatModel.chat(userMessage1);
AiMessage aiMessage1 = chatResponse1.aiMessage();
System.out.println("AI回复1:" + aiMessage1.text()); // 输出:你好呀环环😊
// 3. 第二轮对话:携带历史消息,实现记忆
UserMessage userMessage2 = UserMessage.userMessage("你知道我是谁吗");
// 关键:将历史消息+新消息组合成列表发送
ChatResponse chatResponse2 = qwenChatModel.chat(Arrays.asList(userMessage1, aiMessage1, userMessage2));
AiMessage aiMessage2 = chatResponse2.aiMessage();
System.out.println("AI回复2:" + aiMessage2.text()); // 输出:你是环环呀~
}
}
关键步骤解释:
- 第一轮仅发送用户消息,模型无上下文,只能基础回应;
- 第二轮将「第一轮用户消息→第一轮 AI 回复→第二轮用户消息」整合成列表发送,模型基于完整上下文识别出 “环环” 这个身份。
2. 优化实现(封装管理类)
基础实现需要手动拼接消息列表,扩展性差,推荐封装ChatHistory和ChatService来简化开发:
java
运行
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.Message;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.output.ChatResponse;
import java.util.ArrayList;
import java.util.List;
// 1. 消息历史管理类:统一管理对话历史
class ChatHistory {
private final List<Message> messages = new ArrayList<>();
// 添加用户消息
public void addUserMessage(String content) {
messages.add(UserMessage.userMessage(content));
}
// 添加AI消息
public void addAiMessage(String content) {
messages.add(AiMessage.aiMessage(content));
}
// 获取完整对话历史
public List<Message> getMessages() {
return messages;
}
// 清空历史
public void clear() {
messages.clear();
}
}
// 2. 对话服务类:封装对话逻辑,对外提供简单接口
class ChatService {
private final ChatLanguageModel model;
private final ChatHistory history = new ChatHistory();
public ChatService(ChatLanguageModel model) {
this.model = model;
}
// 核心对话方法:自动管理历史,返回AI回复
public String chat(String userInput) {
try {
// 1. 添加用户输入到历史
history.addUserMessage(userInput);
// 2. 发送完整历史给模型
ChatResponse response = model.chat(history.getMessages());
AiMessage aiMessage = response.aiMessage();
// 3. 添加AI回复到历史
history.addAiMessage(aiMessage.text());
// 4. 返回回复内容
return aiMessage.text();
} catch (Exception e) {
System.err.println("对话异常:" + e.getMessage());
return "抱歉,暂时无法回复你的问题。";
}
}
// 清空对话历史
public void clearHistory() {
history.clear();
}
}
// 3. 测试优化后的实现
public class OptimizedChatMemoryDemo {
public static void main(String[] args) {
// 初始化模型
ChatLanguageModel qwenChatModel = QwenChatModel.builder()
.apiKey("your-api-key")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.modelName("qwen-turbo")
.build();
// 初始化对话服务
ChatService chatService = new ChatService(qwenChatModel);
// 对话测试:无需手动管理历史,调用接口即可
String response1 = chatService.chat("我是环环");
System.out.println("AI回复1:" + response1); // 你好环环~
String response2 = chatService.chat("你知道我是谁吗");
System.out.println("AI回复2:" + response2); // 你是环环呀😜
// 清空历史后,模型丢失记忆
chatService.clearHistory();
String response3 = chatService.chat("你知道我是谁吗");
System.out.println("AI回复3:" + response3); // 我不知道你是谁哦,可以告诉我~
}
}
优化点解释:
ChatHistory:统一处理消息的添加、获取、清空,避免重复代码;ChatService:封装模型调用和异常处理,对外仅暴露chat()和clearHistory(),调用方无需关注底层逻辑;- 异常处理:避免因网络 / 模型调用失败导致程序崩溃,提升鲁棒性。
3. 高级记忆机制(LangChain4J 内置组件)
LangChain4J 提供了ChatMemory接口,无需手动封装,直接实现自动记忆管理:
java
运行
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.qwen.QwenChatModel;
public class AdvancedChatMemoryDemo {
public static void main(String[] args) {
// 1. 初始化基础模型
ChatLanguageModel qwenChatModel = QwenChatModel.builder()
.apiKey("your-api-key")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.modelName("qwen-turbo")
.build();
// 2. 创建记忆组件:限制最多保留10条消息(避免上下文过长)
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
// 3. 包装模型,赋予记忆能力
ChatLanguageModel modelWithMemory = ChatLanguageModel.withMemory(qwenChatModel, chatMemory);
// 4. 对话:无需手动管理历史,模型自动记忆
String response1 = modelWithMemory.chat("我是环环");
System.out.println("AI回复1:" + response1); // 你好环环~
String response2 = modelWithMemory.chat("你知道我是谁吗");
System.out.println("AI回复2:" + response2); // 你是环环呀😜
}
}
核心优势:
- 自动管理消息列表,无需手动添加 / 拼接;
- 支持
MessageWindowChatMemory限制消息数量,避免超出模型上下文窗口; - 代码极简,适合生产环境快速落地。
三、底层技术细节补充
1. 消息处理全流程
预览
查看代码
用户输入
封装为UserMessage
添加到对话历史列表
序列化消息列表为模型可识别格式
发送到LLM(包含历史+新消息)
模型解析上下文生成回复
解析回复为AiMessage
添加到对话历史
返回回复给用户
flowchart TD
A[用户输入] --> B[封装为UserMessage]
B --> C[添加到对话历史列表]
C --> D[序列化消息列表为模型可识别格式]
D --> E[发送到LLM(包含历史+新消息)]
E --> F[模型解析上下文生成回复]
F --> G[解析回复为AiMessage]
G --> H[添加到对话历史]
H --> I[返回回复给用户]
用户输入
封装为UserMessage
添加到对话历史列表
序列化消息列表为模型可识别格式
发送到LLM(包含历史+新消息)
模型解析上下文生成回复
解析回复为AiMessage
添加到对话历史
返回回复给用户
![]()
豆包
你的 AI 助手,助力每日工作学习
2. 上下文窗口限制注意事项
- 不同模型的上下文窗口不同(如 qwen-turbo 是 8k,qwen-plus 是 32k);
- 消息列表的总 token 数不能超过窗口大小,否则会被截断(优先保留最新消息);
- 手动管理历史时,需自行实现 “旧消息清理” 逻辑;使用
MessageWindowChatMemory可自动限制消息数量。
四、总结
- 核心原理:聊天记忆的本质是将历史对话(结构化消息列表)作为上下文,与新问题一起发送给无状态的 LLM,让模型基于完整上下文生成回复;
- 实现方式:
- 基础版:手动拼接消息列表,适合学习原理;
- 优化版:封装
ChatHistory+ChatService,适合自定义场景; - 高级版:使用 LangChain4J 内置
ChatMemory,适合快速落地;
- 关键注意点:需控制上下文长度,避免超出模型窗口限制,同时做好异常处理保证稳定性。
一、ChatMemory实现聊天记忆的核心原理
ChatMemory是 LangChain4J 为解决 “手动管理对话历史繁琐” 问题设计的标准化记忆组件,其核心原理可总结为:
- 解耦记忆逻辑:将 “对话历史管理” 从业务代码中抽离,封装成独立组件,负责消息的存储、清理、查询;
- 自动上下文构建:
AIService作为桥梁,在每次调用模型时,自动从ChatMemory中读取历史消息,与新消息拼接成完整上下文发送给模型; - 窗口化内存控制:通过
MessageWindowChatMemory实现 “滑动窗口” 机制,只保留最近 N 条消息,避免上下文过长超出模型窗口限制。
简单来说:ChatMemory是 “记忆仓库”,AIService是 “仓库管理员 + 对话调度员”,二者配合让你无需手动拼接消息列表,只需调用简单的chat()方法就能实现带记忆的多轮对话。
二、完整实现流程(附可运行代码)
1. 前置准备
首先确保引入 LangChain4J 相关依赖(以 Maven 为例):
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.34.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qwen</artifactId>
<version>0.34.0</version>
</dependency>
2. 分步实现(从基础到优化)
步骤 1:定义 AIService 接口(核心)
AIService需要一个接口来定义对话方法,这是 LangChain4J 的 “声明式” 设计,框架会自动为接口生成实现类:
java
运行
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
// 定义助手接口,通过注解简化消息处理
interface Assistant {
// 可选:SystemMessage定义模型的行为准则(全局生效)
@SystemMessage("你是一个友好的助手,必须记住用户的姓名和关键信息,回复要亲切")
// UserMessage标记该方法接收用户输入
String chat(@UserMessage String userInput);
}
步骤 2:创建 ChatMemory 实例
使用MessageWindowChatMemory创建带窗口限制的记忆组件:
java
运行
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
// 构建记忆组件:最多保留10条消息,超出则自动删除最早的
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10) // 核心参数:消息窗口大小
.id("user_12345") // 可选:为记忆实例设置唯一ID,区分不同用户
.build();
步骤 3:构建 AIService 并集成模型 + 记忆
将模型、记忆组件注入AIService,框架会自动完成集成:
java
运行
import dev.langchain4j.model.qwen.QwenChatModel;
import dev.langchain4j.service.AiServices;
public class ChatMemoryDemo {
public static void main(String[] args) {
// 1. 初始化通义千问模型(替换为你的API Key)
QwenChatModel qwenChatModel = QwenChatModel.builder()
.apiKey("your-api-key")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.modelName("qwen-turbo")
.temperature(0.7) // 可选:调整回复随机性
.build();
// 2. 创建记忆组件(步骤2的代码)
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.id("user_12345")
.build();
// 3. 构建AIService:集成模型+记忆
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(qwenChatModel) // 绑定模型
.chatMemory(chatMemory) // 绑定记忆
.build();
// 4. 多轮对话测试(核心:无需手动管理历史)
try {
// 第一轮:告知姓名
String answer1 = assistant.chat("我是环环");
System.out.println("AI回复1:" + answer1);
// 预期输出:你好环环!很高兴认识你😊
// 第二轮:验证记忆
String answer2 = assistant.chat("我是谁?");
System.out.println("AI回复2:" + answer2);
// 预期输出:你是环环呀~我记得你的名字😜
// 第三轮:基于记忆的扩展对话
String answer3 = assistant.chat("帮我规划一个环环的专属周末计划");
System.out.println("AI回复3:" + answer3);
// 预期输出:环环的专属周末计划来啦...
} catch (Exception e) {
System.err.println("对话异常:" + e.getMessage());
// 兜底回复
System.out.println("抱歉,我暂时无法回复你的问题,请稍后再试~");
}
}
}
3. 底层流转流程(可视化)
预览
查看代码
LLMLLM (Qwen)ChatMemoryAIServiceUserLLMLLM (Qwen)ChatMemoryAIServiceUser调用chat("我是环环")添加用户消息"我是环环"到记忆获取当前完整历史(仅"我是环环")发送上下文(历史+新消息)返回回复"你好环环!很高兴认识你😊"添加AI回复到记忆返回AI回复调用chat("我是谁?")添加用户消息"我是谁?"到记忆获取完整历史("我是环环"+AI回复+"我是谁?")发送完整上下文返回回复"你是环环呀~我记得你的名字😜"添加AI回复到记忆返回AI回复
sequenceDiagram
participant User
participant AIService
participant ChatMemory
participant LLM (Qwen)
User->>AIService: 调用chat("我是环环")
AIService->>ChatMemory: 添加用户消息"我是环环"到记忆
AIService->>ChatMemory: 获取当前完整历史(仅"我是环环")
AIService->>LLM: 发送上下文(历史+新消息)
LLM->>AIService: 返回回复"你好环环!很高兴认识你😊"
AIService->>ChatMemory: 添加AI回复到记忆
AIService->>User: 返回AI回复
User->>AIService: 调用chat("我是谁?")
AIService->>ChatMemory: 添加用户消息"我是谁?"到记忆
AIService->>ChatMemory: 获取完整历史("我是环环"+AI回复+"我是谁?")
AIService->>LLM: 发送完整上下文
LLM->>AIService: 返回回复"你是环环呀~我记得你的名字😜"
AIService->>ChatMemory: 添加AI回复到记忆
AIService->>User: 返回AI回复
LLMLLM (Qwen)ChatMemoryAIServiceUserLLMLLM (Qwen)ChatMemoryAIServiceUser调用chat("我是环环")添加用户消息"我是环环"到记忆获取当前完整历史(仅"我是环环")发送上下文(历史+新消息)返回回复"你好环环!很高兴认识你😊"添加AI回复到记忆返回AI回复调用chat("我是谁?")添加用户消息"我是谁?"到记忆获取完整历史("我是环环"+AI回复+"我是谁?")发送完整上下文返回回复"你是环环呀~我记得你的名字😜"添加AI回复到记忆返回AI回复
三、关键技术细节解析
1. MessageWindowChatMemory的内部机制
- 数据存储:内部维护一个
Deque(双端队列),新消息从队尾添加,超出maxMessages时从队头删除最早的消息; - 消息类型:自动区分
UserMessage/AiMessage,无需手动封装; - 用户隔离:通过
id参数为不同用户创建独立的记忆实例,避免多用户记忆混淆(核心生产级特性)。
2. AIService的核心能力
- 注解驱动:
@SystemMessage/@UserMessage自动封装消息类型,无需手动创建UserMessage对象; - 自动上下文拼接:每次调用
chat()时,自动从ChatMemory读取历史,拼接成模型可识别的格式; - 异常封装:框架会捕获模型调用的基础异常,你只需外层捕获业务异常即可。
3. 优化配置(生产级建议)
java
运行
// 1. 更精细的ChatMemory配置
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(8) // 根据模型窗口调整(qwen-turbo建议8-10条)
.id("user_" + userId) // 动态生成用户ID,实现多用户隔离
.build();
// 2. AIService增强配置(添加系统指令+超时)
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(qwenChatModel)
.chatMemory(chatMemory)
.systemMessage("你是专业的个人助手,严格记住用户的姓名、偏好,回复简洁友好,不超过50字")
.timeout(30) // 超时时间(秒)
.build();
四、总结
- 核心原理:
ChatMemory是 “自动记忆仓库”,AIService是 “调度员”,二者配合将 “手动拼接历史” 的工作完全封装,只需调用chat()方法即可实现带记忆的多轮对话; - 关键流程:定义接口→创建记忆组件→构建 AIService→调用 chat () 方法,四步完成多轮对话,无需关注底层消息拼接;
- 核心优势:自动管理消息窗口(避免超模型上下文)、多用户隔离、代码极简(相比手动管理减少 80% 冗余代码),是 LangChain4J 推荐的生产级聊天记忆实现方式。
使用 AIService 实现记忆对话智能体
一、核心原理
通过将ChatMemory组件与AIService集成,让智能体自动管理、利用对话历史,实现上下文感知的多轮对话。核心是结合AIService简洁的 API 和ChatMemory的记忆能力,无需手动维护对话历史。
二、核心实现步骤
1. 配置 ChatMemory Bean(记忆管理)
java
运行
@Configuration
public class MemoryChatAssistantConfig {
@Bean
ChatMemory chatMemory() {
// 推荐:扩展配置版(指定ID+消息窗口大小)
return MessageWindowChatMemory.builder()
.maxMessages(10) // 最多保存10条消息,超出则移除最早的
.id("default-chat-memory") // 多会话场景用于区分记忆实例
.build();
// 简洁版(仅指定消息数)
// return MessageWindowChatMemory.withMaxMessages(10);
}
}
关键参数:maxMessages设置消息滑动窗口大小,自动维护最近 N 条对话,避免超出模型上下文限制。
2. 定义 Assistant 接口(AI 服务契约)
java
运行
@AiService(
wiringMode = EXPLICIT, // 显式绑定组件
chatModel = "qwenChatModel" // 指定使用的聊天模型Bean名称
)
public interface Assistant {
/** 基础记忆聊天 */
String chat(String userMessage);
/** 带系统提示的记忆聊天 */
@SystemMessage("你是专业助手,回答详细准确且记住对话历史")
String chatWithSystemPrompt(String userMessage);
/** 生成指定主题/长度的内容(带记忆) */
String generateContent(String topic, int length);
}
注解说明:
@AiService:标记为 AI 服务接口,Spring 自动识别并生成实现类;@SystemMessage:为对话添加固定系统提示,约束 AI 回复风格。
3. 两种使用方式
方式 1:Spring 自动注入(推荐,生产环境)
java
运行
@Autowired
private Assistant assistant;
@Test
public void testAutoInjectAssistant() {
try {
// 多轮对话,自动维护记忆
String answer1 = assistant.chat("我是环环");
String answer2 = assistant.chat("我是谁"); // AI能识别"环环"的身份
System.out.println("回复1:" + answer1);
System.out.println("回复2:" + answer2);
} catch (Exception e) {
System.err.println("对话失败:" + e.getMessage());
}
}
工作原理:Spring 启动时自动检测@AiService接口,发现ChatMemory Bean 后自动集成,注入的实例直接具备记忆能力。
方式 2:手动构建(特殊场景,如动态配置)
java
运行
@Test
public void testManualBuildAssistant() {
// 1. 创建记忆实例
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
// 2. 手动构建AI服务(需注入聊天模型)
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(qwenChatModel) // 注入指定的聊天模型
.chatMemory(chatMemory) // 绑定记忆组件
.build();
// 3. 调用(效果与自动注入一致)
String answer1 = assistant.chat("我是环环");
String answer2 = assistant.chat("我是谁");
}
三、底层技术机制
1. 记忆存储:滑动窗口机制
MessageWindowChatMemory内部通过滑动窗口管理对话历史:
- 新消息追加到窗口末尾;
- 消息数超
maxMessages时,自动移除最早的消息; - 仅保留最近 N 条消息,平衡上下文完整性和模型性能。
2. AIService + ChatMemory 集成流程
预览
查看代码
下一轮对话
用户输入
AIService捕获消息
从ChatMemory读取历史
构建完整上下文
调用AI模型生成回复
捕获模型回复
更新ChatMemory(添加用户+AI消息)
返回回复给用户
flowchart LR
A[用户输入] --> B[AIService捕获消息]
B --> C[从ChatMemory读取历史]
C --> D[构建完整上下文]
D --> E[调用AI模型生成回复]
E --> F[捕获模型回复]
F --> G[更新ChatMemory(添加用户+AI消息)]
G --> H[返回回复给用户]
H -->|下一轮对话| A
下一轮对话
用户输入
AIService捕获消息
从ChatMemory读取历史
构建完整上下文
调用AI模型生成回复
捕获模型回复
更新ChatMemory(添加用户+AI消息
四、代码优化要点
| 优化方向 | 优化前(基础版) | 优化后(生产版) |
|---|---|---|
| 接口能力 | 仅单一场景的chat方法 |
扩展多方法(带系统提示、内容生成) |
| 配置灵活性 | 仅指定消息数 | 增加实例 ID,支持多会话区分 |
| 异常处理 | 无异常捕获 | 增加 try-catch,友好处理调用失败场景 |
五、技术对比与适用场景
| 实现方式 | 代码复杂度 | 维护难度 | 功能丰富度 | 适用场景 |
|---|---|---|---|---|
| 手动管理对话历史 | 高 | 高 | 中 | 简单对话、学习 / 调试 |
| ChatMemory + AIService | 低 | 低 | 高 | 复杂多轮对话、生产环境 |
| 仅 AIService(无记忆) | 低 | 低 | 低 | 单次问答、无上下文需求场景 |
典型应用场景
- 个人助手:记住用户偏好、日程、历史对话;
- 客服系统:关联订单号、历史问题,提供连贯服务;
- 教育辅导:跟踪学习进度、薄弱点,针对性解答;
- 创意协作:保留项目背景、讨论历史,持续优化创意。
六、核心优势
- 开发效率:无需手动编写对话历史管理逻辑,几行代码实现记忆对话;
- 用户体验:上下文感知,对话更自然、个性化;
- 系统性能:滑动窗口自动控内存,仅发送必要历史,提升响应速度。
总结
使用 AIService 实现记忆对话智能体的核心是:
- 配置
ChatMemory定义记忆规则(消息窗口大小); - 用
@AiService定义简洁的服务接口; - 通过 Spring 自动注入 / 手动构建集成记忆能力;
- 直接调用接口即可实现上下文感知的多轮对话。
更多推荐


所有评论(0)