07-advanced-rag-patterns 高级 RAG:查询改写、路由、过滤、重排和来源返回
LangChain4j 进阶实战:第 7 篇,高级 RAG 模式,压缩、路由、过滤、重排和来源返回
1. 为什么基础 RAG 还不够
基础 RAG 的流程是:
用户问题 -> 向量检索 -> 取 TopK -> 塞给模型 -> 生成回答这个流程能跑通,但在真实业务里很快会遇到问题:
- 多轮对话里,用户问“他什么时候出生”,检索不到“他”是谁。
- 知识分散在多个库里,不知道该查哪个。
- 用户只能看自己有权限的数据。
- 检索出来的 TopK 里有不少无关片段。
- 用户想知道答案来自哪里。
- 有些问题根本不需要检索,却也走了一遍 RAG,浪费成本。
- 内部知识库没有最新信息,需要补充网络搜索。
这就是高级 RAG 的价值。
我的总结先放前面:高级 RAG 的本质不是“加更多组件”,而是把检索过程从粗暴 TopK 变成可控流程。你要决定查不查、查哪里、怎么改写问题、怎么过滤、怎么排序、怎么解释来源。
2. Query Compression:解决多轮对话指代问题
实现查询改写的核心写法:
QueryTransformerqueryTransformer=newCompressingQueryTransformer(chatModel);returnDefaultRetrievalAugmentor.builder().queryTransformer(queryTransformer).contentRetriever(contentRetriever).build();完整案列
**@PostMapping("/chat") public String queryCompressionRagChat(@RequestParam @NotBlank String userMessage) { log.info("收到查询压缩高级 RAG 聊天请求: {}", userMessage); try { // 创建带有查询压缩高级 RAG 功能的聊天服务 ChatService queryCompressionRagChatService = createQueryCompressionRagChatService(); // 使用查询压缩高级 RAG 服务进行聊天 String response = queryCompressionRagChatService.chat(userMessage); log.info("查询压缩高级 RAG 聊天完成,响应长度: {}", response.length()); return response; } catch (Exception e) { log.error("查询压缩高级 RAG 聊天处理失败: {}", e.getMessage(), e); return "查询压缩高级 RAG 聊天处理失败: " + e.getMessage(); } } /** * 创建带有查询压缩高级 RAG 功能的聊天服务 * * @return 配置了查询压缩高级 RAG 的聊天服务 */ private ChatService createQueryCompressionRagChatService() { // 加载并处理文档 RetrievalAugmentor retrievalAugmentor = createQueryCompressionRetrievalAugmentor(); return AiServices.builder(ChatService.class) .chatModel(chatModel) .retrievalAugmentor(retrievalAugmentor) .chatMemory(MessageWindowChatMemory.withMaxMessages(CHAT_MEMORY_SIZE)) .build(); } /** * 创建查询压缩检索增强器 * * @return 查询压缩检索增强器 */ private RetrievalAugmentor createQueryCompressionRetrievalAugmentor() { // 加载文档 Document document = loadDocument(Path.of(DOCUMENT_PATH), new TextDocumentParser()); // 创建嵌入模型 EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel(); log.info("使用 BgeSmallEnV15QuantizedEmbeddingModel 嵌入模型"); // 创建嵌入存储 EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>(); // 使用 EmbeddingStoreIngestor 简化文档摄入过程 EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() .documentSplitter(DocumentSplitters.recursive(SEGMENT_SIZE, SEGMENT_OVERLAP)) .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .build(); // 摄入文档 ingestor.ingest(document); log.info("已使用 EmbeddingStoreIngestor 摄入文档,段大小: {},重叠: {}", SEGMENT_SIZE, SEGMENT_OVERLAP); // 创建查询转换器(查询压缩) QueryTransformer queryTransformer = new CompressingQueryTransformer(chatModel); log.info("使用 CompressingQueryTransformer 进行查询压缩"); // 创建内容检索器 ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(MAX_RESULTS) .minScore(MIN_SCORE) .build(); log.info("创建内容检索器,最大结果: {},最小分数: {}", MAX_RESULTS, MIN_SCORE); // 创建检索增强器 return DefaultRetrievalAugmentor.builder() .queryTransformer(queryTransformer) .contentRetriever(contentRetriever) .build(); }**2.1 原理
多轮对话中,用户经常使用指代:
用户:John Doe 有哪些贡献? AI:John Doe 是一位科学家... 用户:他什么时候出生?如果直接拿“他什么时候出生?”去检索,向量库可能不知道“他”是谁。
Query Compression 会结合历史对话,把问题改写成:
John Doe 什么时候出生?这就是查询压缩或查询改写。
2.2 适用场景
- 多轮问答。
- 用户经常说“刚才那个”“它”“这个方案”。
- 客服对话。
- 文档问答连续追问。
- 智能衣橱里用户追问“那换成休闲一点呢?”
2.3 我的建议
Query Compression 很实用,但会增加一次模型调用。建议只在多轮会话中启用,单轮 FAQ 可以不启用。
3. Query Routing:解决多知识库选择问题
实现知识库路由的核心写法:
Map<ContentRetriever,String>retrieverToDescription=newHashMap<>();retrieverToDescription.put(biographyContentRetriever,"biography of John Doe");retrieverToDescription.put(termsOfUseContentRetriever,"terms of use of car rental company");QueryRouterqueryRouter=newLanguageModelQueryRouter(chatModel,retrieverToDescription);returnDefaultRetrievalAugmentor.builder().queryRouter(queryRouter).build();案例
public class QueryRoutingAdvancedRagController { private final OpenAiChatModel chatModel; // 硬编码的配置值 private static final String BIOGRAPHY_DOCUMENT_PATH = "./documents/biography-of-john-doe.txt"; private static final String TERMS_OF_USE_DOCUMENT_PATH = "./documents/miles-of-smiles-terms-of-use.txt"; private static final int SEGMENT_SIZE = 300; private static final int SEGMENT_OVERLAP = 0; private static final int MAX_RESULTS = 2; private static final double MIN_SCORE = 0.6; private static final int CHAT_MEMORY_SIZE = 10; /** * 查询路由高级 RAG 聊天接口 - 基于文档检索的聊天(包含查询路由) * * @param userMessage 用户消息,不能为空 * @return 基于文档检索的 AI 回复 */ @PostMapping("/chat") public String queryRoutingRagChat(@RequestParam @NotBlank String userMessage) { log.info("收到查询路由高级 RAG 聊天请求: {}", userMessage); try { // 创建带有查询路由高级 RAG 功能的聊天服务 ChatService queryRoutingRagChatService = createQueryRoutingRagChatService(); // 使用查询路由高级 RAG 服务进行聊天 String response = queryRoutingRagChatService.chat(userMessage); log.info("查询路由高级 RAG 聊天完成,响应长度: {}", response.length()); return response; } catch (Exception e) { log.error("查询路由高级 RAG 聊天处理失败: {}", e.getMessage(), e); return "查询路由高级 RAG 聊天处理失败: " + e.getMessage(); } } /** * 创建带有查询路由高级 RAG 功能的聊天服务 * * @return 配置了查询路由高级 RAG 的聊天服务 */ private ChatService createQueryRoutingRagChatService() { // 加载并处理文档 RetrievalAugmentor retrievalAugmentor = createQueryRoutingRetrievalAugmentor(); return AiServices.builder(ChatService.class) .chatModel(chatModel) .retrievalAugmentor(retrievalAugmentor) .chatMemory(MessageWindowChatMemory.withMaxMessages(CHAT_MEMORY_SIZE)) .build(); } /** * 创建查询路由检索增强器 * * @return 查询路由检索增强器 */ private RetrievalAugmentor createQueryRoutingRetrievalAugmentor() { // 创建嵌入模型 EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel(); // 为传记创建单独的嵌入存储 EmbeddingStore<TextSegment> biographyEmbeddingStore = embed(Path.of(BIOGRAPHY_DOCUMENT_PATH), embeddingModel); ContentRetriever biographyContentRetriever = EmbeddingStoreContentRetriever.builder() .embeddingStore(biographyEmbeddingStore) .embeddingModel(embeddingModel) .maxResults(MAX_RESULTS) .minScore(MIN_SCORE) .build(); // 为使用条款创建单独的嵌入存储 EmbeddingStore<TextSegment> termsOfUseEmbeddingStore = embed(Path.of(TERMS_OF_USE_DOCUMENT_PATH), embeddingModel); ContentRetriever termsOfUseContentRetriever = EmbeddingStoreContentRetriever.builder() .embeddingStore(termsOfUseEmbeddingStore) .embeddingModel(embeddingModel) .maxResults(MAX_RESULTS) .minScore(MIN_SCORE) .build(); // 创建查询路由器 Map<ContentRetriever, String> retrieverToDescription = new HashMap<>(); retrieverToDescription.put(biographyContentRetriever, "biography of John Doe"); retrieverToDescription.put(termsOfUseContentRetriever, "terms of use of car rent