第 06 章:检索策略与动态计划¶
上一章:意图分类与路由入口 下一章:查询改写与变体生成
本章目标¶
第 05 章已经完成入口路由:用户问题进来后,系统能判断是直接回答、source 边界提示,还是进入检索链路。
本章继续沿着同一条代码主线往后走:当 RouteDecision.route="retrieval" 时,把 IntentResult 转成一份可执行的 RetrievalPlan。
也就是说,本章要解决的问题是:
既然这个问题需要检索,那 FAQ 查不查?文档查不查?各查多少?阈值多高?是否需要查询变体?
本章不连接 Milvus,不执行真实检索,只生成后续检索会消费的计划参数。
本章主线¶
第 06 章只做一件事:把已经进入 route="retrieval" 的问题,从 IntentResult 转成 RetrievalPlan。
它不重新判断问候、越界、转人工,也不连接 Milvus、不生成答案。第 05 章负责入口判断,第 06 章只接住需要检索的问题,决定后续 FAQ 和文档应该怎么查。
第 05 章:decide_route() / classify_intent()
-> route="retrieval"
-> IntentResult(intent, rule_score, confidence, requires_rewrite, suggested_source)
第 06 章:build_retrieval_plan()
-> RetrievalPlan(run_faq, run_doc, top_k, threshold, query_variant 开关)
第 07/08/10 章:
-> 追问改写和查询变体
-> FAQ/Doc 检索和重排
-> Prompt 组装和答案生成
本章围绕五个输入信号展开。读后面的参数时,不要把它们当成零散规则;它们都在回答同一个问题:当前请求应该更快直出,还是更谨慎地多召回证据?
| 输入信号 | 本章如何使用 | 影响 |
|---|---|---|
intent.intent |
决定主检索方向:FAQ 优先、文档增强、追问扩召回 | faq_top_k、doc_top_k、use_query_variants |
intent.rule_score |
保留规则候选强弱,主要用于诊断 | intent_rule_score |
intent.confidence |
判断规则/模型仲裁后稳不稳 | FAQ 直出阈值、是否只允许精确 FAQ 直出 |
question_category |
判断错答风险类型 | 费用、合规、排障、总结类问题更保守 |
prefer_table |
判断是否依赖清单、字段、行记录 | 扩大文档候选,禁用模糊 FAQ 直出 |
本章对应业务流程中的 Stage 2 检索计划节点。如果只想抓主线,按下面顺序看就够了:
| 顺序 | 文件 | 只看什么 |
|---|---|---|
| 1 | scripts/demo/demo_query_prepare.py --plan-only |
route、intent、retrieval_plan 三层输出 |
| 2 | qa_core/pipeline/steps.py |
route="retrieval" 后才进入本章逻辑 |
| 3 | qa_core/retrieval/strategy.py |
build_retrieval_plan() 和规则叠加顺序 |
| 4 | qa_core/intent/question_category.py |
风险类别和表格问题识别 |
| 5 | 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 直出 |
intent_rule_score |
规则候选分数,来自 IntentResult.rule_score |
intent_decision_score |
网关最终决策分数,来自 IntentResult.confidence,用于保守检索保护 |
reason |
本次计划的原因标签 |
source 不放在 RetrievalPlan 中。它属于检索过滤范围,而不是检索策略本身:前端显式选择的 source_filter 优先,其次才使用 IntentResult.suggested_source。第 08 章真正执行 Milvus 检索时,再把最终生效的 source 转成过滤条件。
这里有三个分数必须分清楚:
| 分数字段 | 所在阶段 | 含义 |
|---|---|---|
intent.rule_score |
意图/检索计划阶段 | 入口规则候选强弱,用于诊断和规则候选排序 |
intent.confidence / plan.intent_decision_score |
意图/检索计划阶段 | 规则候选和模型候选仲裁后的最终决策分,影响 FAQ 直出阈值和召回策略 |
RetrievalHit.score / sources[*].score |
FAQ/Doc 检索阶段 | 候选内容的检索相关性排序分 |
answer_confidence.evidence_confidence.score |
生成前证据评估阶段 | 综合 top-1 检索分、上下文数量、来源数量、意图决策分和追问改写后的证据置信度 |
answer_confidence.generation_verification.score |
LLM 生成后核验阶段 | 检查最终答案的行内引用覆盖、引用编号合法性和上下文词面支撑度 |
answer_confidence.score |
最终答案收口阶段 | 取证据置信度和生成核验分中更保守的一侧,作为最终展示和 Trace 使用的综合置信度 |
第 06 章只负责意图/检索计划分数。检索候选 score 在第 08 章 Milvus Hybrid Search 和 reranker 中产生;answer_confidence 在第 10 章 RAG Pipeline 收口时生成。
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 问题也可以只查一路或偏向一路。
代码执行时序图¶
这章最适合按 build_retrieval_plan() 的真实执行顺序阅读。顺序不是“先看参数,再看规则”,而是先识别问题形态,再按固定层叠加补丁,最后产出不可变的 RetrievalPlan。
sequenceDiagram
autonumber
participant Prep as prepare_retrieval()
participant Plan as build_retrieval_plan()
participant Cat as infer_question_category()/is_table_query()
participant Base as _base_params()
participant Rules as _apply_plan_rules()
participant Intent as _intent_rules()
participant Guard as _rule_score_guard()
participant CatRules as _category_rules()
Prep->>Plan: build_retrieval_plan(rewritten_query, intent)
Plan->>Cat: infer_question_category(query)
Cat-->>Plan: question_category
Plan->>Cat: is_table_query(query)
Cat-->>Plan: prefer_table
Plan->>Base: _base_params(settings, is_short)
Base-->>Plan: default params
Plan->>Rules: _apply_plan_rules(params, intent, is_short, question_category, prefer_table)
Rules->>Intent: _intent_rules(settings)
Intent-->>Rules: FAQ_QUERY / KNOWLEDGE_QUERY / FOLLOW_UP patches
Rules->>Rules: 先按 intent 叠加召回量和阈值
Rules->>Guard: _rule_score_guard(settings, rules, intent.confidence)
Guard-->>Rules: 低分/中分保护补丁
Rules->>CatRules: _category_rules(settings)
CatRules-->>Rules: pricing / compliance / troubleshooting / summary 补丁
Rules->>Rules: prefer_table 时再收紧表格策略
Rules-->>Plan: params + reason
Plan-->>Prep: RetrievalPlan
读图重点: 1. build_retrieval_plan() 只在 route="retrieval" 后执行,不承担直答分支。 2. 检索参数不是一次算完,而是默认值叠加意图、短问题、分数、类别和表格补丁。 3. intent.confidence 会继续影响 FAQ 直出门槛和文档召回范围。
单独查看:打开可缩放时序图。
检索计划如何生成¶
1. 从 route="retrieval" 开始¶
scripts/demo/demo_query_prepare.py --plan-only 的核心逻辑是:
route = decide_low_cost_route(query, scenario, source_filter)
if route.route == "retrieval":
intent = classify_intent(query, history, scenario)
plan = build_retrieval_plan(query, intent)
这段代码说明本章的边界非常清楚:
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 |
| 规则候选分 | intent.rule_score |
入口规则候选强不强? | 0.60 兜底、0.82+ 强规则 |
| 决策分数 | intent.confidence |
规则/模型仲裁后是否稳定,是否需要更保守地直出? | 0.68 冲突降分、0.99 规则模型一致 |
| 风险标签 | 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() 统一执行规则。
默认参数
↓
意图规则 _intent_rules()
↓
短问题保护规则
↓
决策分数保护 _rule_score_guard()
↓
问题类别规则 _category_rules()
↓
表格偏好规则
↓
RetrievalPlan
这样做的好处是:参数规则集中在表里,主函数只负责识别问题形态和组装 RetrievalPlan,不会在多个长分支之间来回跳。
规则一:按意图调整¶
| intent | 调整结果 |
|---|---|
FAQ_QUERY |
FAQ 优先,文档候选减半,降低基础 FAQ 直出阈值 |
KNOWLEDGE_QUERY |
扩大文档候选,增加最终上下文数量 |
FOLLOW_UP |
扩大 FAQ 和文档候选,提高 FAQ 直出阈值,打开查询变体 |
build_retrieval_plan() 只服务 route="retrieval" 的检索类问题。问候、转人工、越界和 source 边界会在 decide_route() 阶段直接返回,不会生成 RetrievalPlan。如果把直答类 IntentResult 误传进来,函数会直接报错,让职责错误尽早暴露。
规则二:短问题保护¶
短问题信息少,更容易误命中。比如:
如果它不是追问,本章会:
- 收缩文档候选
- 提高 FAQ 直出门槛
- 在
reason中追加short_query_guard
追问不套用这个保护,因为第 07 章会先结合历史问题做改写。
规则三:决策分数保护¶
IntentResult.rule_score 只表示规则候选强弱;IntentResult.confidence 表示规则候选和模型候选经过网关仲裁后的最终决策分。它不是概率,也不是向量相似度。检索计划直接消费的是 confidence,并把它记录为 intent_decision_score:
下面的保护线来自 config/rules.toml 的 [retrieval_strategy],表格中的数字是项目默认配置。调参时优先改配置,再用测试和评测集验证效果。
| 分数区间 | 典型来源 | 检索策略 |
|---|---|---|
< 0.70 |
default_knowledge 兜底,或规则/模型冲突后降分 |
提高 FAQ 直出阈值到至少 0.86,扩大文档候选和上下文,只允许精确 FAQ 直出 |
0.70 - 0.82 |
追问或中等确定规则 | FAQ 直出阈值至少 0.82,保持足够文档候选 |
>= 0.82 |
规则/模型一致,或高分稳定判断 | 不额外加决策分数保护,继续按意图、风险和表格规则叠加 |
这一层解决的问题是:入口越不确定,后续越不能轻易因为一个相似 FAQ 分数高就直接返回答案。它在短问题保护之后执行,所以低分兜底会覆盖短句收缩策略,优先保证证据召回。
决策分数的校准评测¶
0.70、0.82、0.86 这些值不能只靠代码注释解释。它们需要通过业务样本验证:当前分数是否真的让检索计划变得更保守,是否避免了 FAQ 误直出,是否没有把高确定 FAQ 问题过度复杂化。
本项目新增了轻量校准脚本:
python scripts\intent\evaluate_intent_policy.py --output reports\intent_policy\intent_policy_latest.json --fail-on-critical
它不连接 Milvus,也不调用 LLM,只执行:
normalize_user_query()
classify_direct_intent()
classify_intent()
apply_intent_decision_gateway()
build_retrieval_plan()
build_answer_prompt_profile()
每条样本会同时检查第 05 章和第 06 章之间的关键契约:
| 检查项 | 说明 |
|---|---|
route |
问候、越界、转人工是否在检索前收口 |
intent |
最终采用的 FAQ / KNOWLEDGE / FOLLOW_UP 是否正确 |
effective_source |
source 推断是否会进入正确 Milvus 过滤范围 |
requires_rewrite |
追问是否触发后续改写 |
decision_policy |
规则和模型仲裁策略是否符合预期 |
rule_score / confidence |
规则候选分和最终决策分是否落在预期区间 |
RetrievalPlan |
faq_direct_exact_only、faq_direct_threshold、use_query_variants 等是否正确 |
Prompt Profile |
费用、合规、追问等是否进入正确回答模板 |
例如低分兜底样本:
{
"question": "帮我分析一下这个问题",
"expected_confidence_max": 0.7,
"expected_plan_contains": {
"faq_direct_exact_only": true,
"faq_direct_threshold": 0.86
}
}
这条样本用来证明:低分不是只被记录,而是会真正触发“只允许精确 FAQ 直出 + 提高直出门槛 + 扩大文档证据”的保守策略。
规则四:风险类别保护¶
费用、合规、排障、总结类问题更需要谨慎。
这些风险保护线同样来自 config/rules.toml,目的是让不同业务风险可以独立调参。
| 类别 | 策略变化 |
|---|---|
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 和文档算一个总分,然后二选一。它做的是:先生成默认计划,再根据意图和风险保护去调几个旋钮。
默认计划可以理解成:
也就是说,系统默认不是只查 FAQ,也不是只查文档,而是 FAQ 和文档都查一批,再交给后续检索、重排、上下文构建和答案生成继续判断。
1. FAQ 优先¶
FAQ 优先通常来自 intent=FAQ_QUERY。它的意思是:问题像一个标准问答,先相信 FAQ,但仍保留少量文档兜底。
| 字段 | 变化 | 含义 |
|---|---|---|
run_faq |
保持 true |
继续查 FAQ |
run_doc |
保持 true |
文档不关闭,只做兜底 |
doc_top_k |
从 20 收到 10 | 少拿一些文档,降低噪声和成本 |
faq_direct_threshold |
从 0.72 降到 0.64 | FAQ 更容易标准直出 |
use_query_variants |
false |
标准问法不需要额外扩写 |
一句话:FAQ 优先 = 标准 FAQ 更有机会快速回答,文档只是兜底证据。
2. 文档优先¶
文档优先通常来自 intent=KNOWLEDGE_QUERY。它的意思是:问题不像一个单条 FAQ 能回答,需要从制度、流程、规范或多段资料里拼答案。
| 字段 | 变化 | 含义 |
|---|---|---|
run_faq |
保持 true |
FAQ 仍可提供候选 |
run_doc |
保持 true |
文档是主要证据来源 |
doc_top_k |
从 20 提到 24 | 多拿文档候选,避免漏召回 |
final_context_top_n |
提高 | 给 LLM 更多证据 |
use_query_variants |
true |
第 07 章生成查询变体扩大召回 |
一句话:文档优先 = 多召回文档证据,让后续答案生成有足够上下文。
3. FAQ 和文档都多¶
FAQ 和文档都多通常来自 intent=FOLLOW_UP,比如用户在有历史上下文时问“那审批呢?”。
| 字段 | 变化 | 含义 |
|---|---|---|
faq_top_k |
提到 24 | FAQ 多取一些,避免短追问漏掉标准问答 |
doc_top_k |
提到 24 | 文档也多取一些,补足上下文 |
faq_direct_threshold |
至少 0.82 | 不轻易 FAQ 模糊直出 |
use_query_variants |
true |
追问需要先结合历史改写 |
一句话:FAQ 和文档都多 = 当前问题信息不足,先多找证据,再谨慎判断。
风险类别怎么参与¶
风险类别不是新的用户意图,也不会直接决定最终答案分数。它是在已有检索倾向上继续加保险。
例如 报销费用超过5000需要谁审批 可能先被识别为 FAQ_QUERY,于是得到 FAQ 优先计划;但它又命中 pricing 风险类别,所以会继续叠加费用保护:
| 风险类别 | 典型词 | 保护动作 |
|---|---|---|
pricing |
费用、金额、报销、付款 | 提高 FAQ 直出门槛,扩大文档候选 |
compliance |
合规、隐私、审计、合同 | 使用更高直出阈值,避免草率回答 |
troubleshooting |
报错、失败、异常、排查 | 扩大文档候选,保留更多排障步骤 |
summary |
总结、归纳、对比、大纲 | 扩大文档候选,覆盖更多资料 |
所以最后看到的 reason 可能是:
它的读法是:先按 FAQ 优先走,再叠加短问题保护,再叠加费用风险保护。
一句话:意图决定主方向,风险类别决定要不要更谨慎。
五类问题的参数体感¶
理解第 06 章时,可以把参数看成五个旋钮:
| 旋钮 | 变大或打开以后意味着什么 | 代价 |
|---|---|---|
faq_top_k |
FAQ 候选更多,更不容易漏掉标准问答 | 后续重排和判断成本更高 |
doc_top_k |
文档候选更多,更适合复杂知识问题 | 噪声更多,检索和重排更慢 |
faq_direct_threshold |
FAQ 直出更谨慎,分数必须更高 | 可能少一些快速直出 |
final_context_top_n |
给 LLM 的证据更多 | Prompt 更长,答案可能更慢 |
use_query_variants |
第 07 章会生成查询变体扩大召回 | 多一次改写/变体成本 |
intent_rule_score |
记录入口规则候选强弱 | 用于诊断规则为什么这么判 |
intent_decision_score |
记录网关最终决策分,低分触发更保守计划 | 低分或规则/模型冲突问题会少一些快速直出 |
下面把五类常见问题放到同一张表里对比。
| 问题类型 | 典型问法 | 核心参数变化 | 设计目的 |
|---|---|---|---|
| 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 |
问题通常需要多段资料拼接,先扩大文档召回,再由后续章节生成更完整答案 |
| 低决策分兜底 | 帮我分析一下这个问题 |
intent_rule_score=0.6,intent_decision_score=0.6;faq_direct_exact_only=true;faq_direct_threshold 至少 0.86;final_context_top_n 至少 6 |
入口判断不够确定时,禁止模糊 FAQ 快速直出,优先收集更多证据 |
| 追问 | 那审批呢,历史问题是 报销流程是什么 |
faq_top_k=24;doc_top_k=24;faq_direct_threshold 至少 0.82;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_top_k=20
doc_top_k=20
final_context_top_n=4
faq_direct_score_threshold=0.72
doc_complex_query_top_k=24
普通 FAQ 问题:
计划会偏向 FAQ:
{
"doc_top_k": 10,
"faq_direct_threshold": 0.64,
"final_context_top_n": 4,
"use_query_variants": false,
"reason": "faq_first"
}
这表示:标准 FAQ 很可能能回答,所以文档候选先收小;问题本身已经完整,不需要第 07 章生成查询变体。
如果问题变成:
计划会叠加费用保护:
{
"doc_top_k": 24,
"faq_direct_threshold": 0.84,
"final_context_top_n": 6,
"reason": "faq_first_short_query_guard_pricing_guard"
}
这表示:虽然它看起来仍像 FAQ,但涉及费用和审批,不能轻易模糊直出;要多召回文档证据,给后续答案生成更多上下文。
如果问题再变成:
计划会继续叠加表格保护:
{
"prefer_table": true,
"faq_direct_exact_only": true,
"final_context_top_n": 7,
"reason": "faq_first_pricing_guard_table_row_preferred"
}
这表示:答案更可能来自某个清单字段或表格行。即使 FAQ 相似度高,也不能只靠“相似”直接返回,除非是精确 FAQ 命中。
典型运行结果¶
在项目根目录执行以下命令。第 06 章统一带上 --plan-only,这样输出会停在 RetrievalPlan,不会调用第 07 章的 LLM 改写或查询变体。
FAQ 查询¶
关键输出:
{
"intent": {
"intent": "FAQ_QUERY"
},
"retrieval_plan": {
"run_faq": true,
"run_doc": true,
"doc_top_k": 10,
"use_query_variants": false,
"reason": "faq_first"
}
}
知识查询¶
关键输出:
{
"intent": {
"intent": "KNOWLEDGE_QUERY"
},
"retrieval_plan": {
"doc_top_k": 24,
"final_context_top_n": 5,
"use_query_variants": true,
"reason": "knowledge_doc_enriched"
}
}
追问¶
关键输出:
{
"intent": {
"intent": "FOLLOW_UP",
"requires_rewrite": true
},
"retrieval_plan": {
"faq_top_k": 24,
"doc_top_k": 24,
"use_query_variants": true,
"reason": "history_aware_follow_up_rule_score_guard"
}
}
费用类问题¶
关键输出:
{
"retrieval_plan": {
"question_category": "pricing",
"faq_direct_threshold": 0.84,
"final_context_top_n": 6,
"reason": "faq_first_short_query_guard_pricing_guard"
}
}
表格类问题¶
关键输出:
{
"retrieval_plan": {
"prefer_table": true,
"faq_direct_exact_only": true,
"final_context_top_n": 7,
"reason": "faq_first_pricing_guard_table_row_preferred"
}
}
这段输出可以这样读:
| 字段 | 说明 |
|---|---|
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 章负责多格式文件加载和表格行入库。 |
参数数字怎么解释¶
本章参数对齐主项目默认配置。检索策略保护线集中配置在 config/rules.toml 的 [retrieval_strategy],代码通过 get_rule_config().retrieval_strategy 读取。它们不是官方标准,也不是固定承诺。
| 参数 | 默认值 | 参数理解 |
|---|---|---|
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 相似直出的基础保护线 |
short_query_guard_threshold |
0.78 | 短问题更容易误命中,所以阈值更高 |
medium_rule_score_direct_threshold |
0.82 | 意图决策不够稳定时,进一步抬高 FAQ 直出门槛 |
pricing_direct_threshold |
0.84 | 金额、报销、付款类问题更谨慎 |
compliance_direct_threshold |
0.86 | 合规、隐私、合同类问题更谨慎 |
low_rule_score_direct_threshold |
0.86 | 默认兜底或规则/模型冲突问题只允许更谨慎的 FAQ 直出 |
这些值来自当前项目样例和默认配置。真实项目上线时,需要结合 evaluate_intent_policy.py、主链路评测集、召回率、误直出率、延迟和人工抽检继续校准。
怎么从评测结果落到参数¶
不要看到一个候选阈值就直接改生产配置。本项目按三步落参:
- 先运行
calibrate_thresholds.py,得到 FAQ 直出阈值和意图模型接管阈值的候选值。 - 再运行
recommend_runtime_params.py,把候选值、主链路评测、意图策略门禁合并成参数建议。 - 只采纳
action=apply_candidate_after_review的项;如果报告显示keep_current,说明当前证据不足或门禁失败,先补样本/修 Bad Case。
示例命令:
python scripts/intent/calibrate_thresholds.py --output reports/threshold_calibration/threshold_candidate_latest.json
python scripts/intent/evaluate_intent_policy.py --output reports/intent_policy/intent_policy_latest.json --fail-on-critical
python scripts/evaluate_core_chain.py --dataset eval_sets/multi_scenario_smoke.json --limit 20 --output reports/evaluation/core_chain_latest.json
python scripts/quality/recommend_runtime_params.py --calibration-report reports/threshold_calibration/threshold_candidate_latest.json --evaluation-report reports/evaluation/core_chain_latest.json --intent-policy-report reports/intent_policy/intent_policy_latest.json
运行时按下面规则理解:
| 评测现象 | 优先调整 | 调参方向 |
|---|---|---|
| FAQ 误直出 | FAQ_DIRECT_SCORE_THRESHOLD / *_direct_threshold |
上调,通常先加 0.03-0.05 |
| 应 FAQ 直出却进入 RAG | FAQ_DIRECT_SCORE_THRESHOLD |
下调,通常先减 0.02-0.03,但必须确认误直出率仍过门禁 |
| 预期来源没有召回 | DOC_TOP_K / FINAL_CONTEXT_TOP_N |
先扩大候选池,例如 DOC_TOP_K 从 20 到 24,FINAL_CONTEXT_TOP_N 从 4 到 5 |
| 意图模型误接管规则 | model_min_score |
上调,或补充意图训练/评测样本 |
| 规则和模型频繁冲突 | conflict_final_score / 训练集 |
先保持保守分,优先补样本,不直接放宽 |
| 平均耗时或 P95 超门禁 | top_k / final_context_top_n / cache |
先查慢样本,确认召回无损后再下调 |
这张表只适合在第 05 章和主链路评测已经通过时使用。顺序不能反过来:如果 evaluate_intent_policy.py 已经显示 route_accuracy、policy_accuracy、source_accuracy 有问题,先修意图和 source,再谈第 06 章的召回参数;否则你会把错误的上游判断“放大”成错误的检索策略。
如果要看完整跨章闭环和一条可落地的排障案例,直接去第 17 章;本章只保留检索策略层该怎么看。
更稳妥的排查顺序是:
- 先看
evaluate_intent_policy.py,排除 route、source、rewrite、policy 级别的问题。 - 再看
calibrate_thresholds.py,确认 FAQ 直出阈值和意图模型接管阈值有没有样本支撑。 - 再看
evaluate_core_chain.py,确认召回、MRR、keyword coverage、hit type 和延迟是否达标。 - 最后才对
DOC_TOP_K、FINAL_CONTEXT_TOP_N、FAQ 直出阈值和风险类 direct threshold 做小步调整。
如果只想一句话记住第 06 章的闭环:意图和 source 先定方向,阈值和 top_k 负责把这个方向执行稳。
所以在项目说明中可以这样解释:评测报告不是直接告诉我们“所有参数应该是多少”,而是先判断问题属于误直出、漏直出、漏召回、误路由还是性能超标;每类问题只对应少量运行时参数,改完必须重新跑门禁。
测试¶
cd D:\workspace\knowforge-rag-platform
python -m pytest tests/test_retrieval_and_prompt.py
python -m pytest tests/test_intent_policy_evaluation.py -q
python scripts\intent\evaluate_intent_policy.py --fail-on-critical
测试应该覆盖:
- 第 05 章入口路由仍然正常
- FAQ_QUERY 会生成 FAQ 优先计划,不在本章本地返回标准答案
- FAQ 查询生成 FAQ 优先计划
- 知识查询扩大文档候选并打开查询变体
- 追问扩大候选并要求第 07 章改写
- 短问题提高 FAQ 直出门槛
- 费用/合规类问题提高保护阈值
- 表格类问题偏向表格行资料
- 意图策略校准报告无 critical failure,证明当前分数和阈值能驱动正确检索计划
本章小结¶
第 06 章只处理 route="retrieval" 的问题。它把 IntentResult 翻译成 RetrievalPlan,再把意图、决策分、风险类别和表格偏好变成后续检索能直接消费的参数。下一章会接着这份计划,处理追问改写和查询变体。