一次提问同时查三个知识库——多通道并行检索架构
开篇引言
上一篇讲完了意图分数到检索动作的映射——SYSTEM 意图短路直接回复,MCP 意图触发工具调用,KB 意图走定向检索或全局兜底。四个电商客服场景跑了一遍,每种意图类型各走一条路径。读者已经知道哪些通道会被激活、每个通道查多少条。
用一句话概括第 9 篇和本篇的分工:第 9 篇解决的是查什么——哪些通道该激活、查哪个 Collection、TopK 多少。本篇解决的是怎么查——通道怎么并行跑、线程池怎么隔离、结果怎么收回来、挂了怎么容错。
来看一个具体场景。假设用户在电商客服界面发了一句:
AirPods 保修多久,顺便退货运费谁出?
经过查询重写拆成两个子问题,意图分类分别命中了 3C 数码 > 保修政策(score=0.88)和 3C 数码 > 退货政策(score=0.85)。第 9 篇告诉我们,定向检索通道被激活了,要查 kb_3c_warranty 和 kb_3c_return 两个 Milvus Collection。分数都在 0.85 以上,全局兜底通道不启用。
两个 Collection 的向量检索各花 50ms 左右。如果串行查,合计 100ms。并行查,50ms 就出结果。快了一倍听起来不错,但问题不只是快不快——
-
通道之间用什么线程池?
-
通道内部多个 Collection 又用什么线程池?会不会互相抢资源?
-
如果
kb_3c_warranty对应的 Milvus 分区正在 compaction 导致超时了怎么办?
这就是多通道并行检索(Multi-Channel Parallel Retrieval)要解决的问题。
总览:MultiChannelRetrievalEngine 的两阶段架构
1. 入口方法
MultiChannelRetrievalEngine 是多通道检索的核心编排者。它的入口方法 retrieveKnowledgeChannels 把整个检索过程分成两个阶段:
@RagTraceNode(name = "multi-channel-retrieval", type = "RETRIEVE_CHANNEL")
public List<RetrievedChunk> retrieveKnowledgeChannels(List<SubQuestionIntent> subIntents, int topK) {
// 构建检索上下文
SearchContext context = buildSearchContext(subIntents, topK);
// 【阶段1:多通道并行检索】
List<SearchChannelResult> channelResults = executeSearchChannels(context);
if (CollUtil.isEmpty(channelResults)) {
return List.of();
}
// 【阶段2:后置处理器链】
return executePostProcessors(channelResults, context);
}
两阶段的职责很清楚:
- 阶段 1:
executeSearchChannels——并行执行所有启用的 Channel,收集每个通道的原始结果 - 阶段 2:
executePostProcessors——串行执行后处理器链,做去重、精排、截断
本篇聚焦阶段 1。阶段 2 的后处理器链(去重 + Cross-Encoder 精排)在第 11 篇详细展开,这里只需要知道它的输入是阶段 1 收回来的所有 Chunk,输出是精选后的最终 Chunk 列表。