LangChain4j RAG 核心组件与组合方式
在使用 LangChain4j 构建 RAG 应用时,经常会看到一组组件名,比如 RetrievalAugmentor、QueryTransformer、QueryRouter、ContentRetriever、ContentAggregator、ContentInjector。
这些组件名看起来很多,容易让人觉得框架封装很深,不知道真实原理是什么。
其实 LangChain4j 并没有改变 RAG 的本质,它只是把 RAG 的几个固定步骤抽象成了标准组件。理解这些组件之后,就能知道一次 RAG 问答到底是怎么执行的。
本文介绍 LangChain4j RAG 的核心组件,以及这些组件如何组合成一个完整的检索增强问答流程。
一、RAG 的真实执行流程
不管使用什么框架,RAG 的核心流程都差不多:
用户问题
|
v
问题改写 / 查询增强
|
v
选择合适的数据源
|
v
执行内容检索
|
v
合并、去重、重排序检索结果
|
v
把检索结果注入 Prompt
|
v
调用大模型生成答案
如果不使用框架,可能会写成类似这样的代码:
String rewrittenQuery = rewrite(userQuestion);
List<Document> vectorDocs = esVectorSearch(rewrittenQuery);
List<Document> fullTextDocs = esFullTextSearch(rewrittenQuery);
List<Document> sqlDocs = text2SqlSearch(rewrittenQuery);
List<Document> graphDocs = graphSearch(rewrittenQuery);
List<Document> finalDocs = mergeAndRerank(
vectorDocs,
fullTextDocs,
sqlDocs,
graphDocs
);
String prompt = injectContext(userQuestion, finalDocs);
String answer = llm.chat(prompt);
LangChain4j 做的事情,就是把上面这些步骤拆成标准组件,然后通过 RetrievalAugmentor 统一编排。
二、LangChain4j RAG 的整体结构
LangChain4j 中,一个完整的 RAG 流程通常由下面几个组件组成:
RetrievalAugmentor
|
|-- QueryTransformer
|-- QueryRouter
| |
| |-- ContentRetriever 1
| |-- ContentRetriever 2
| |-- ContentRetriever 3
|
|-- ContentAggregator
|-- ContentInjector
可以简单理解为:
| 组件 | 作用 |
|---|---|
RetrievalAugmentor |
RAG 总编排器,负责串起完整检索增强流程 |
QueryTransformer |
问题改写器,对用户问题进行压缩、改写或扩展 |
QueryRouter |
问题路由器,决定当前问题应该走哪些检索器 |
ContentRetriever |
内容检索器,真正从 ES、数据库、向量库、图数据库等数据源检索内容 |
ContentAggregator |
内容聚合器,对多路检索结果进行合并、去重、重排序 |
ContentInjector |
内容注入器,把检索结果注入到最终 Prompt 中 |
这些组件并不是都负责搜索。真正搜索的是 ContentRetriever,其他组件负责搜索前后的流程编排。
三、RetrievalAugmentor:RAG 总编排器
RetrievalAugmentor 是 LangChain4j RAG 流程的核心入口。
它负责把问题改写、问题路由、内容检索、结果聚合和上下文注入串起来。
常见组合方式如下:
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.build();
这段代码表示:
- 使用
queryTransformer对用户问题进行改写 - 使用
queryRouter决定问题应该走哪些检索器 - 使用
contentAggregator对多路检索结果进行聚合和重排序 - 使用
contentInjector把最终内容注入 Prompt - 使用
DefaultRetrievalAugmentor串起完整 RAG 流程
所以 RetrievalAugmentor 本身不直接查 ES,也不直接查数据库。它更像一个流程控制器。
四、QueryTransformer:问题改写器
QueryTransformer 负责在真正检索之前,对用户原始问题进行处理。
用户的问题往往比较口语化,比如:
这个怎么弄?
如果直接拿这个问题去检索,效果可能不好。问题改写器可以结合上下文,把它改写成更适合检索的问题。
例如改写成:
如何打开汽车零重力座椅?
它的作用可以理解为:
用户原始问题
|
v
LLM 改写 / 压缩 / 补全
|
v
更适合检索的问题
在 RAG 中,问题改写很重要。因为检索效果很大程度上取决于 query 的质量。
五、QueryRouter:问题路由器
QueryRouter 负责判断一个问题应该走哪些检索器。
在简单 RAG 中,可能只有一个知识库检索器,不需要路由。
但是在复杂系统中,数据源可能有很多种:
- ES 向量检索
- ES 全文检索
- 关系型数据库检索
- 图数据库检索
- 外部接口检索
- 文件系统检索
这时就需要路由器判断:这个问题到底应该去哪里查?
例如可以定义三类路由策略:
| 路由策略 | 适合场景 | 对应检索器 |
|---|---|---|
knowledge_base |
非结构化知识问答、文档问答、语义搜索 | ES / 向量库 / 文档库检索器 |
relational_db |
订单、车辆、用户、库存等结构化数据查询 | SQL 检索器 |
graph_db |
实体关系、路径分析、关联查询 | 图数据库检索器 |
一个简化的路由器可以这样理解:
class ExampleQueryRouter implements QueryRouter {
private final ContentRetriever vectorRetriever;
private final ContentRetriever fullTextRetriever;
private final ContentRetriever sqlRetriever;
private final ContentRetriever graphRetriever;
@Override
public Collection<ContentRetriever> route(Query query) {
String strategy = classify(query.text());
return switch (strategy) {
case "knowledge_base" -> List.of(vectorRetriever, fullTextRetriever);
case "relational_db" -> List.of(sqlRetriever);
case "graph_db" -> List.of(graphRetriever);
default -> List.of(vectorRetriever, fullTextRetriever);
};
}
}
这里的 classify() 可以是规则判断,也可以是调用大模型做意图识别。
六、ContentRetriever:真正执行检索的组件
ContentRetriever 是 LangChain4j RAG 中真正负责检索内容的组件。
不同数据源可以实现不同的 ContentRetriever:
| 检索器 | 数据源 | 作用 |
|---|---|---|
vectorRetriever |
ES / 向量数据库 | 向量检索,召回语义相似内容 |
fullTextRetriever |
Elasticsearch | 全文检索,召回关键词匹配内容 |
sqlRetriever |
关系型数据库 | 通过 Text2SQL 查询结构化数据 |
graphRetriever |
图数据库 | 查询实体关系、路径和图结构 |
apiRetriever |
外部 API | 调用业务系统获取实时数据 |
也就是说,ES 搜索只是 ContentRetriever 的一种实现。
LangChain4j 的 RAG 框架并不关心底层是 ES、MySQL、Neo4j 还是外部接口。只要它实现了 ContentRetriever 接口,就可以被接入到 RAG 流程中。
七、ES 向量检索和全文检索在 RAG 中的位置
在 RAG 系统中,ES 检索经常会分成两路:
ES 向量检索
ES 全文检索
这两路都属于 ContentRetriever。
1. ES 向量检索
向量检索的核心流程是:
用户问题
|
v
embedding 模型生成问题向量
|
v
用问题向量到 ES dense_vector 字段中做相似度搜索
|
v
返回语义相似的知识片段
简化代码如下:
ContentRetriever vectorRetriever = ElasticsearchContentRetriever.builder()
.restClient(restClient)
.embeddingModel(embeddingModel)
.indexName("knowledge_chunks")
.maxResults(5)
.minScore(0.5)
.build();
向量检索适合处理表达不同但语义相近的问题。
例如用户问:
ES 能不能做语义搜索?
文档里写的是:
Elasticsearch 支持基于 dense_vector 的相似度召回。
关键词不完全一样,但向量检索可能仍然能召回。
2. ES 全文检索
全文检索的核心流程是:
用户问题
|
v
分词
|
v
倒排索引查找
|
v
BM25 等相关性评分
|
v
返回关键词匹配度高的知识片段
简化代码如下:
ContentRetriever fullTextRetriever = ElasticsearchContentRetriever.builder()
.restClient(restClient)
.indexName("knowledge_chunks")
.maxResults(5)
.build();
全文检索适合处理专有名词、编号、错误码、精确短语等问题。
例如:
ERROR_CODE_10086 是什么意思?
这种问题通常更依赖关键词精确匹配,全文检索会比单纯向量检索更稳定。
八、多路检索:为什么要组合多个 Retriever
单一路径检索往往不够稳定。
只用全文检索,可能遇到这些问题:
- 同义词召回效果差
- 用户表达和文档表达不一致时容易漏召回
- 对自然语言问题不够友好
只用向量检索,也可能遇到这些问题:
- 对错误码、编号、类名、方法名不够稳定
- 结果可解释性弱
- 依赖 embedding 模型质量
所以更常见的做法是多路检索:
用户问题
|
+--> ES 向量检索
|
+--> ES 全文检索
|
+--> SQL 检索
|
+--> 图数据库检索
然后再交给 ContentAggregator 做统一处理。
九、ContentAggregator:多路结果聚合和重排序
当一个问题走了多个检索器之后,会得到多批结果。
例如:
ES 向量检索结果
ES 全文检索结果
SQL 检索结果
图数据库检索结果
这些结果不能直接全部塞给大模型,因为可能存在:
- 重复内容
- 不相关内容
- 排序不准确
- 结果数量太多
所以需要 ContentAggregator 进行聚合。
常见处理方式包括:
- 合并多路结果
- 根据文档 ID 或 chunk ID 去重
- 对分数做归一化
- 使用 rerank 模型重新排序
- 过滤低分内容
- 截取 Top N 条结果
简化代码如下:
ContentAggregator contentAggregator = ReRankingContentAggregator.builder()
.scoringModel(scoringModel)
.minScore(0.6)
.maxResults(5)
.build();
可以理解为:
多路召回结果
|
v
去重 + 过滤 + rerank
|
v
最适合注入 Prompt 的 Top N 内容
十、ContentInjector:把检索内容注入 Prompt
ContentInjector 负责把聚合后的检索内容注入到大模型 Prompt 中。
简化代码如下:
ContentInjector contentInjector = new DefaultContentInjector();
它的作用可以理解为:
用户问题 + 检索到的知识片段
|
v
组装成最终 Prompt
|
v
发送给大模型
例如用户问题是:
如何打开零重力座椅?
检索到的知识片段是:
零重力座椅可通过中控屏座椅菜单开启,也可以通过语音助手控制。
ContentInjector 会把这些内容拼到 Prompt 中,让大模型基于资料回答,而不是完全依赖模型自身记忆。
这也是 RAG 能减少幻觉的关键步骤。
十一、组件组合后的完整执行链路
把这些组件组合起来后,完整流程可以画成这样:
用户问题
|
v
QueryTransformer
问题改写 / 查询增强
|
v
QueryRouter
判断应该走哪些检索器
|
+------------------------------+
| |
v v
ContentRetriever ContentRetriever
ES 向量检索 ES 全文检索
| |
+--------------+---------------+
|
v
ContentAggregator
多路结果聚合、过滤、rerank
|
v
ContentInjector
把检索结果注入 Prompt
|
v
ChatModel / StreamingChatModel
大模型生成答案
如果问题需要查结构化数据,可以路由到 SQL 检索器。
如果问题需要查实体关系,可以路由到图数据库检索器。
如果问题需要查文档知识库,可以路由到向量检索和全文检索。
十二、AiServices 如何使用 RetrievalAugmentor
构建好 RetrievalAugmentor 之后,需要把它交给 LangChain4j 的 AI Service。
示例代码如下:
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.systemMessageProvider(memoryId -> "你是一个专业助手,请基于资料回答问题。")
.retrievalAugmentor(retrievalAugmentor)
.build();
这里的关键是:
.retrievalAugmentor(retrievalAugmentor)
它表示:在调用大模型之前,先执行 RAG 检索增强流程。
也就是说,大模型最终看到的不是单纯的用户问题,而是类似这样的内容:
系统提示词
+ 用户问题
+ 检索到的相关资料
+ 历史会话记忆
然后再生成最终答案。
如果使用流式模型,也可以替换成:
Assistant assistant = AiServices.builder(Assistant.class)
.streamingChatModel(streamingChatModel)
.retrievalAugmentor(retrievalAugmentor)
.build();
十三、不要被框架封装迷惑
LangChain4j 的组件名很多,但真实原理并不复杂。
可以把它还原成普通代码:
Query transformedQuery = queryTransformer.transform(originalQuery);
Collection<ContentRetriever> retrievers = queryRouter.route(transformedQuery);
List<Content> contents = new ArrayList<>();
for (ContentRetriever retriever : retrievers) {
contents.addAll(retriever.retrieve(transformedQuery));
}
List<Content> finalContents = contentAggregator.aggregate(transformedQuery, contents);
Prompt prompt = contentInjector.inject(finalContents, originalQuery);
String answer = chatModel.chat(prompt);
框架只是帮我们把这套流程标准化了。
真正需要关注的是:
- 问题有没有被正确改写
- 路由器有没有选对检索器
- 检索器底层查的是什么数据源
- 多路结果有没有正确合并和重排序
- 注入到 Prompt 的内容是否准确、足够、不过量
十四、总结
LangChain4j RAG 的核心不是某一个搜索引擎,而是一套检索增强生成的流程抽象。
它把 RAG 拆成了几个核心组件:
QueryTransformer:问题改写
QueryRouter:问题路由
ContentRetriever:内容检索
ContentAggregator:结果聚合和重排序
ContentInjector:上下文注入
RetrievalAugmentor:统一编排
其中,ES 搜索只是 ContentRetriever 的一种实现。
在真实应用中,ES 通常可以分成两路:
ES 向量检索:负责语义相似召回
ES 全文检索:负责关键词匹配召回
它们可以和 SQL 检索、图数据库检索、外部 API 检索一起,由 QueryRouter 选择,再由 RetrievalAugmentor 串联成完整的 RAG 流程。
所以理解 LangChain4j RAG 的关键是:
不要先看框架名字,而是先还原真实流程。
只要抓住这条主线:
改写问题 -> 路由检索器 -> 执行检索 -> 聚合重排 -> 注入 Prompt -> 大模型回答
再复杂的 LangChain4j RAG 封装,也就能看明白了。
作者:Work Hard Work Smart
出处:http://www.cnblogs.com/linlf03/
欢迎任何形式的转载,未经作者同意,请保留此段声明!
浙公网安备 33010602011771号