执行器注册表与模式分发机制
在之前的文档中用了很多篇幅把 BusinessChatService#prepareExecutionPlan 给讲解完毕了,接下来依旧还是在 BusinessChatService#buildConversationExecution 中,再回顾下此方法的流程
buildConversationExecution
/**
* 构建完整的会话执行流。
* 执行顺序大致为:发送思考提示 -> 准备执行计划 -> 选择执行器 -> 消费执行器输出 -> 成功/失败收尾。
*
* @param taskInfo 当前任务
* @return 真正驱动模型输出的执行流
*/
private Flux<String> buildConversationExecution(TaskInfo taskInfo) {
return Flux.defer(() -> {
// 在真正出字前先给前端一个可见状态,减少用户等待时的空白感。
safeEmit(taskInfo.sink(), streamEventWriter.thinking("正在分析问题上下文。", taskInfo.eventMetadata()));
// 执行计划的准备可能包含检索、摘要、编排等阻塞操作,因此切到弹性线程池执行。
return Mono.fromCallable(() -> prepareExecutionPlan(taskInfo))
.subscribeOn(Schedulers.boundedElastic())
.flatMapMany(plan -> {
// 根据执行模式选择对应执行器,例如澄清、开放问答、文档问答等不同路径。
ConversationExecutor executor = conversationExecutorRegistry.get(plan.getMode());
return executor.execute(taskInfo);
});
})
.publishOn(Schedulers.boundedElastic())
// 每收到一段模型输出,就实时累计答案并向前端推送,形成流式体验。
.doOnNext(chunk -> emitModelChunk(taskInfo, chunk))
// 统一把异常收敛到失败收尾逻辑,确保状态落库和资源释放不会漏掉。
.doOnError(error -> finishWithFailure(taskInfo, error))
// 执行流自然结束时走成功态收尾逻辑,例如补发引用、推荐问题和最终归档。
.doOnComplete(() -> finishSuccessfully(taskInfo));
}
这段代码做了四件事:
- 发送 thinking 状态——先告诉前端"我正在分析",避免用户看到一片空白
- 准备执行计划——
prepareExecutionPlan(taskInfo)会完成问题改写、历史压缩、模式判定等一系列准备工作,用了大量的篇幅进行了讲解 - 选择并执行执行器——这就是我们要本章节重点要看的内容
- 挂载收尾逻辑——不管成功还是失败,都会有对应的收尾处理
这篇就来拆解这个过程:系统是怎么根据不同的问答模式,把请求分发到不同执行器上的?当你在前端选了"当前文档问答"并指定了某个文档,为什么最终会走到 RagChatExecutor 的 execute 方法?
其中最关键的就是这两行:
ConversationExecutor executor = conversationExecutorRegistry.get(plan.getMode());
return executor.execute(taskInfo);
第一行从注册表里根据执行模式拿到对应的执行器实例,第二行调用执行器的 execute 方法开始真正的问答流程。
要理解这个过程,我们需要搞清楚三个东西:ExecutionMode 枚举有哪些模式?ConversationExecutor 接口长什么样?ConversationExecutorRegistry 是怎么把它们串起来的?
ExecutionMode:执行模式枚举
public enum ExecutionMode {
/**
* 结构图直答模式。
*
* <p>适用于用户只问文档结构关系的问题,例如“这个章节包含哪些小节”、“上一节/下一节是什么”、
* “某章节属于哪个父章节”。该模式通常由 {@code GraphOnlyExecutor} 执行,只查询结构图中的章节、
* 父子关系或兄弟关系,不再进入向量/关键词证据检索,也不调用大模型生成长答案。</p>
*/
GRAPH_ONLY,
/**
* 结构图定位后取证模式。
*
* <p>适用于问题需要先通过结构图定位章节,再读取章节正文或编号项证据的场景,例如“某章节第 3 步是什么”、
* “哪一步要求执行某个动作”。该模式由 {@code GraphThenEvidenceExecutor} 执行,会先根据导航锚点找到
* 目标章节,再在章节树内递归查找 item、关键词命中的步骤或章节正文,最后把结构化证据渲染成回答。</p>
*/
GRAPH_THEN_EVIDENCE,
/**
* 普通知识库检索问答模式。
*
* <p>适用于大多数需要基于知识文档内容回答的问题。该模式由 {@code RagChatExecutor} 执行,会根据规划阶段
* 得到的检索问题、子问题、文档范围,走向量检索、关键词检索、RRF 融合、父块提升、可选 rerank、Prompt
* 预算组装,然后调用模型基于证据流式生成答案。</p>
*/
RETRIEVAL,
/**
* 开放式 ReAct Agent 模式。
*
* <p>适用于固定 RAG 或结构图路径无法覆盖的问题,或者需要 Agent 自主判断是否调用工具的场景。
* 该模式由 {@code ReactAgentExecutor} 执行,会把规划后的 agentQuestion 交给 ReAct Agent,
* 由 Agent 自主进行推理、工具调用和最终回答输出。</p>
*/
REACT_AGENT,
/**
* 澄清模式。
*
* <p>适用于路由阶段发现候选文档、知识范围或用户意图存在歧义,暂时不能稳定选择某个执行路径的场景。
* 该模式由 {@code ClarificationExecutor} 执行,不进行检索或模型生成,而是直接返回澄清问题,
* 引导用户补充更明确的文档名、主题或关键词。</p>
*/
CLARIFICATION,
/**
* 旧版 RAG 对话模式。
*
* <p>该枚举值已废弃,保留它主要是为了兼容历史数据、历史配置或旧路由结果。新的普通知识库问答应使用
* {@link #RETRIEVAL},不要再为新逻辑依赖该模式。</p>
*/
@Deprecated
RAG_CHAT
}
每个枚举值代表一种不同的问答路径:
| 模式 | 说明 |
|---|---|
RETRIEVAL | 知识检索问答,标准的 RAG 路径,先检索文档再基于证据回答 |
GRAPH_ONLY | 仅用结构图直接回答,不需要文档检索 |
GRAPH_THEN_EVIDENCE | 先用结构图定位,再取证补充 |
REACT_AGENT | ReAct Agent 自主执行,可以调用工具 |
CLARIFICATION | 路由歧义时向用户发起澄清 |
RAG_CHAT | 已废弃的旧模式 |
重点
当用户在前端选择 "当前文档问答" 模式时,前置编排阶段(prepareExecutionPlan)会把执行计划的 mode 设为 ExecutionMode.RETRIEVAL。这个值决定了后续会走到哪个执行器。
付费内容提示
该文档的全部内容仅对「JavaUp项目实战&技术讲解」知识星球用户开放
加入星球后,你可以获得:
- 超级八股文:100万+字的全栈技术知识库,涵盖技术核心、数据库、中间件、分布式等深度剖析的讲解
- 讲解文档:超级AI智能体、黑马点评Plus、大麦、大麦pro、大麦AI、流量切换、数据中台的从0到1的详细文档
- 讲解视频:超级AI智能体、黑马点评Plus、大麦、大麦pro、大麦AI、流量切换、数据中台的核心业务详细讲解
- 1 对 1 解答:可以对我进行1对1的问题提问,而不仅仅只限于项目
- 针对性服务:有没理解的地方,文档或者视频还没有讲到可以提出,本人会补充
- 面试与简历指导:提供面试回答技巧,项目怎样写才能在简历中具有独特的亮点
- 中间件环境:对于项目中需要使用的中间件,可直接替换成我提供的云环境
- 面试后复盘:小伙伴去面试后,如果哪里被面试官问住了,可以再找我解答
- 远程的解决:如果在启动项目遇到问题,本人可以帮你远程解决
进入星球后,即可享受上述所有服务,保证不会再有其他隐藏费用。
