QAService 编排
Written: 2026.06第 09 章跟敲代码:上一讲:Milvus 混合检索深度解析codealong/chapters/ch09_qaservice_orchestration。 这部分代码是本章跟敲版,用来先跑通核心闭环;完整项目源码仍以本讲后文标注的qa_core/、scripts/等路径为准。
下一讲:RAG Pipeline 主流程深度解析
1. 本讲目标
- 理解 QAService 作为服务编排层(Orchestration Layer)的设计理念
- 掌握”服务门面”模式在 RAG 系统中的应用
- 理解两个核心方法的职责分工
- 理解 Generator(生成器)在流式问答中的角色
2. 前置知识 — 服务编排模式
2.1 什么是服务编排
服务编排(Service Orchestration) 是软件架构中的一种模式:用一个中心化的”编排器”来协调多个子服务的调用顺序和数据流转。 打个比方:- 没有编排:每个厨师自己决定做什么菜、用什么食材、先炒哪个后炒哪个 → 混乱
- 有编排:主厨(编排器)决定菜单、分配任务、协调出菜顺序 → 有序
- 意图识别 → 判断用户想干什么
- 历史读取 → 获取会话上下文
- 查询改写 → 补全追问
- 检索计划 → 决定如何检索
- FAQ 检索 → 查标准答案
- 文档检索 → 查业务资料
- 上下文构建 → 组织参考资料
- LLM 生成 → 产生答案
- 历史写入 → 保存对话
2.2 QAService 不做什么(边界)
3. QAService 的两个核心方法
3.1 方法职责对照
3.2 stream_query() — 唯一主干链路
yield from 是 Python 的委托语法。rag_stream_query 是 qa_core.pipeline.rag 模块中 stream_query 函数的 import alias(from qa_core.pipeline.rag import stream_query as rag_stream_query),它是一个生成器函数,每次 yield 产生一个事件。yield from 把这些事件”透传”给调用方(FastAPI WebSocket 路由),所以 QAService 不需要自己维护生成循环。
3.3 debug_retrieval() — 检索诊断半链路
debug_retrieval() 是检索诊断半链路,它故意从 prepare_retrieval() 开始,以便观察检索类意图、source 推断、按需改写、检索计划和召回质量;线上用户问答仍从 stream_query() 进入,并先执行 decide_route()。因此,调试接口没有走 route=direct_answer / faq_exact / retrieval,并不代表在线主链路跳过查询路由。
3.4 source 白名单校验
- API 层:不应该知道 Milvus 过滤规则(它只管 HTTP 参数校验)
- Retrieval 层:不应该承担业务白名单判断(它只管执行检索)
- QAService(编排层):最清楚”前端筛选项 + 意图推断分类”如何进入主链路
4. Generator 模式在 RAG 中的应用
4.1 什么是 Generator
Generator(生成器)是 Python 的一个核心特性,使用yield 关键字:
4.2 为什么 RAG 适合用 Generator
RAG 的问答过程不是一个”输入→等待→输出”的单步操作,而是一个多阶段持续产出的过程:yield 都是一个可以立即推送给前端的事件。用户不需要等全部流程跑完才能看到任何东西。
4.3 前端接收到的体验
5. 应用工厂模式
5.1 get_qa_service() 工厂函数
settings是只读配置,加载一次即可history是历史存储适配器,本身负责按 session_id 隔离会话- 每次请求创建新的 QAService 会重复加载配置,但没有好处
- QAService 只保存
settings(只读)和history(线程安全适配器) - 请求级变量(query、intent、plan、sources 等)都不在 QAService 上,而在方法局部变量中
5.2 在 API 中使用
6. 错误处理与事件协议
6.1 异常不抛给 WebSocket 路由
6.2 事件类型汇总
| 事件类型 | 含义 | 前端处理 |
|---|---|---|
start | 请求已接收 | 创建答案区域,显示加载状态 |
status | 当前进行到哪个阶段 | 更新进度提示文字 |
token | LLM 生成的一个 token | 追加到答案文本末尾 |
end | 问答完成 | 显示来源引用、诊断信息、耗时 |
error | 可恢复的错误 | 显示错误信息,允许继续提问 |
7. 本讲实践闭环
| 项目 | 内容 |
|---|---|
| 本讲类型 | 系统集成 |
| 实践产物 | QAService、WebSocket stream、检索诊断的服务编排 |
| 是否进入最终项目 | 是 |
| 验收方式 | WebSocket 能持续输出事件,检索诊断能返回命中明细 |
| 后续落点 | 第 10 讲接入完整 Pipeline 事件,第 12 讲纳入 FastAPI 路由 |
7.1 本讲从 0 到 1 实现闭环
这一讲的目标不是再写一个 RAG 算法,而是把前面已经具备的能力包装成一个稳定的应用服务层。实现时按这个顺序推进:- 先定义
QAService,让 API 层以后只调用 service,不直接碰 Milvus、Prompt、Pipeline。 - 实现
stream_query(),把完整 RAG Pipeline 产出的事件原样透传给 WebSocket;问候、越界、转人工等直答也在这条链路内完成。 - 实现
debug_retrieval(),复用检索准备逻辑但不调用最终回答 LLM。 - 最后补一个工厂函数
get_qa_service(),避免每个请求重复初始化 settings、history、retriever。
qa_core/application/service.py::QAService。
qa_core/api/chat.py。
start/status/token/end/error 事件;/api/retrieval/debug 能返回检索诊断信息但不生成最终答案。
来源:命令行验收,对应 scripts/api_e2e_smoke.py。
| 验证项 | 验证方式 | 期望结果 |
|---|---|---|
| WebSocket stream | 连接 /api/stream | 持续收到事件 |
| 检索诊断 | 请求 /api/retrieval/debug | 返回意图、计划、FAQ/Doc 命中 |
| 业务分层 | 查看 chat.py | 路由只调用 service,不写检索细节 |
| 历史保存 | 连续追问 | 下一轮能读取上下文 |
| Trace 记录 | 查看诊断字段 | 有 hit_type、intent、kb_version、data_scope、耗时等信息 |
| 调试入口 | 调用 debug_retrieval() | 只跑检索半链路,不调用最终 LLM |
8. 重点掌握
| 优先级 | 内容 | 原因 |
|---|---|---|
| ★★★ 必会 | QAService 服务编排层的定位:协调意图识别、历史、检索、生成、存储,不直接处理 HTTP 或 Milvus 细节 | 理解”编排层”在分层架构中的角色,面试常问 |
| ★★★ 必会 | 两个核心方法:stream_query(唯一在线问答主干链路,yield from 透传事件)、debug_retrieval(只查不生成) | QAService 对外的完整接口 |
| ★★★ 必会 | Generator(生成器)模式在流式问答中的应用:惰性求值,每个 yield 产生一个可立即推送给前端的事件 | 理解 RAG 流式体验的技术实现 |
| ★★ 理解 | 直答分流放在 Pipeline 主链路中:问候、越界、转人工也通过 WebSocket 事件返回 | 避免多套在线入口导致口径和状态不一致 |
| ★★ 理解 | 单例工厂 get_qa_service() + @lru_cache:只缓存 settings 和 history,请求级状态在局部变量中 | 并发安全的保证 |
| ★★ 理解 | 错误以事件(error 类型)形式返回给前端,不抛异常到 WebSocket 路由 | 用户体验和安全设计 |
| ★ 了解 | source 白名单校验放在 QAService 层的理由 | 理解分层职责的划分依据 |
| ★ 了解 | 事件类型汇总:start / status / token / end / error | 回顾第 11 讲的 WebSocket 事件协议 |
9. 本讲小结
- QAService 是服务编排层,协调意图、历史、检索、生成、存储,但不直接处理 HTTP 或 Milvus 细节
- stream_query 是唯一在线问答主干链路,通过 Generator 持续产出事件
- debug_retrieval 是检索诊断半链路,只查不生成
- yield from 将 RAG Pipeline 的事件透传给调用方
- 单例工厂确保 settings 和 history 只加载一次,请求级状态全在局部变量中
- 错误以事件形式返回,前端可以优雅展示并允许继续提问

