上下文记忆管理
大约 2 分钟
第八章:对话记忆与上下文控制:别让历史无限膨胀
8.1 记忆不是“越多越好”
历史对话越多,常见副作用越明显:
- 成本上升:token 越来越多
- 稳定性下降:模型更容易被历史细节带偏
- 延迟变差:请求更大更慢
所以你的目标不是“记住所有”,而是“记住足够的、对当前任务有用的”。
8.2 最常用策略:窗口记忆(MessageWindowChatMemory)
窗口记忆保留最近 N 条消息,简单可靠,适合绝大多数对话产品的第一版上线。
package com.example.langchain4j.memory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
public class MemoryExample {
interface Assistant {
String chat(String message);
}
public static void main(String[] args) {
String apiKey = System.getenv("OPENAI_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("请先设置环境变量 OPENAI_API_KEY");
}
var model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.temperature(0.2)
.build();
var memory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemory(memory)
.build();
System.out.println(assistant.chat("我叫小李,做后端开发。"));
System.out.println(assistant.chat("请用一句话总结一下我是谁。"));
}
}8.3 生产要点:把 memoryId 放进你的业务主键
很多人会在 demo 里写 withMaxMessages(10) 就结束了,但上线后你需要明确:
- 一个用户是否只有一个会话?
- 同一个用户是否会同时开多个会话?
- 多租户怎么隔离?
常见做法是把 memoryId 设计为:
tenantId:userId:conversationId
并把 chat memory 持久化到你自己的存储(数据库/Redis)。这样你就能实现:
- 多实例水平扩展
- 对话跨重启保留
- 按会话维度做成本与风控
8.4 上下文控制的“组合拳”
除了窗口记忆,你还会经常用:
- 只保留最近 N 轮 + 系统摘要(把旧对话压缩成摘要)
- RAG 注入“只与问题相关”的知识片段
- 对工具调用结果做摘要(不要把大段 JSON 全塞回历史)
8.5 本章小结
你已经把“记忆”从概念落到工程:窗口策略 + 可扩展的 memoryId 设计。下一章开始我们进入最危险但也最有价值的能力:工具调用(函数调用)。一旦做对,你的 AI 才能真正“办事”;一旦做错,系统就可能被越权与注入打穿。
