LangChain4j 面试题:多知识源怎么路由?QueryRouter、ReRankingContentAggregator 讲透
当知识库从一个变成多个以后,RAG 就不只是‘搜一下’了,而是要先决定去哪里搜、搜回来以后怎么重排。
LangChain4j 在这块给得很完整:QueryRouter负责路由,ReRankingContentAggregator负责重排,DefaultRetrievalAugmentor负责把整条链路串起来。
🦅个人主页
🐼GitHub主页
文章目录
- LangChain4j 面试题:多知识源怎么路由?QueryRouter、ReRankingContentAggregator 讲透
- 先看真实问题:为什么多知识源场景下,‘全部都搜一遍’通常不是好主意
- 一张表先看懂:多知识源 RAG 最关键的两个部件在管什么
- 举个具体例子:电商客服助手:同一句问题,可能要去 FAQ、退款规则、物流知识里找
- 企业里的典型应用场景
- 如果按企业项目落地,我会这样走完整闭环
- 代码示例:QueryRouter + ReRankingContentAggregator + RetrievalAugmentor
- 定义多个检索器
- 使用 LanguageModelQueryRouter 做路由
- 使用重排模型聚合候选内容
- 挂到 AI Service 上
- 企业级代码示例:企业级高级 RAG 更像一个可观测的检索编排器
- 多知识源检索编排服务
- SQL 示例:多知识源配置表
- 系统设计时我会优先拆哪几层
- 路由层
- 聚合重排层
- 注入层
- 真正上线时最容易卡住的点
- 监控和指标建议盯哪些
- 如果面试官问我这块怎么设计,我会这样答
- 结语
先看真实问题:为什么多知识源场景下,‘全部都搜一遍’通常不是好主意
如果一个问题同时对 FAQ、退款规则、物流说明、营销政策全量搜,理论上召回不会漏,但现实里 prompt 很快就会被噪音撑满,模型也更容易被干扰。
所以高级 RAG 的关键不是把所有东西都检一遍,而是让查询先路由到最相关的知识源,再用重排模型把候选结果重新排序。
- 不同知识源的内容风格和密度差异很大,简单混排很容易把真正关键的信息淹掉
- 路由错了会直接影响召回上限,重排做不好会影响最终 prompt 质量
- 高级 RAG 真正需要的是编排,不是单个组件越多越好
一张表先看懂:多知识源 RAG 最关键的两个部件在管什么
| 维度 | 怎么做 | 为什么 |
|---|---|---|
| QueryRouter | 决定某个 query 去哪些检索器 | 先控制检索范围 |
| ContentAggregator | 融合多个检索器返回的内容 | 决定最终 prompt 里保留谁 |
| ReRanking | 用额外模型重新排序内容 | 提高最终命中质量 |
| RetrievalAugmentor | 串起 transform、route、retrieve、aggregate、inject | 把高级 RAG 变成一条完整链路 |
举个具体例子:电商客服助手:同一句问题,可能要去 FAQ、退款规则、物流知识里找
- 用户问‘发货后还能仅退款吗’,系统先判断这更像退款规则问题,而不是营销 FAQ。
LanguageModelQueryRouter根据每个检索器的描述决定路由到退款规则库和物流规则库。- 多个检索器返回候选片段后,
ReRankingContentAggregator用重排模型重排一次。 - 最终只把最相关的几段内容注入 prompt,减少噪音和 token 浪费。
企业里的典型应用场景
- 电商客服中台:FAQ、退款规则、物流规则、营销政策是四套知识源,不可能每次全量搜索。
- 企业搜索平台:HR、财务、法务、研发文档同在一个系统里,需要先路由再重排。
- 跨区域知识中心:国内站和海外站规则差异大,同一问题要先判断落哪个区域库。
如果按企业项目落地,我会这样走完整闭环
- 问题进入后先做 query transform,把模糊问法转成更适合检索的表达。
- 路由层根据 query 语义和业务域选择一个或多个知识源,而不是全量铺开。
- 召回层从多个知识源拿到候选内容后,先做格式归一和来源补全。
- 重排层用 reranker 或 scoring model 对候选内容重新排序,留下最终前几段。
- 注入层把内容、来源、分数一起塞进 prompt,并对候选不足场景做拒答降级。
- 治理层记录路由结果、重排前后顺序、最终命中来源,后续才能持续调优。
代码示例:QueryRouter + ReRankingContentAggregator + RetrievalAugmentor
定义多个检索器
ContentRetrieverfaqRetriever=EmbeddingStoreContentRetriever.builder().embeddingStore(faqStore).embeddingModel(embeddingModel).maxResults(4).build();ContentRetrieverrefundRetriever=EmbeddingStoreContentRetriever.builder().embeddingStore(refundRuleStore).embeddingModel(embeddingModel).maxResults(4).build();ContentRetrieverlogisticsRetriever=EmbeddingStoreContentRetriever.builder().embeddingStore(logisticsStore).embeddingModel(embeddingModel).maxResults(4).build();使用 LanguageModelQueryRouter 做路由
QueryRouterqueryRouter=LanguageModelQueryRouter.builder().chatModel(chatModel).retrieverToDescription(Map.of(faqRetriever,"适合常见售后FAQ和标准问答",refundRetriever,"适合退款、退货、售后规则说明",logisticsRetriever,"适合物流、签收、拒收、配送时效说明")).fallbackStrategy(LanguageModelQueryRouter.FallbackStrategy.DO_NOT_ROUTE).build();使用重排模型聚合候选内容
ScoringModelscoringModel=JinaScoringModel.builder().apiKey(System.getenv("JINA_API_KEY")).modelName("jina-reranker-v2-base-multilingual").build();ContentAggregatoraggregator=ReRankingContentAggregator.builder().scoringModel(scoringModel).build();RetrievalAugmentorretrievalAugmentor=DefaultRetrievalAugmentor.builder().queryRouter(queryRouter).contentRetriever(faqRetriever).contentRetriever(refundRetriever).contentRetriever(logisticsRetriever).contentAggregator(aggregator).build();挂到 AI Service 上
publicinterfaceCustomerSupportAssistant{Stringchat(Stringquestion);}CustomerSupportAssistantassistant=AiServices.builder(CustomerSupportAssistant.class).chatModel(chatModel).retrievalAugmentor(retrievalAugmentor).build();企业级代码示例:企业级高级 RAG 更像一个可观测的检索编排器
多知识源检索编排服务
@Service@RequiredArgsConstructorpublicclassMultiRetrieverOrchestrator{privatefinalQueryRouterqueryRouter;privatefinalMap<String,ContentRetriever>retrieverRegistry;privatefinalContentAggregatorcontentAggregator;privatefinalRouteAuditRepositoryrouteAuditRepository;publicList<Content>retrieve(MultiRetrieverCommandcommand){Queryquery=Query.from(command.question(),Metadata.from(command.toMetadata()));Collection<ContentRetriever>routedRetrievers=queryRouter.route(query);routeAuditRepository.save(RouteAuditEntity.builder().question(command.question()).routedRetrieverCodes(routedRetrievers.stream().map(this::lookupCode).toList()).build());List<Content>candidates=routedRetrievers.stream().flatMap(retriever->retriever.retrieve(query).stream()).toList();if(candidates.isEmpty()){returnList.of();}returncontentAggregator.aggregate(query,candidates);}privateStringlookupCode(ContentRetrieverretriever){returnretrieverRegistry.entrySet().stream().filter(entry->entry.getValue()==retriever).map(Map.Entry::getKey).findFirst().orElse("UNKNOWN");}}SQL 示例:多知识源配置表
createtablekb_retriever_config(idbigintprimarykeyauto_increment,retriever_codevarchar(64)notnullunique,retriever_namevarchar(128)notnull,descriptionvarchar(500)notnull,source_typevarchar(32)notnull,statusvarchar(32)notnulldefault'ENABLED',created_timedatetimenotnulldefaultcurrent_timestamp);系统设计时我会优先拆哪几层
路由层
- 多知识源场景下,最先要决定的不是 TopK,而是 query 到底该去哪几个知识库
- LLM 路由适合复杂语义问题,业务规则路由适合强边界场景,很多项目最后是两者混合
聚合重排层
- 多个检索器的候选结果一定要做二次融合,否则最终 prompt 很容易噪音过高
- 重排模型通常比 embedding 相似度更接近最终阅读相关性
注入层
- 最终注入 prompt 的内容不宜过多,否则再好的检索也会被上下文噪音稀释
- 来源 metadata 可以一起注入,后面回答更容易带引用和解释
真正上线时最容易卡住的点
- 所有知识源无脑一起搜,短期看不漏召回,长期看 prompt 噪音会越来越大。
- 只做路由不做重排,最后候选内容还是可能顺序不对,关键信息排不到前面。
- 把路由规则写死在一堆 if else 里,后面知识源一多就很难维护和调优。
监控和指标建议盯哪些
- 各检索器路由命中率
- 重排前后 TopN 质量变化
- 最终注入 prompt 的平均内容数
- 按知识源维度的召回耗时和命中率
如果面试官问我这块怎么设计,我会这样答
如果面试官问多知识源 RAG 怎么做,我会先讲 QueryRouter,再讲 ContentAggregator。LangChain4j 里我会用LanguageModelQueryRouter决定 query 去哪些 retriever,用ReRankingContentAggregator把多个结果重新排序,再交给DefaultRetrievalAugmentor串起整条链路。这样回答既有官方组件,也有真实工程取舍。
结语
高级 RAG 的重点,不在于组件堆得多,而在于路由和重排有没有把知识真正收敛到正确范围里。
如果是你来做,你会更相信模型做知识源路由,还是先写一层业务规则路由?