第8章:LangChain检索增强生成RAG--2.4Advanced RAG【高级RAG】
这一部分详细介绍了 LangChain4j 中的高级 RAG(Advanced RAG)实现。高级 RAG 通过多个模块化组件来增强检索功能,提供了高度的灵活性和定制化能力,一般要做好,多半都会采用此方案
高级 RAG(Advanced RAG)
高级 RAG 可以通过 LangChain4j 实现,涉及以下核心组件:
- QueryTransformer(查询转换器)
- QueryRouter(查询路由器)
- ContentRetriever(内容检索器)
- ContentAggregator(内容聚合器)
- ContentInjector(内容注入器)
以下图表展示了这些组件是如何协同工作的:
流程如下:
- 用户生成一个 UserMessage,该消息被转换为一个 Query。
- QueryTransformer 将 Query 转换为一个或多个 Query。
- 每个 Query 通过 QueryRouter 路由到一个或多个 ContentRetriever。
- 每个 ContentRetriever 检索与每个 Query 相关的 Content。
- ContentAggregator 将所有检索到的 Content 合并为一个最终的排序列表。
- 这些 Content 被注入到原始的 UserMessage 中。
- 最后,包含原始查询以及注入的相关内容的 UserMessage 被发送到语言模型(LLM)。
请参考每个组件的 JavaDoc 以获取更多详细信息。
Retrieval Augmentor(检索增强器)
RetrievalAugmentor 是 RAG 流程的入口点,负责将 ChatMessage 与从各种来源检索到的相关 Content 增强。
在创建 AI 服务时,可以指定一个 RetrievalAugmentor 实例:
Assistant assistant = AiServices.builder(Assistant.class)
...
.retrievalAugmentor(retrievalAugmentor)
.build();
每次调用 AI 服务时,指定的 RetrievalAugmentor 将被调用来增强当前的 UserMessage。
你可以使用默认的 RetrievalAugmentor 实现(如下所述),或者实现一个自定义的。
默认检索增强器(Default Retrieval Augmentor)
LangChain4j 提供了一个现成的 RetrievalAugmentor 接口实现:DefaultRetrievalAugmentor,它适用于大多数 RAG 使用场景。它的设计灵感来源于 这篇文章 和 这篇论文。建议查阅这些资源以更好地理解该概念。
Query(查询)
Query 表示 RAG 流程中的用户查询。它包含查询文本和查询元数据。
查询元数据(Query Metadata)
Query 中的 Metadata 包含以下可能在 RAG 流程的各个组件中有用的信息:
- Metadata.userMessage():应该被增强的原始 UserMessage。
- Metadata.chatMemoryId():@MemoryId 注解的方法参数的值。更多详情见 这里。这可以用于识别用户,并在检索过程中应用访问限制或过滤器。
- Metadata.chatMemory():所有之前的 ChatMessage。这有助于理解查询被提出时的上下文。
查询转换器(Query Transformer)
QueryTransformer 将给定的 Query 转换为一个或多个 Query。目标是通过修改或扩展原始查询来提高检索质量。
一些已知的改进检索的方法包括:
- 查询压缩(Query Compression)
- 查询扩展(Query Expansion)
- 查询重写(Query Re-writing)
- 回退提示(Step-back Prompting)
假设文档嵌入(Hypothetical Document Embeddings,HyDE)
更多详细信息可以在这里找到。
默认查询转换器(Default Query Transformer)
DefaultQueryTransformer 是在 DefaultRetrievalAugmentor 中使用的默认实现。它不对 Query 进行任何修改,只是将其传递下去。
压缩查询转换器(Compressing Query Transformer)
CompressingQueryTransformer 使用语言模型(LLM)将给定的查询和之前的对话压缩为一个独立的查询。这在用户可能提出涉及之前问题或答案中信息的后续问题时非常有用。
例如:
用户:告诉我关于 John Doe 的信息
AI:John Doe 是……
用户:他住在哪里?
仅凭 “他住在哪里?” 这个查询本身无法检索到所需的信息,因为它没有明确提及 John Doe,不清楚 “他” 指代的是谁。
当使用 CompressingQueryTransformer 时,LLM 将读取整个对话,并将 “他住在哪里?” 转换为 “John Doe 住在哪里?”。
扩展查询转换器(Expanding Query Transformer)
ExpandingQueryTransformer 使用语言模型(LLM)将给定的查询扩展为多个查询。这很有用,因为 LLM 可以以多种方式重新表述和重新构建查询,这将有助于检索到更相关的内容。
内容(Content)
Content 表示与用户查询相关的文本内容。目前,它仅限于文本内容(即 TextSegment),但未来可能会支持其他模态(例如图像、音频、视频等)。
内容检索器(Content Retriever)
ContentRetriever 使用给定的查询从底层数据源检索相关的内容。底层数据源可以是任何东西:
- 嵌入存储(Embedding Store)
- 全文搜索引擎(Full-text Search Engine)
- 向量和全文搜索的混合(Hybrid of Vector and Full-text Search)
- Web 搜索引擎(Web Search Engine)
- 知识图谱(Knowledge Graph)
- SQL 数据库(SQL Database)
- 等等
ContentRetriever 返回的内容列表按相关性从高到低排序。
嵌入存储内容检索器(Embedding Store Content Retriever)
EmbeddingStoreContentRetriever 使用嵌入模型(EmbeddingModel)将查询嵌入向量空间,然后从嵌入存储(EmbeddingStore)中检索相关的内容。
示例代码如下:
EmbeddingStore embeddingStore = ...; // 嵌入存储实例
EmbeddingModel embeddingModel = ...; // 嵌入模型实例
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3) // 最多返回 3 个结果
.minScore(0.75) // 最小相似度分数为 0.75
.filter(metadataKey("userId").isEqualTo("12345")) // 应用元数据过滤器
.build();
Web 搜索内容检索器(Web Search Content Retriever)
WebSearchContentRetriever 使用 Web 搜索引擎从网络上检索相关的内容。
支持的 Web 搜索引擎集成可以在这里找到。
示例代码如下:
WebSearchEngine googleSearchEngine = GoogleCustomWebSearchEngine.builder()
.apiKey(System.getenv("GOOGLE_API_KEY"))
.csi(System.getenv("GOOGLE_SEARCH_ENGINE_ID"))
.build();
ContentRetriever contentRetriever = WebSearchContentRetriever.builder()
.webSearchEngine(googleSearchEngine)
.maxResults(3) // 最多返回 3 个结果
.build();
SQL 数据库内容检索器(SQL Database Content Retriever)
SqlDatabaseContentRetriever 是 ContentRetriever 的一个实验性实现,位于 langchain4j-experimental-sql 模块中。
它使用 DataSource 和语言模型(LLM)为给定的自然语言查询生成并执行 SQL 查询。
更多详细信息,请参阅 SqlDatabaseContentRetriever 的 JavaDoc。
官方示例
Azure AI 搜索内容检索器(Azure AI Search Content Retriever)
AzureAiSearchContentRetriever 是与 Azure AI 搜索的集成。它支持全文、向量和混合搜索,以及重新排序。它位于 langchain4j-azure-ai-search 模块中。请参阅 AzureAiSearchContentRetriever 的 JavaDoc 以获取更多信息。
Neo4j 内容检索器(Neo4j Content Retriever)
Neo4jContentRetriever 是与 Neo4j 图数据库的集成。它将自然语言查询转换为 Neo4j Cypher 查询,并通过运行这些查询在 Neo4j 中检索相关信息。它位于 langchain4j-neo4j 模块中。
查询路由器(Query Router)
QueryRouter 负责将查询路由到适当的 ContentRetriever。
默认查询路由器(Default Query Router)
DefaultQueryRouter 是在 DefaultRetrievalAugmentor 中使用的默认实现。它将每个查询路由到所有配置的 ContentRetriever。
语言模型查询路由器(Language Model Query Router)
LanguageModelQueryRouter 使用语言模型(LLM)来决定将给定的查询路由到哪里。
内容聚合器(Content Aggregator)
ContentAggregator 负责将来自多个查询和多个内容检索器的内容列表合并为一个最终的排序列表。
默认内容聚合器(Default Content Aggregator)
DefaultContentAggregator 是 ContentAggregator 的默认实现,它执行两阶段的互惠排名融合(Reciprocal Rank Fusion,RRF)。更多详细信息,请参阅 DefaultContentAggregator 的 JavaDoc。
重新排序内容聚合器(Re-Ranking Content Aggregator)
ReRankingContentAggregator 使用评分模型(如 Cohere)进行重新排序。支持的评分(重新排序)模型的完整列表可以在这里找到。更多详细信息,请参阅 ReRankingContentAggregator 的 JavaDoc。
内容注入器(Content Injector)
ContentInjector 负责将由 ContentAggregator 返回的内容注入到原始的 UserMessage 中。
默认内容注入器(Default Content Injector)
DefaultContentInjector 是 ContentInjector 的默认实现,它简单地将内容附加到 UserMessage 的末尾,并使用前缀“Answer using the following information:”。
你可以通过以下三种方式自定义内容如何被注入到 UserMessage 中:
覆盖默认的 PromptTemplate:
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("{{userMessage}}\n{{contents}}"))
.build())
.build();
请注意,PromptTemplate 必须包含 {{userMessage}} 和 {{contents}} 变量。
扩展 DefaultContentInjector 并覆盖其中一个 format 方法。
实现一个自定义的 ContentInjector。
DefaultContentInjector 还支持从检索到的 Content.textSegment() 注入元数据条目:
DefaultContentInjector.builder()
.metadataKeysToInclude(List.of("source"))
.build();
在这种情况下,TextSegment.text() 将被加上 “content:” 前缀,而每个元数据值将被加上键名前缀。最终的 UserMessage 将如下所示:
How can I cancel my reservation?
Answer using the following information:
content: To cancel a reservation, go to ...
source: ./cancellation_procedure.html
content: Cancellation is allowed for ...
source: ./cancellation_policy.html
并行化(Parallelization)
当只有一个查询和一个内容检索器时,DefaultRetrievalAugmentor 将在同一个线程中执行查询路由和内容检索。否则,将使用 Executor 来并行化处理。默认情况下,使用的是修改版的 Executors.newCachedThreadPool()(keepAliveTime 为 1 秒而不是 60 秒),但你可以在创建 DefaultRetrievalAugmentor 时提供一个自定义的 Executor 实例:
DefaultRetrievalAugmentor.builder()
...
.executor(executor)
.build();
示例代码
以下是一个完整的示例,展示如何配置和使用 Advanced RAG:
// 创建嵌入存储和嵌入模型
EmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingModel embeddingModel = new OpenAiEmbeddingModel("your-api-key", "text-embedding-ada-002");
// 配置内容检索器
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.75)
.build();
// 配置查询转换器
QueryTransformer queryTransformer = new CompressingQueryTransformer(embeddingModel);
// 配置查询路由器
QueryRouter queryRouter = new LanguageModelQueryRouter(embeddingModel);
// 配置内容聚合器
ContentAggregator contentAggregator = new ReRankingContentAggregator();
// 配置内容注入器
ContentInjector contentInjector = DefaultContentInjector.builder()
.promptTemplate(PromptTemplate.from("{{userMessage}}\n{{contents}}"))
.build();
// 创建检索增强器
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentRetriever(contentRetriever)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.build();
// 配置 AI 服务
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.retrievalAugmentor(retrievalAugmentor)
.build();
// 使用 AI 服务
String userMessage = "How to do Advanced RAG with LangChain4j?";
String answer = assistant.chat(userMessage);
System.out.println("Answer: " + answer);
总结
高级 RAG(Advanced RAG)通过多个模块化组件(如查询转换器、查询路由器、内容检索器、内容聚合器和内容注入器)来增强检索功能。这种实现方式提供了高度的灵活性和定制化能力,适用于需要复杂检索逻辑的场景。通过合理配置这些组件,开发者可以构建高效、智能的问答系统。