第 06 章:检索策略与动态计划¶
本讲目标¶
第 05 章已经完成入口路由:用户问题进来后,系统能判断是直接回答、source 边界提示,还是进入检索链路。
本章继续沿着同一条代码主线往后走:当 RouteDecision.route="retrieval" 时,把 IntentResult 转成一份可执行的 RetrievalPlan。
也就是说,本章要解决的问题是:
既然这个问题需要检索,那 FAQ 查不查?文档查不查?各查多少?阈值多高?是否需要查询变体?
本章不连接 Milvus,不执行真实检索,只生成后续检索会消费的计划参数。
本讲地图¶
本图对应本讲功能闭环,展示从输入到本讲交付物的主干路径。节点与主项目代码文件和函数保持一致,后续章节消费的能力只作为交付边界出现。
图 1:第 06 讲功能闭环地图¶
flowchart TD
C06_INPUT["检索入口<br/>decide_route()"]
C06_INTENT["意图结果<br/>classify_intent()"]
C06_CATEGORY["问题类别<br/>infer_question_category()"]
C06_TABLE["表格偏好<br/>is_table_query()"]
C06_BASE["参数基线<br/>Settings"]
C06_PLAN["计划生成<br/>build_retrieval_plan()"]
C06_OUT{{"章节输出<br/>RetrievalPlan"}}
C06_INPUT --> C06_INTENT
C06_INTENT --> C06_CATEGORY
C06_INTENT --> C06_TABLE
C06_INTENT --> C06_BASE
C06_CATEGORY --> C06_PLAN
C06_TABLE --> C06_PLAN
C06_BASE --> C06_PLAN
C06_PLAN --> C06_OUT
style C06_INPUT fill:#F8FAFC,stroke:#64748B,stroke-width:2px
style C06_INTENT fill:#DBEAFE,stroke:#2563EB,stroke-width:2px
style C06_CATEGORY fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px
style C06_TABLE fill:#FEF3C7,stroke:#D97706,stroke-width:2px
style C06_BASE fill:#DBEAFE,stroke:#2563EB,stroke-width:2px
style C06_PLAN fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px
style C06_OUT fill:#DCFCE7,stroke:#16A34A,stroke-width:2px
节点与代码对齐¶
| 节点 | 对齐文件 | 函数/对象 | 本章职责 |
|---|---|---|---|
| 检索入口 | qa_core/pipeline/steps.py |
decide_route() |
只有 route=retrieval 才继续进入本章逻辑。 |
| 意图结果 | qa_core/intent/classifier.py |
classify_intent() |
沿用第 05 章的 FAQ_QUERY、KNOWLEDGE_QUERY、FOLLOW_UP。 |
| 问题类别 | qa_core/intent/question_category.py |
infer_question_category() |
识别 pricing、compliance、troubleshooting、summary 等风险类别。 |
| 表格偏好 | qa_core/intent/question_category.py |
is_table_query() |
表格/清单/字段类问题禁用模糊 FAQ 直出。 |
| 参数基线 | qa_core/config/settings.py |
Settings |
定义 top_k、阈值、上下文长度和短问题阈值。 |
| 计划生成 | qa_core/retrieval/strategy.py |
build_retrieval_plan() |
按意图、短问题、风险类别、表格偏好逐层调整参数。 |
| 章节输出 | qa_core/retrieval/strategy.py |
RetrievalPlan |
输出 run_faq、run_doc、top_k、阈值和 use_query_variants。 |
和第 05 章的关系¶
第 05 章当前输出两类可执行分支:
| 第 05 章输出 | 系统动作 | 第 06 章是否继续 |
|---|---|---|
route="direct_answer",reason="greeting_rule/human_service_rule/safety_rule" |
问候、转人工、越界等确定性问题,直接返回固定文案 | 不继续 |
route="direct_answer",reason="source_boundary" |
用户选择的 source 与问题明显不匹配,提示切换分类 | 不继续 |
route="retrieval" |
需要进入 RAG 检索链路 | 继续生成 RetrievalPlan |
所以第 06 章不是重新写一套流程,而是在第 05 章的基础上增量开发:
这里要特别区分两件事:source_boundary 是路由阶段的确定性拦截,目的是防止用户选错资料分类;RetrievalPlan 是进入检索后的参数计划,只有 route="retrieval" 的问题才会生成。
动画节点对照¶
第 06 章对应业务流程中的 Stage 2 检索计划部分。下表用于把业务流程位置和本章代码对应起来。
| 动画位置 | 代码节点 | 本章学习内容 |
|---|---|---|
| Stage 1 | decide_route() |
复用第 05 章入口路由,只接住 route="retrieval" |
| Stage 2 | classify_intent() |
复用第 05 章意图分类,得到 IntentResult |
| Stage 2 | build_retrieval_plan() |
本章新增,把意图变成检索计划 |
| Stage 2 | run_faq/run_doc/top_k/threshold |
本章新增,决定后续如何检索 |
| Stage 2 | use_query_variants |
本章只给出开关,第 07 章实现查询变体 |
本章涉及的项目代码¶
本章讲的是主项目中的检索计划生成逻辑,核心文件如下:
| 顺序 | 文件 | 只看什么 |
|---|---|---|
| 1 | scripts/demo_query_prepare.py --plan-only |
命令行入口,只看 route、intent、retrieval_plan 三层输出 |
| 2 | qa_core/pipeline/steps.py |
第 05 章入口路由,只有 retrieval 才进入本章逻辑 |
| 3 | qa_core/intent/classifier.py |
第 05 章意图分类,输出 IntentResult |
| 4 | qa_core/intent/question_category.py |
问题类别和表格问题识别 |
| 5 | qa_core/retrieval/strategy.py |
本章核心:生成 RetrievalPlan |
| 6 | tests/test_retrieval_and_prompt.py |
主项目测试,确认检索计划和后续上下文行为 |
这就是本章在主项目中的代码执行主线:
RetrievalPlan 是什么¶
RetrievalPlan 是后续检索阶段要读取的参数包。后面的代码不需要到处写 if intent == ...,而是统一消费这份计划。
它定义在:
核心字段如下:
| 字段 | 含义 |
|---|---|
run_faq |
是否检索 FAQ 集合 |
run_doc |
是否检索文档集合 |
faq_top_k |
FAQ 初始召回数量 |
doc_top_k |
文档初始召回数量 |
rerank |
后续是否进入重排 |
faq_direct_threshold |
FAQ 相似直出的保护阈值 |
final_context_top_n |
最终进入 Prompt 的上下文条数 |
min_context_score |
上下文最低相关性分数 |
max_context_chars |
上下文总字符上限 |
max_context_doc_chars |
单条文档字符上限 |
use_query_variants |
第 07 章是否生成查询变体 |
question_category |
问题类别 |
prefer_table |
是否偏向表格、清单、字段类资料 |
faq_direct_exact_only |
是否只允许精确 FAQ 直出 |
reason |
本次计划的原因标签 |
source 不放在 RetrievalPlan 中。它属于检索过滤范围,而不是检索策略本身:前端显式选择的 source_filter 优先,其次才使用 IntentResult.suggested_source。第 08 章真正执行 Milvus 检索时,再把最终生效的 source 转成过滤条件。
FAQ/Doc 检索和 Hybrid Search 的边界
run_faq/run_doc 控制的是业务上的两路/分层检索:是否查 FAQ collection、是否查 Doc collection。它不是 Milvus Hybrid Search 的定义。
第 08 章的 Hybrid Search 指每个被执行的 collection 内部用 Dense 向量召回 + BM25 Sparse 关键词召回做融合排序。真实业务中,企业问答常见默认策略是 FAQ 和 Doc 都查,但最终仍要服从 RetrievalPlan:直答类问题不查,FAQ-only 或 Doc-heavy 问题也可以只查一路或偏向一路。
代码执行主线¶
1. 运行入口¶
scripts/demo_query_prepare.py --plan-only 的核心逻辑是:
这段代码说明本章的边界非常清楚:
direct_answer:已经有答案或边界提示,不生成计划retrieval:继续意图分类,并生成检索计划--plan-only:只看第 06 章交付物,不执行第 07 章的追问改写和查询变体
完整在线链路里的 decide_route() 还会尝试 FAQ 精确 fast path;那一步会访问 FAQ collection。第 06 章为了聚焦“计划怎么生成”,验证脚本只保留确定性路由预览,不执行 Milvus 检索。
2. 问题类别识别¶
qa_core/intent/question_category.py 负责识别问题类型:
这些类别不是新的用户意图,而是检索策略的风险标签。
这里要先把三个维度分清楚,否则很容易觉得参数没有感觉:
| 维度 | 代码字段 | 解决的问题 | 例子 |
|---|---|---|---|
| 用户意图 | intent.intent |
这类问题更像 FAQ、知识查询,还是追问? | FAQ_QUERY、KNOWLEDGE_QUERY、FOLLOW_UP |
| 风险标签 | question_category |
这类问题错答成本高不高,需不需要更谨慎? | pricing、compliance、troubleshooting |
| 资料形态 | prefer_table |
这类问题是不是更依赖表格、清单、字段、行记录? | true / false |
它们不是互斥关系,而是可以叠加。比如:
这句话可能同时触发:
- 意图:像 FAQ 查询,所以先偏向 FAQ。
- 风险:包含“费用、5000、审批”,所以进入
pricing保护。 - 资料形态:如果问到清单、字段、明细表,还会继续触发
prefer_table=true。
所以最后的 reason 可能不是一个词,而是一串策略叠加结果,例如:
读 reason 时,要从左到右理解为:先按意图定主方向,再叠加短问题、风险类别、表格偏好的保护规则。
| 类别 | 典型问题 | 策略倾向 |
|---|---|---|
default |
普通业务问题 | 使用默认检索计划 |
pricing |
费用、金额、报销、付款 | 提高 FAQ 直出门槛,扩大候选 |
compliance |
合规、隐私、合同、审计 | 使用更高保护阈值 |
troubleshooting |
报错、失败、异常、排查 | 扩大文档候选,保留更多步骤 |
summary |
总结、归纳、对比、大纲 | 扩大文档候选,覆盖更多资料 |
表格类问题由 is_table_query() 判断,例如清单、台账、字段、金额、责任人、付款节点等。
这里的“表格类问题”不是指第 06 章已经去解析 Excel,也不是指用户问题里一定出现了 .xlsx 文件。它指的是:用户问法明显依赖某个清单、台账、字段、行记录或明细项,答案很可能藏在结构化资料的一行或一列里。
| 不是表格类问题 | 是表格类问题 |
|---|---|
报销流程是什么 |
报销材料清单里发票字段怎么填 |
VPN 连不上怎么处理 |
故障台账里的责任人字段是谁 |
合同审批流程有哪些步骤 |
付款节点明细表里超过 5000 的审批要求是什么 |
第 06 章只负责识别这种问题形态,并把保护信号写进 RetrievalPlan;真正的表格文件加载、切分和入库在第 16 章,真正按计划执行 Milvus 检索在第 08 章。
3. 检索计划规则表¶
build_retrieval_plan() 不是一次性返回固定参数,而是先生成默认参数,再按固定顺序应用规则补丁。当前代码用 PlanPatch 描述每条规则要改哪些字段,用 _apply_plan_rules() 统一执行规则。
这样做的好处是:参数规则集中在表里,主函数只负责识别问题形态和组装 RetrievalPlan,不会在多个长分支之间来回跳。
规则一:按意图调整¶
| intent | 调整结果 |
|---|---|
FAQ_QUERY |
FAQ 优先,文档候选减半,降低基础 FAQ 直出阈值 |
KNOWLEDGE_QUERY |
扩大文档候选,增加最终上下文数量 |
FOLLOW_UP |
扩大 FAQ 和文档候选,提高 FAQ 直出阈值,打开查询变体 |
GREETING/HUMAN_SERVICE/OUT_OF_SCOPE |
关闭 FAQ 和文档检索 |
本章正常情况下只会给 route="retrieval" 的问题生成计划。保留直答类处理,是为了让 build_retrieval_plan() 单独调用时仍然有明确行为。
规则二:短问题保护¶
短问题信息少,更容易误命中。比如:
如果它不是追问,本章会:
- 收缩文档候选
- 提高 FAQ 直出门槛
- 在
reason中追加short_query_guard
追问不套用这个保护,因为第 07 章会先结合历史问题做改写。
规则三:风险类别保护¶
费用、合规、排障、总结类问题更需要谨慎。
| 类别 | 策略变化 |
|---|---|
pricing |
扩大 FAQ 和文档候选,FAQ 直出阈值至少 0.84 |
compliance |
扩大文档候选,FAQ 直出阈值至少 0.86 |
troubleshooting |
扩大文档候选和上下文数量 |
summary |
扩大文档候选和上下文数量 |
规则四:表格偏好¶
表格类问题通常要定位具体行列,例如:
这类问题最怕“看起来差不多”的误命中。比如 FAQ 里有“报销材料需要哪些”,但用户真正问的是“材料清单里的付款金额字段”,两者都和报销材料相关,却不是同一个答案。
所以本层会把检索计划改得更保守:
| 策略变化 | 含义 | 后续影响 |
|---|---|---|
| 扩大文档候选 | 不只看少量 FAQ 候选 | 第 08 章检索时给文档集合更多召回机会 |
| 增加最终上下文数量 | 给 LLM 更多证据 | 第 10/11 章生成答案时更容易引用到正确行或字段 |
prefer_table=True |
标记这是表格、清单、字段类问题 | 后续上下文选择时优先保留表格化资料 |
faq_direct_exact_only=True |
禁止模糊 FAQ 高分直接返回 | 第 09/10 章只有精确 FAQ 命中才允许直出 |
换句话说,表格偏好不是“现在查表”,而是告诉后续链路:这一问要谨慎,宁愿多查一点证据,也不要因为一个相似 FAQ 分数高就直接回答。
五类问题的参数体感¶
理解第 06 章时,可以把参数看成五个旋钮:
| 旋钮 | 变大或打开以后意味着什么 | 代价 |
|---|---|---|
faq_top_k |
FAQ 候选更多,更不容易漏掉标准问答 | 后续重排和判断成本更高 |
doc_top_k |
文档候选更多,更适合复杂知识问题 | 噪声更多,检索和重排更慢 |
faq_direct_threshold |
FAQ 直出更谨慎,分数必须更高 | 可能少一些快速直出 |
final_context_top_n |
给 LLM 的证据更多 | Prompt 更长,答案可能更慢 |
use_query_variants |
第 07 章会生成查询变体扩大召回 | 多一次改写/变体成本 |
下面把五类常见问题放到同一张表里对比。
| 问题类型 | 典型问法 | 核心参数变化 | 设计目的 |
|---|---|---|---|
| FAQ 查询 | 异地入职材料办理时需要准备哪些资料和审批信息 |
doc_top_k 从 20 收到 10;use_query_variants=false;基础 FAQ 直出阈值从 0.72 降到 0.64 |
先相信标准 FAQ,文档只作为兜底证据,避免简单问题走复杂链路 |
| 知识查询 | 公司会议室预约规则在哪里查看以及需要遵守哪些流程要求 |
doc_top_k 提到 24;final_context_top_n 提到 5;use_query_variants=true |
问题通常需要多段资料拼接,先扩大文档召回,再由后续章节生成更完整答案 |
| 追问 | 那审批呢,历史问题是 报销流程是什么 |
faq_top_k=24;doc_top_k=24;faq_direct_threshold 至少 0.78;use_query_variants=true |
当前问题信息不足,必须依赖历史改写,不能被短词误命中后直接回答 |
| 费用类问题 | 报销费用超过5000需要谁审批 |
doc_top_k 至少 24;final_context_top_n 至少 6;faq_direct_threshold 至少 0.84 |
金额、报销、付款类问题错答成本高,宁愿多找证据,也不轻易 FAQ 模糊直出 |
| 表格类问题 | 材料清单里的付款金额字段是什么 |
prefer_table=true;faq_direct_exact_only=true;final_context_top_n 至少 7 |
这类问题常藏在表格行、清单字段或台账明细里,必须抑制“相似 FAQ 直接回答” |
这张表要注意两点。
第一,FAQ 查询不等于只查 FAQ。本项目默认仍然允许 run_doc=true,只是把文档候选收小,让文档作为兜底;真正是否返回 FAQ 标准答案,要等第 08/09/10 章拿到真实 FAQ hit 后判断。
第二,费用类和表格类是叠加保护,不是新的主意图。一个问题可以既是 FAQ 查询,又是费用类问题,还可以同时是表格类问题。最终计划会把这些规则叠加起来。
参数叠加示例¶
假设默认配置是:
普通 FAQ 问题:
计划会偏向 FAQ:
这表示:标准 FAQ 很可能能回答,所以文档候选先收小;问题本身已经完整,不需要第 07 章生成查询变体。
如果问题变成:
计划会叠加费用保护:
这表示:虽然它看起来仍像 FAQ,但涉及费用和审批,不能轻易模糊直出;要多召回文档证据,给后续答案生成更多上下文。
如果问题再变成:
计划会继续叠加表格保护:
这表示:答案更可能来自某个清单字段或表格行。即使 FAQ 相似度高,也不能只靠“相似”直接返回,除非是精确 FAQ 命中。
典型运行结果¶
在项目根目录执行以下命令。第 06 章统一带上 --plan-only,这样输出会停在 RetrievalPlan,不会调用第 07 章的 LLM 改写或查询变体。
FAQ 查询¶
关键输出:
知识查询¶
关键输出:
追问¶
关键输出:
费用类问题¶
关键输出:
表格类问题¶
关键输出:
这段输出可以这样读:
| 字段 | 说明 |
|---|---|
prefer_table=true |
这个问题像是在问清单、字段或表格行,后续证据选择要偏向表格化资料。 |
faq_direct_exact_only=true |
FAQ 不能只因为相似度高就直接返回,必须是精确匹配才允许直出。 |
final_context_top_n=7 |
最终给 LLM 的上下文条数增加,避免漏掉正确行或字段。 |
reason 包含 table_row_preferred |
诊断标签,说明本轮计划触发了表格/行记录保护。 |
这里要明确一个边界:第 06 章只输出计划,不负责加载表格文件,也不负责执行检索;它把“这可能是表格类问题”的信号传给第 08 章及后续 Pipeline。
容易混淆的边界¶
| 问题 | 正确理解 |
|---|---|
run_faq/run_doc 是不是 Hybrid Search? |
不是。它决定查 FAQ collection 还是 Doc collection;第 08 章每个 collection 内部的 Dense + BM25 才是 Milvus Hybrid Search。 |
为什么 source 不在 RetrievalPlan 里? |
source 是数据过滤范围,来自前端选择或意图推断;RetrievalPlan 是检索策略参数,两者职责不同。 |
faq_direct_threshold 是不是本章会直接返回 FAQ? |
不是。本章只计算阈值;是否直出要等第 08/09/10 章拿到真实 FAQ hit 后再判断。 |
use_query_variants=true 是不是本章已经生成变体? |
不是。本章只打开开关;第 07 章才真正改写追问并生成查询变体。 |
prefer_table=true 是不是本章已经读取 Excel? |
不是。本章只识别“像表格/清单/字段类问题”;第 16 章负责多格式文件加载和表格行入库。 |
参数数字怎么解释¶
本章参数对齐主项目默认配置,但这些数字不是官方标准,也不是固定承诺。
| 参数 | 默认值 | 参数理解 |
|---|---|---|
faq_top_k |
20 | FAQ 先召回一批候选,后续再精筛 |
doc_top_k |
20 | 文档先召回一批候选,避免过早漏召回 |
faq_short_query_top_k |
30 | 短问题信息少,FAQ 多取一些候选 |
doc_complex_query_top_k |
24 | 复杂知识问题需要更多文档候选 |
faq_direct_score_threshold |
0.72 | FAQ 相似直出的基础保护线 |
0.78 |
短问题保护线 | 短问题更容易误命中,所以阈值更高 |
0.84 |
费用类保护线 | 金额、报销、付款类问题更谨慎 |
0.86 |
合规类保护线 | 合规、隐私、合同类问题更谨慎 |
这些值来自当前项目样例和默认配置。真实项目上线时,需要结合评测集、召回率、误直出率、延迟和人工抽检继续校准。
测试¶
测试应该覆盖:
- 第 05 章入口路由仍然正常
- FAQ_QUERY 会生成 FAQ 优先计划,不在本章本地返回标准答案
- FAQ 查询生成 FAQ 优先计划
- 知识查询扩大文档候选并打开查询变体
- 追问扩大候选并要求第 07 章改写
- 短问题提高 FAQ 直出门槛
- 费用/合规类问题提高保护阈值
- 表格类问题偏向表格行资料
本章小结¶
本章完成了从“用户意图”到“检索计划”的转换。
第 05 章解决:
第 06 章解决:
下一章会继续使用 IntentResult.requires_rewrite 和 RetrievalPlan.use_query_variants,实现追问改写和查询变体生成。