LangChain4j中RAG结合对话记忆时上下文窗口重复信息的解决方法
LangChain4j中RAG结合对话记忆时上下文窗口重复信息的解决方法
我完全懂你遇到的困扰——每次调用对话接口,RAG检索到的内容都会被硬塞进用户消息里存进对话记忆,导致后续请求里这些内容被反复发送,既浪费token又显得冗余。咱们来拆解问题根源,再一步步解决它:
问题根源
默认情况下,LangChain4j的AiServices会把RAG检索到的内容直接附加到用户输入消息的前面,然后把这个拼接了RAG内容的完整消息存入对话记忆。这样下一次对话时,历史消息里已经包含了之前的RAG内容,新的请求又会再追加一次检索结果,自然就造成了重复发送的问题。
另外你的代码里还有几个小细节需要修正:注解的大小写错误(@memoryid应该是@MemoryId,@Usermessage应该是@UserMessage),这些虽然不影响功能,但规范写法能避免潜在问题。
解决方法:自定义检索增强逻辑,分离RAG内容与对话记忆
核心思路是:让RAG检索到的内容只作为当前对话的临时系统上下文,不被存入对话记忆。我们可以通过自定义RetrievalAugmentor来实现这个效果:
步骤1:修正接口注解并定义对话接口
首先修正接口的注解大小写,确保符合LangChain4j的规范:
interface ChatInterface extends ChatMemoryAccess { // 纯用户消息,不包含RAG内容 String chat(@MemoryId int memoryId, @UserMessage String userMessage); }
步骤2:自定义RetrievalAugmentor
创建一个自定义的检索增强器,把RAG内容封装成系统消息,只在当前请求中传给AI模型,不会被存入对话记忆:
RetrievalAugmentor customAugmentor = context -> { // 把检索到的所有内容拼接成字符串 String retrievedContent = context.retrievedContents().stream() .map(Content::text) .reduce((a, b) -> a + "\n" + b) .orElse(""); // 封装成系统消息,作为当前对话的上下文 SystemMessage systemMsg = SystemMessage.from("请基于以下信息回答用户的问题:\n" + retrievedContent); return RetrievalAugmentor.Result.from(systemMsg); };
步骤3:整合到AiServices配置中
把自定义的增强器加入AiServices的构建流程,替换默认的拼接逻辑:
完整修改后的代码
import java.util.ArrayList; import java.util.List; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.openai.OpenAiChatModelName; import dev.langchain4j.rag.RetrievalAugmentor; import dev.langchain4j.rag.content.Content; import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.rag.query.Query; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.memory.ChatMemoryAccess; interface ChatInterface extends ChatMemoryAccess { String chat(@MemoryId int memoryId, @UserMessage String userMessage); } public class RagHistoryFix { public static void main(String args[]) throws Exception { String apiKey = "你的API密钥或本地模型配置"; OpenAiChatModel chatModel = OpenAiChatModel.builder() .apiKey(apiKey) .modelName(OpenAiChatModelName.GPT_4_O_MINI) .build(); // 自定义RAG内容检索器(和你原来的逻辑一致) ContentRetriever contentRetriever = new ContentRetriever() { @Override public List<Content> retrieve(Query q) { List<Content> list = new ArrayList<>(); list.add(Content.from("I have a cat called Pulasy. It likes mice very much")); return list; } }; // 自定义检索增强器:将RAG内容作为临时系统上下文,不存入对话记忆 RetrievalAugmentor customAugmentor = context -> { String retrievedContent = context.retrievedContents().stream() .map(Content::text) .reduce((a, b) -> a + "\n" + b) .orElse(""); SystemMessage systemMsg = SystemMessage.from("请基于以下信息回答用户问题:\n" + retrievedContent); return RetrievalAugmentor.Result.from(systemMsg); }; // 构建AI服务 ChatInterface assistant = AiServices.builder(ChatInterface.class) .chatModel(chatModel) .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(11)) .contentRetriever(contentRetriever) .retrievalAugmentor(customAugmentor) // 应用自定义增强器 .build(); // 测试对话 System.out.println(assistant.chat(1, "What is the name of my cat?")); System.out.println(assistant.chat(1, "Do my cat like mice?")); System.out.println(assistant.chat(1, "Is Pulasy a lion?")); // 检查对话记忆:确认只有纯用户消息和AI回复 ChatMemory mem = assistant.getChatMemory(1); List<ChatMessage> messages = mem.messages(); for (ChatMessage msg : messages) { System.out.println("Msg:" + msg); } } }
效果验证
运行修改后的代码,你会发现对话记忆里的用户消息是纯原始提问(比如“What is the name of my cat?”),RAG内容只会作为每次请求的系统上下文被发送一次,不会被存入记忆。这样后续请求就不会再重复发送相同的RAG内容,完美解决了重复问题。
内容来源于stack exchange
相关产品推荐
相关产品推荐

