Skip to content

第15章 Query Rewrite:让用户问题变成可检索问题

"The query is what the user typed. The intent is what the user wanted. Our job is the bridge." — IR 课堂第一课

本章要点

  • 真实用户查询经常不适合直接检索——口语化、多意图、含指代、带业务黑话
  • Query Rewrite 五大模式:扩展、拆解、HyDE、routing、上下文消歧——各修一类弱点
  • HyDE(让 LLM 先猜一个假答案、用答案 embedding 去检索)是 2023 年后的 SOTA 策略
  • Rewrite 增加延迟和成本——值不值得看召回质量瓶颈在不在 query 侧
  • 生产 Rewrite 的可观测指标:rewrite 前后 recall 对比routing 误判率

15.1 为什么原始 query 不够好

生产里的真实用户 query 长这样:

  • "这玩意儿怎么退"(指代 "这玩意儿"——上下文里的某订单?)
  • "上次说的那个"(指代前文)
  • "SSO 怎么弄,要钱吗,啥版本"(复合问题,三个子问题)
  • "新版贵不贵"(模糊、比较词、没说和什么比)
  • "pro 和 ent 的 sso 区别"(缩写、小写,库里可能是"专业版""企业版")

这些 query 直接丢给 Embedding 检索会触发第 3 章的"找不到"或"找错"家族。问题不是检索系统弱,是输入给检索系统的 query 本身缺信息。Query Rewrite 的工作就是在检索之前把 query"整理"成更容易命中相关文档的形式。

Rewrite 的三种产出

根据策略不同,Rewrite 输出三种形式之一:

  • 改写后的单 query:替换术语、补全上下文、扩展同义词,仍然是一条 query
  • 多个子 query:拆解成几条独立 query 并行检索、结果融合
  • 增强文档:不改 query、生成一段"假的答案"作为额外的检索 anchor(HyDE)

15.2 扩展:同义词、改写

最朴素的 Rewrite:给 query 加上同义词、变体、领域黑话到规范词的映射。

规则式扩展

维护一张同义词表:

text
SSO, 单点登录, SAML, 身份联合
企业版, ent, enterprise
私有化, on-prem, 本地部署
pro, 专业版, professional

查询 "企业版 SSO 怎么开" 扩展成:

text
(企业版 OR ent OR enterprise) (SSO OR 单点登录 OR SAML) 怎么开

BM25 直接用这种 OR 语法。Embedding 侧可以给扩展后的 query 生成多个向量并发召回再融合。

规则表从哪来

  • 业务初始:领域专家列 100-200 条核心术语映射
  • 日志挖掘:高频 miss 词 + 人工标注语义对应
  • LLM 辅助:用 LLM 对已有术语生成变体、人工复查

同义词表是持续工作——产品迭代一次、术语可能就加一批。

LLM 式扩展

直接让 LLM 改写:

text
Prompt: 给定用户查询,生成 3 个不同表达但意思相同的版本。

Query: pro 和 ent 的 sso 区别
Expansions:
1. 专业版和企业版在 SSO 功能上的区别
2. pro 版本和 enterprise 版本的单点登录差异
3. Professional 和 Enterprise 产品 SAML 集成对比

优点:灵活、覆盖面广;缺点:慢(一次 LLM 调用)、成本。

混合策略

生产常见:规则式做高频确定映射(缩写、产品别名)、LLM 做长尾兜底

15.3 拆解:把复合问题切成子问题

复合问题用户经常一股脑问:"SSO 怎么开,要钱吗,哪些版本支持"。单 query 检索得到的 chunk 可能只回答一个子问题。拆解成:

text
子 q1: SSO 怎么开
子 q2: SSO 要钱吗
子 q3: SSO 支持哪些版本

各自检索、结果合并送 LLM 生成。

LLM 拆解 prompt

text
把用户的复合问题拆成多个独立子问题。每个子问题自包含、可独立检索。

问题:SSO 怎么开,要钱吗,哪些版本支持
子问题(JSON):
[
  "SSO 如何开启配置",
  "SSO 功能的价格",
  "哪些产品版本支持 SSO"
]

多子 query 的融合

各子 query 召回的 chunk 汇总去重、RRF 融合或直接 union。注意每个子 query 最多带 top-10 candidates、union 不超过 top-30——否则上下文打包塞不下。

什么问题适合拆解

  • 用"并且"、"和"、"以及"连接多个实体的问题
  • 包含多个疑问词的问题("是什么,怎么用,多少钱")
  • 跨主题的问题("A 产品的 X 功能对比 B 产品的 Y 功能")

不适合拆的:简单事实问题、单一意图的模糊问题。拆错会增加噪声。第 22 章生产实战会给更细的分类器逻辑。

15.4 HyDE:让 LLM 先编一个假答案

HyDE(Hypothetical Document Embeddings,arXiv:2212.10496,Gao et al. 2022)是 2023 年后 RAG 领域最有影响力的技巧之一。

核心思路

用户 query 和 chunk 的语义空间可能不完全对齐——query 短而问式、chunk 长而陈述式。直接 cosine 距离有"问题 vs 答案"的语义鸿沟。

HyDE 的解法:先让 LLM 根据 query 生成一段假设答案、用这段"假答案"的 embedding 去检索。

text
Query: 企业版 SSO 怎么开通
↓ (LLM 生成)
假答案: 企业版用户可以通过管理后台的"身份认证"选项配置 SSO,支持 SAML 2.0 和 OIDC。配置流程包括上传 IDP 元数据...
↓ (embedding)
用假答案向量去向量库检索

假答案不需要是"真的对"——只要在向量空间里落在真答案附近就行。实际效果:recall 提升 3-8 个点、对长尾问题尤其有效。

工程实现

  • 小模型够用:Claude Haiku / GPT-4o-mini / Qwen 7B 生成假答案足够
  • Prompt 极简"Answer this question in 2-3 sentences: {query}"
  • 延迟:额外 200-500ms——对总延迟影响明显、要权衡

HyDE 适合的场景

  • Query 太短(< 5 词)embedding 捕捉不够
  • 查询和答案的表达模式差距大(问句 vs 陈述)
  • 冷领域(embedding 训练时没见过的词汇)

不适合:

  • Query 已经很详细(本身就像答案)
  • 有明确关键词("订单 OD-12345"——走 BM25 更准)
  • 延迟敏感场景(对话初次请求 TTFT 敏感)

HyDE 的调优

HyDE 不是直接上就最优——有几个细节影响效果 5-10 点:

  • 多假答案:生成 1 个假答案 vs 3 个再取平均向量。多假答案平均能稳定召回(减少单次生成的方差),代价是 3 倍 LLM 成本。生产常用 2 个假答案的折中
  • 假答案长度:太短(<30 token)像 query 的改写、不够"答案";太长(>200 token)引入 LLM 自己的先验、偏差大。100-150 token 是甜点
  • 假答案和原 query 的融合:纯用假答案向量 vs 0.7 * 假答案向量 + 0.3 * 原 query 向量——后者稳健、避免假答案跑偏
  • 领域特化:通用 prompt 在专业领域效果一般。在 prompt 里加"用 {domain} 的语言回答"("用法律条文的语言"、"用医学术语")能显著提升

HyDE 的失败模式:当 LLM 对 query 本身没有先验知识(极新的产品、极冷门的概念),生成的假答案可能完全跑偏、反而把检索带歪。所以 HyDE 要和 BM25 并行(第 13 章 Hybrid)——BM25 命中关键词兜底、HyDE 扩展语义覆盖。单用 HyDE 风险大。

15.5 Routing:不同 query 走不同索引

有些项目的"正确"检索策略因 query 类型而异:

  • 结构化 ID 查询(订单号、合同号)→ 走精确匹配索引
  • 概念性问题 → 走 dense embedding
  • 多步推理问题 → 走 Agent 多轮检索(第 18 章)
  • 历史数据查询 → 走带时间 filter 的冷索引

Query Router 的工作:识别 query 类型、路由到对应索引或 pipeline

实现方式

  • 规则式:正则匹配关键 pattern(订单号格式、日期、产品代号)
  • 小模型分类:训一个 query classifier,输出 intent label
  • LLM 判断:直接问 LLM "这个 query 是结构化查询 / 概念问题 / 多步推理?"

生产常见:规则优先(零成本)+ LLM 兜底(处理规则没覆盖的)

Routing 的错判成本

错判是 Routing 的主要风险——把"概念问题"误判成"结构化 ID"、走错索引就查不到。缓解:

  • 多路 fallback:首选路径 miss 时自动降级到 default hybrid
  • 监控误判率:对 gold set 标注正确路由、定期回归
  • 人工 review:badcase 里溯源是否 routing 错

Query classifier 的训练

如果选择用小模型做 classifier、数据怎么来:

  • 规则标注起步:先用正则标 500-1000 条——订单号、日期、产品 SKU 都能规则匹配
  • 人工校验:随机抽 100 条看规则对不对、纠正错标
  • LLM 扩展:用 LLM 对 query log 采样打标、人工审
  • 训练小模型:用 distilbert / MiniLM 二分类或多分类、训 3-5 epoch 够用

训练完的 classifier 典型指标:accuracy 90%+、p99 延迟 < 10ms、能覆盖 80% 的 query。剩下 20% 走 default hybrid——保底策略。

Routing 的演化

生产系统的 routing 通常会演化:

  • V1:全部走 hybrid(不做 routing)
  • V2:加一两条明显规则(ID 走精确、其他走 hybrid)
  • V3:规则扩展到 10+ 类,处理 60-70% 的 query
  • V4:训 classifier,处理 85%+ 的 query
  • V5:classifier + LLM 兜底,处理极端长尾

每个 V 都看业务 badcase 驱动——没看到对应问题时不加规则。过早复杂化会让维护成本压过收益。

15.6 上下文消歧:多轮对话的指代

对话场景的 query 常带指代——"那个怎么退"、"上面说的配置怎么做"。单独看这条 query 没办法检索——"那个" 是什么?

基本做法

把对话历史 + 当前 query 送 LLM,让它输出"独立版 query":

text
历史:
- User: SSO 多少钱
- Assistant: 企业增强包包含 SSO,每年 20000 元
- User: 那基础版呢

Rewrite prompt:
根据对话历史改写用户最后的问题,让它独立可检索。

Rewritten: 基础版产品是否包含 SSO 功能?价格是多少?

改写后的 query 就是独立的、可直接用于检索。

延迟优化

每轮都走 LLM 改写太慢。优化:

  • 首轮跳过:首轮 query 没历史、不需要改写
  • 指代检测:先用小模型或规则判断是否有指代词(它、那个、上面、前面)、没有才不改写
  • 缓存:同一 session 相同上文的改写结果可以缓存

改写 vs 上下文拼接

另一种思路:不改 query、直接把历史拼到 embedding 输入前面。优点:一次 embedding 调用;缺点:历史越长信号越乱、embedding 模型对长输入处理差。

生产推荐:多轮对话用 LLM 改写、单轮问答不需要

15.7 Rewrite 的延迟和成本

每种 Rewrite 都增加开销。典型数字:

方法延迟每 1000 query 成本
规则扩展< 5ms~$0
LLM 扩展(Haiku)200-400ms~$0.5
LLM 拆解(Haiku)300-600ms~$1
HyDE(Haiku)300-500ms~$0.5
Routing(规则)< 1ms~$0
Routing(LLM)100-200ms~$0.3
多轮消歧(Haiku)200-400ms~$0.5

成本可以接受、延迟是 RAG 在线链路的硬约束。策略:

  • 首选规则:能用规则的不用 LLM
  • 并行化:LLM 改写和某些不依赖它的阶段并行(比如预热 rerank 服务)
  • 缓存:相同 query 的改写结果缓存 10-60 分钟

15.8 什么时候做 Rewrite,什么时候不做

不做 Rewrite 的情况:

  • 召回质量瓶颈不在 query 侧(比如索引覆盖不够、embedding 弱)
  • 延迟极敏感(对话首屏 TTFT < 500ms)
  • 查询已经结构化(API 调用而非自然语言)

做 Rewrite 的情况:

  • 召回 gold set 上发现 "问题表达和文档表达差距大"
  • 用户 query 短(< 5 词)、指代多、黑话多
  • 多轮对话场景

判断标准:用归因分析(第 3 章)看"找不到"家族里是否有大比例是 query 表达问题——有则做、没有就别加这层复杂度。

15.9 Rewrite 的评估

评估 Rewrite 效果的两个指标:

  • rewrite 前后 recall@k 对比:同一批 query 分别跑"原始 query"和"改写 query"、对比召回率。提升 3-5 点才值得部署
  • 语义保真度:改写后的 query 是否扭曲了原意?随机抽 50 条让人工评审

反模式:改写过度

过度改写的典型表现:

  • Query "SSO 价格" 改成 "企业级产品的单点登录功能的定价策略和商业模式"——加了噪声词、召回漂移
  • 每次都改写——把简单问题搞复杂

Prompt 要明确"只在必要时改写、否则原样返回"。生产 Rewrite 服务应该监控"改写率"——如果 > 80% 的 query 都被改写,说明 prompt 有偏差。

15.10 多策略组合

生产 RAG 往往组合多种 rewrite 策略:

这条 pipeline 的每一步都有条件跳过——不是所有 query 都走全程。工程要点:默认轻量 + 按需加重

15.11 跨语言 Query Rewrite

多语言 RAG 的一个常见需求:用户用中文问、知识库是英文的(或反之)。两条路:

策略 A:Query 翻译

把 query 翻译成知识库语言、再检索。

  • 简单直接、一次 LLM 翻译 + 一次检索
  • 翻译质量决定检索上限
  • 失去原 query 的语言信号(对 BM25 是损失)

策略 B:多语言 Embedding

用多语言 embedding 模型(bge-m3、multilingual-e5、jina-v3)——把中英文都映射到同一向量空间、不需要翻译。

  • 无需 LLM 翻译、延迟低
  • 模型能力决定精度
  • 专业术语可能跨语言对不齐(中文"企业版" vs 英文 "Enterprise")

策略 C:双路召回

  • 路 1:中文 query → 中文 BM25 召回 → 如果有中文 chunk
  • 路 2:翻译成英文 query → 英文 BM25 / 英文 embedding 召回
  • 融合两路

覆盖最全、成本也最高。

生产选型

  • 知识库以某一语言为主(> 80%):策略 A
  • 中英各占一半:策略 B(多语言 embedding)
  • 业务强依赖多语言精度:策略 C

跨语言的其他细节

  • Chunk 语言 metadata:每 chunk 标 language=zh/en/ja,filter 下推
  • 答案语言跟随 query:用户中文问、答案中文;英文问、答案英文。prompt 加 "reply in the same language as the user's query"
  • dual-citation:引用的 chunk 和答案不同语言时、同时提供原语言引用("见《产品规格》英文原文第 3 节")

15.12 Rewrite 的失败模式与防线

Rewrite 不是"锦上添花"的中间件——它处在 query 进入检索的必经之路上,一旦出错,下游全错。这种级联失败是生产 Rewrite 的头号风险。必须在架构上先建防线、再谈效果。

级联失败的三种典型

  • 语义漂移:Rewrite 改成了另一个意思的 query、下游召回整个跑偏。例:原问 "退款要多久"、改写成 "退款流程是什么"——召回的是流程文档而非时效回答
  • 关键词丢失:原 query 里的产品代号、订单号被 LLM "润色" 掉。例:"OD-12345 怎么取消"改成 "如何取消订单"——丢失了具体订单号、BM25 路完全失效
  • 过度具体化:LLM 基于自己的先验把模糊 query 改成某个具体假设。例:"新功能好用吗" 改成 "最新发布的 AI 助手功能是否易用"——但用户问的其实是另一个新功能

级联失败的隐蔽性在于:下游检索和生成看起来都正常,只是答非所问。监控 retrieval latency / answer length 都看不出来、只有对照 gold 或用户反馈才暴露。

防线 1:原 query 并行保留

最关键的一条防线:无论做了多少 rewrite,原 query 也一定走一路检索、和 rewrite 后的结果 RRF 融合。rewrite 跑偏时原 query 路兜底、rewrite 正确时两路互相加强。

这条防线的代价只是多一路检索——但换来的是"rewrite 再怎么错都不至于全链路崩溃"的稳健。多数团队只上 rewrite 不留原 query,出事一次就知道为什么要有这条。

防线 2:Rewrite 后的相似度校验

改写结果和原 query 做一次 cosine 相似度检查。相似度过低(< 0.6)说明改写偏离太远、丢弃改写、退回原 query。这是轻量的"sanity check"、避免 LLM 一次极端跑偏污染整次请求。

不是所有场景都适用——HyDE 生成的假答案本来就不要求和原 query 高相似度。相似度阈值要按 rewrite 模式分别标定。

防线 3:Rewrite 的灰度与 shadow

和 ch13 融合调参同理,rewrite prompt 的每次修改也要走 shadow → A/B → 全量:

  • shadow:rewrite v2 对线上流量镜像跑、对比 v1 的召回集差异。差异超过 30% 先警戒、人工抽查
  • A/B:小流量(1-3%)真实下发、看业务指标(答案满意度、对话延展轮数)
  • 回滚路径:rewrite 服务版本化,rollback 到上一版本是一次配置变更、不是代码回滚

Rewrite prompt 改动最容易被低估——一句话的 prompt 改动可能让线上 recall 掉 10 个点。

防线 4:用户侧的可见性(可选)

一些 UI 产品显式给用户看 "我理解的问题是:XX"(改写后的 query)、让用户可以 "No, I meant..." 纠正。这是最强的 safety net——但只适合能打断用户的产品形态(Chat UI 类),不适合企业 API 场景。

三种常见 badcase 的排查法

症状可能原因排查动作
答案完全答非所问Rewrite 语义漂移查 trace 看 rewrite 前后 query 对比
精确 ID 查不到但其他正常Rewrite 去掉了关键词对比原 query 和 rewrite 里实体列表
新 rewrite prompt 上线后整体 recall 掉Prompt 改动引入偏差回滚 rewrite 到上一版、观察是否恢复
某类 query 上 recall 掉、其他正常Routing 误判检查 classifier 对这类 query 的分类分布

把这张表写进 runbook——rewrite 出事时按表查比现场推理快得多。

15.13 Step-Back 与 Multi-Query:HyDE 之外的两条 SOTA 路径

§15.4 的 HyDE 把 query 转成一个假答案——用"答案视角"做检索。2023 年后还有两条和 HyDE 互补的 SOTA 路径:Step-Back promptingMulti-Query / RAG-Fusion。它们解决的问题和 HyDE 不同、适用场景也不重合、生产里经常同时存在。

Step-Back:向上抽象、而非向前生成

Step-Back(Zheng et al., Google 2023,arXiv:2310.06117)的核心观察:复杂推理 query 的关键信息常不在表面、而在其上位概念里。

例子:

text
原 query: 在 2018 年到 2020 年期间,埃隆·马斯克担任 SpaceX CEO 吗?

直接检索:命中的 chunk 是 Musk 传记的细节段落、噪声大

Step-Back 出的抽象问题:
"埃隆·马斯克在 SpaceX 担任过什么职位?"

用抽象问题检索:命中高层 fact 文档、再让 LLM 推理时间范围

Step-Back 让 LLM 先生成一个更一般化的问题、用它去检索背景知识、再用背景知识回答原问题。

和 HyDE 的差异:

维度HyDEStep-Back
方向向下生成"假答案"向上生成"抽象问题"
适合事实型 query推理型 / 时间范围 / 比较型
失败模式假答案跑偏抽象过度、丢失关键约束
成本1 次 LLM 调用1-2 次 LLM 调用

多跳问题上 Step-Back 比单纯 dense retrieval recall 提升 5-15 个点(论文数据)。

Multi-Query / RAG-Fusion:广覆盖式改写

Multi-Query(LangChain 社区模式、也叫 RAG-Fusion)的思路:让 LLM 对同一 query 生成 k 个语义等价但表达不同的变体、每个变体独立检索、最后 RRF 融合

text
原 query: 企业版 SSO 怎么配置

生成 4 个变体:
1. 企业增强包的 SSO 配置步骤
2. Enterprise 版本 SAML 集成方法
3. 企业版身份联合认证的开启流程
4. 专业版 SSO 设置教程

每个变体跑一次 hybrid 检索、top-20 候选、RRF 融合、取最终 top-10

这招的价值来自 query 表达方差——单一 query 的召回可能被某个措辞带偏、多个变体汇总大幅降低"表达敏感"的 badcase。

实测在真实生产环境(客服 FAQ、企业知识库)里 Multi-Query 比单 query recall@10 提升 3-8 点。成本:多做 k 次检索 + 1 次 LLM 生成(4-5 个变体约 300ms)。

三种技术的选型

不是用一个就够——三者解决不同问题

场景推荐
短 / 模糊 / 语言表达差距大HyDE
多跳 / 时间约束 / 需要背景知识Step-Back
口语化 / 表达方差大 / 同义多种说法Multi-Query
以上多种共存组合用(但延迟翻倍)

生产常见组合:Query 分类器识别 query 类型 → 按类选技术

python
def pick_rewrite(query_type):
    if query_type == "short_ambiguous":
        return "hyde"
    if query_type == "reasoning":
        return "step_back"
    if query_type == "colloquial":
        return "multi_query"
    return "none"

不要"全部都上"——延迟和成本会崩。

组合的工程代价

如果真的需要组合多种技术,延迟叠加要算清楚:

  • HyDE:+300ms
  • Step-Back:+500ms(两次 LLM)
  • Multi-Query:+400ms

三个全上 +1200ms、对 TTFT < 1s 的场景直接崩溃。按 query 类型路由比"全 pipeline 全开"值钱得多。

这些技术的发展趋势

2025-2026 年的研究方向:

  • 自适应 rewrite:LLM 自己判断当前 query 是否需要 rewrite、需要哪种——不靠外部 classifier。延迟代价 1 次 LLM 决策
  • LLM 作检索主控(Self-RAG、CRAG):LLM 不只做 rewrite、还控制"要不要检索"、"检索结果是否够"——Agent 化的 RAG
  • 多轮 rewrite 协同:把 HyDE + Step-Back + Multi-Query 在同一个 prompt 里让 LLM 一次产出所有变体

2026 年这些路线仍在快速演化——生产项目不追最新、等 6-12 月社区验证再采用。但把 query 视为可修复的输入、而非固定输入是确定的方向。

15.14 领域专门的 Rewrite 模板

前 13 节的 rewrite 技术都是通用——适合任何领域但都不是任何领域的最优。真实企业 RAG 通常服务特定垂直(代码 / 法律 / 医疗 / 金融),这些领域的 query 有独特模式、通用 rewrite 帮助有限、领域专门模板能提升 10-20 个 recall 点。

为什么通用 rewrite 不够

通用 LLM 对领域术语的理解往往浅:

  • 代码搜索:UserService.authenticate() 需要保留精确符号、不能"改写成自然语言"
  • 法律:条款编号 "第 35 条第 2 款" 必须精确匹配、同义改写有害
  • 医疗:疾病 ICD 编码、药品规范名 vs 商品名的映射
  • 金融:产品代号、监管条款、合规术语的精确对应

通用 rewrite 可能把精确信号"改写"成模糊语义——对代码 / 法律场景是灾难。

代码 RAG 的 Rewrite 模板

代码 query 的典型模式和对应策略:

Query 模式Rewrite 策略
函数名 getUserById保留原名 + 加 camelCase 切分 getUserById OR "get user by id"
包路径 com.acme.User精确匹配、不改写
错误消息 NullPointerException at line 42保留 exception 类名 + 剥离行号
自然语言描述 "怎么验证密码"扩展代码相关关键词 密码 OR password OR pwd OR auth
跨语言查找 "类似 Rust 的 ? 操作符在 Go 里"拆解为 "Go 错误处理" + "Rust ? 操作符等价" 两子 query

关键工具:tree-sitter AST 识别(第 5 章)——从 query 里识别代码片段、符号、语言、单独处理。

法律 RAG 的 Rewrite 模板

法律 query 对精确性要求极高:

Query 模式Rewrite 策略
"XXX 法第 N 条"精确保留条款号、不改写
"侵权责任"扩展同义 + 相关条款路径
"合同违约怎么处理"识别法律实体 → 路由到合同法 + 相关判例
跨法域查询 "欧盟 vs 中国的数据保护"拆解为两子 query、分法域检索
非专业用户口语 "我被骗了能告吗"LLM 判断法律领域 → 生成正式法律 query

重要约束:绝不能让 rewrite 改变条款号或引用——出现 "第 35 条" 必须原样保留。一个字错、答案全错。

医疗 RAG 的 Rewrite 模板

医疗有术语规范(ICD-10、SNOMED-CT、药品规范名):

Query 模式Rewrite 策略
规范名 "高血压"扩展 ICD 编码 I10 + 相关并发症
商品名 "施慧达"映射到规范名 "苯磺酸左旋氨氯地平"
口语 "头疼怎么办"识别 "头疼" → 规范化为 "头痛" + 列可能病因类别
复杂病情 "70岁老人高血压糖尿病"拆解为年龄 filter + 两个疾病子 query
症状 query "胸闷气短"关联 ICD 编码 + 紧急程度判断(可能是心脏相关 → 高优先)

关键:领域词典——ICD / SNOMED / 药品名映射表、rewrite 时显式替换、不靠 LLM 自由发挥。LLM 医学术语会幻觉、词典映射零错。

金融 RAG 的 Rewrite 模板

金融领域有严格的产品代号和监管术语:

Query 模式Rewrite 策略
产品代号 "沪深 300 ETF"保留 + 扩展代号 "510300"
监管术语 "资管新规"精确保留、扩展到具体文件号 "《关于规范金融机构资产管理业务的指导意见》"
流程问题 "开户要什么材料"识别客户类型(个人/机构) → 路由对应 SOP
风险查询 "高风险产品"按风险等级 filter、返回 R4-R5 产品
时效敏感 "当前利率"加 freshness 权重、优先召回最新公告

金融和法律一样:编号、文件号、产品代号不能改写。同义词扩展只用在模糊表达上。

垂直领域 rewrite 的通用方法论

不管什么领域、好的领域 rewrite 遵循三条原则:

  1. 识别精确信号:符号、编号、规范名、产品代号——这些是精确匹配的信号
  2. 保留并增强:精确信号原样保留 + 加别名 / 编码 / 路径等关联精确信号
  3. 对模糊部分 LLM 扩展:只在自然语言描述部分用 LLM、精确信号区绝不动

这个顺序反过来会出问题——先 LLM 后识别、精确信号已经被改写丢失。

实现:hybrid pipeline

领域 rewrite 的典型实现是 规则 + 词典 + LLM 三层 pipeline:

python
def domain_rewrite(query, domain):
    # 1. 规则识别精确信号
    symbols = extract_symbols(query, domain)  # 符号/编号/代号
    natural_part = remove_symbols(query, symbols)  # 剩余自然语言

    # 2. 词典扩展精确信号
    expanded_symbols = []
    for sym in symbols:
        expanded_symbols += lookup_synonyms(sym, domain_dict[domain])

    # 3. LLM 对自然语言部分扩展
    if natural_part:
        natural_expanded = llm_expand(natural_part, domain_context=domain)
    else:
        natural_expanded = ""

    return {
        "symbols": symbols + expanded_symbols,  # 精确 OR 查询
        "natural": natural_expanded,             # 语义扩展
    }

检索端两部分分别走 BM25 精确匹配和 dense 检索、最后 RRF 融合。

词典维护的工程化

领域词典是领域 rewrite 的核心资产、不是一次性建完就完:

  • 初始:从开源词表(ICD / SNOMED / 法规库)拿底
  • 挖掘:从业务 query log 挖高频 OOV 词、人工审核加入
  • 时效:法规更新 / 新产品上线 / 药品新增——词典要同步
  • 版本化:词典变更要版本化、线上 rewrite 带 dict_version、便于回滚

词典的维护工作量不小——专人每月 2-4 小时、或 scheduled pipeline 自动更新。这是垂直 RAG "能用 → 好用" 的分水岭。

什么时候上领域 rewrite

不是所有项目都需要领域 rewrite:

  • 通用企业知识库:通用 rewrite 够用、领域细分不值
  • 单一垂直产品:值得、ROI 高
  • 多垂直并存:先上 query classifier 分流、再按领域走对应 rewrite

判断标准:通用 rewrite 后的 recall 在本领域低于 70%、且领域术语密度高 → 值得上。

15.15 查询纠错与输入规范化

前面几节的 rewrite 默认 query 是规范的——只是需要语义改写、扩展、拆解。真实用户 query 经常是不规范的:错别字、漏字、大小写混乱、全半角错误、标点缺失、口语化缩写。不先做一轮纠错和规范化、所有下游 rewrite 都是在噪声上做优化。这节把这个经常被忽视的前置环节讲清楚。

真实用户 query 的噪声

每种噪声都可能让下游 rewrite / 检索偏差——加起来 recall 可能降 10-20 个点。

三层纠错 pipeline

L1 规则规范化(必做、零延迟):

  • Unicode 标准化(NFKC)
  • 全角转半角
  • 繁简转换(按业务选方向)
  • 大小写统一(英文小写)
  • 去除多余空白和控制字符
  • 标点统一(中英文标点混用规范化)
python
import unicodedata

def normalize_query(q):
    q = unicodedata.normalize('NFKC', q)
    q = q.strip().lower()
    q = re.sub(r'\s+', ' ', q)
    q = q.translate(全半角表)
    return q

L2 拼写检查(几 ms、必做):

  • 英文:基于词典(pyspellchecker / SymSpell)、编辑距离 ≤ 2 的替换候选
  • 中文:错别字识别(如"企业板"→"企业版")、同音字纠错("升级"vs"深级")

常用工具:

  • 英文:hunspell、SymSpell
  • 中文:pycorrector、百度 API
  • 多语言:LanguageTool

L3 LLM 润色(可选、100-300ms):

L1+L2 处理不了的高层规范化:

  • 口语化变书面语("这玩意儿" → "这个功能")
  • 拼音变汉字(qiyeban → 企业版)
  • 补全省略("咋弄" → "如何配置")

只在 L1+L2 后仍明显不规范时触发——不是每个 query 都走。

中文的特殊挑战

中文纠错比英文难:

  • 错别字分类:形近字("企业版"→"企业板")、音近字("升级"→"深级")、字形错误(输入法问题)
  • 字粒度歧义:单字替换可能改变整个词的意思
  • 同音不同字:中文同音字多(升级 / 生机)、只能靠上下文判断
  • 专业术语:技术名词拼写错("单点登陆" 应是 "单点登录")——需要领域词典

解决:规则 + 领域词典 + 小模型(如 Macbert)联合。纯规则覆盖 60-70%、加小模型覆盖 85-90%。

拼音 / 字母混用

特殊场景:用户用拼音或英文缩写替代中文术语

  • "qiyeban SSO" → "企业版 SSO"
  • "dakai SSO" → "打开 SSO"

处理:

  • 检测纯拼音段(^[a-z\s]+$ 且不含英文词)
  • 用拼音 → 汉字转换(pypinyin 反查)
  • 配合业务词典(常见产品名的拼音映射)

这是中文 RAG 的独特问题、英文场景不需要考虑。

纠错的安全边界

纠错不能改变 query 意图——这是红线:

  • 用户问 "企业板 SSO"——纠错改成 "企业版 SSO" 合理
  • 用户问 "OD-12345"——绝不能纠错(精确 ID)
  • 用户问 "升级 vs 降级"——不能把"降级"当错别字纠到"升级"

规则:

  • 识别精确信号保留:编号 / 代号 / 专有名词 / 含数字字母的 token 不纠
  • 高风险领域保守纠错:法律 / 医疗 / 金融场景、宁可不纠也不错纠
  • 纠错阈值设高:编辑距离 > 2 的不自动替换、需要用户确认

用户可见性

纠错是否让用户看到?两种策略:

  • 静默纠错:应用自动改、用户不感知——简单、但纠错出错时用户不知道为什么答错
  • 显式纠错:UI 显示 "你是否想问:企业版 SSO?"——用户确认后再 query。Google 风格

生产选择:

  • 消费级产品(聊天助手):静默纠错 + 如果置信度低、在答案开头加 "我理解你问的是 XX"
  • 企业级产品(合规、法律):显式纠错、用户确认
  • 专业用户(开发者、研究员):不纠错、尊重用户输入

纠错效果的评估

建 gold set:

  • 带错别字的 query → 正确 query 映射、100-500 对
  • 评估纠错后 query 和正确 query 的 BLEU / 精确匹配
  • 下游:纠错后的 query 在下游检索的 recall 变化

只看纠错本身正确率不够——纠错的价值要下游端到端看

常见坑

  • 纠错太激进:把正确但不常见的术语当错别字纠——recall 下降
  • 纠错和查询扩展冲突:纠错改了 term、扩展基于错 term 生成同义词
  • 纠错不更新词典:新产品上线后、用户用新术语被当错别字
  • 多语言 query 纠错单语:中英混文只纠中文、英文部分保留、整体不协调
  • 纠错延迟太高:L3 LLM 每 query 都跑、500ms 累加到检索链路

纠错的监控

生产指标:

  • correction_rate:被纠错的 query 比例、健康值 3-10%
  • correction_recall_lift:纠错后 recall 相对原 query 的提升
  • over_correction_rate:纠错把正确 query 改错的比例(用 shadow 对比估算)
  • correction_latency:纠错本身的延迟、应 < 50ms

纠错 vs 其他 rewrite 的顺序

纠错应该是 rewrite pipeline 的第一步

text
raw query → 纠错规范化 → 语义扩展 → 拆解 / HyDE → 检索

顺序颠倒的话——语义扩展在错别字上跑、同义词扩展也错。纠错是清洗、其他 rewrite 是加工——先洗后加工。

纠错的投入产出

对大多数 RAG:

  • L1 规则:必做、零成本、收益 2-5 点 recall
  • L2 拼写:投入低(现成工具)、收益 3-8 点
  • L3 LLM:投入高(延迟 + 成本)、只在 L1+L2 没救时触发、收益 1-3 点

ROI 顺序:L1 > L2 > L3。别跳过 L1 直接上 L3——浪费。

15.16 Rewrite 的调试:当改写出问题时怎么查

§15.12 讲了 rewrite 的防线——事前预防。但实际项目里 rewrite 仍会出问题——答案错了、用户投诉了、怎么快速定位是哪里出错?这节给一套 rewrite 问题的调试手册——让 on-call 同学从"一头雾水"到"半小时找到根因"。

调试的典型症状

每个症状指向不同根因——先识别症状、再对症下药

Trace 字段的设计

Rewrite 调试第一前提是有完整 trace——每次 rewrite 记录:

json
{
  "request_id": "req-abc",
  "original_query": "企业版 SSO 怎么配",
  "rewrite_strategy": "hyde",
  "rewrite_input": {
    "strategy_params": {"temperature": 0.3, "prompt_version": "v3"},
    "model": "claude-haiku",
  },
  "rewrite_output": "配置企业版的 SSO,首先进入管理后台...",
  "rewrite_duration_ms": 320,
  "rewrite_cost_usd": 0.0005,
  "rewrite_token_usage": {"input": 45, "output": 120},
  "post_rewrite_similarity": 0.78,  // 原 query 和改写后的相似度
  "downstream_retrieval_ids": ["doc-1", "doc-3", "doc-5"]
}

关键字段:strategy 版本 + 模型 + 输入输出 + 延迟成本 + 下游影响。缺字段事后查抓瞎。

五步调试流程

步骤 1:确认 rewrite 真的出问题了

先排除 "rewrite 是无辜的" —— 关 rewrite 跑同一 query:

python
def debug_rewrite(query, user_ctx):
    # 对照组:不 rewrite
    baseline = rag.query(query, user_ctx, skip_rewrite=True)
    # 实验组:正常 rewrite
    current = rag.query(query, user_ctx)
    
    print("Baseline recall:", recall(baseline))
    print("Current recall:", recall(current))
    print("Diff:", current.retrieved - baseline.retrieved)

如果两者差不多——rewrite 不是元凶、查下游(rerank / 生成)。差距大——rewrite 有嫌疑、继续查。

步骤 2:对比原 query 和 rewrite 后的 query

拿出 trace 里的 rewrite_output、和 original_query 对比:

  • 语义有没漂移:改写后意思变了吗
  • 关键实体是否保留:订单号 / 产品代号还在吗
  • 长度是否合理:原 20 字改成 200 字是过度扩展

步骤 3:手动跑 rewrite prompt

把 prompt 和 query 在 playground 里手动跑几次:

  • 结果稳定吗?(多次跑同 query、输出应相似)
  • prompt 有没有被 query 劫持?(prompt injection)
  • 温度参数是不是太高?

步骤 4:对比新老版本 rewrite

如果最近改过 rewrite prompt / model、拿新老版本在 gold set 上对比:

python
for case in gold_set:
    old_rewrite = old_rewriter.rewrite(case.query)
    new_rewrite = new_rewriter.rewrite(case.query)
    old_recall = recall_with_rewrite(case, old_rewrite)
    new_recall = recall_with_rewrite(case, new_rewrite)
    if new_recall < old_recall:
        print(f"Regression: {case.query}")
        print(f"  Old: {old_rewrite}")
        print(f"  New: {new_rewrite}")

步骤 5:归因到具体 regression 原因

根据前四步定位到具体问题:

  • Prompt 模板改了 → 回滚
  • Classifier 误判 query 类型 → 看 classifier trace
  • LLM 升级后行为变 → 固定版本 / 调 prompt
  • 温度参数变了 → 降 temperature
  • 训练数据污染 → 检查 gold set

常见根因分类

按概率排序:

根因频率特征症状
Prompt 改动最高最近改过 prompt、所有 query 都变
LLM 静默升级Claude / GPT 版本变了、rewrite 输出风格变
Classifier 误判某类 query 走错策略(如事实 query 走了 HyDE)
温度过高同一 query 多次 rewrite 不稳
数据污染Gold set 或训练数据混入坏例子
上下文消歧丢了多轮对话指代错、但单轮没事

80% 的 rewrite 问题在前三项——改 prompt 前先审。

A/B 对比工具

调试时一个实用工具——并行跑多个 rewrite 版本看差异:

python
def ab_rewrites(query, strategies):
    results = {}
    for strategy in strategies:
        r = rewrite(query, strategy=strategy)
        recall = retrieve_and_evaluate(r)
        results[strategy.name] = {
            "rewrite": r,
            "recall": recall,
            "latency": ...
        }
    return results

# 使用
ab_rewrites("新版 SSO 咋开", [
    no_rewrite,
    expansion_rewrite,
    hyde_rewrite,
    decomposition_rewrite,
])

立即看出哪个策略好——比纸面推理快。

Rewrite 的单元测试

调试完后、把 regression case 加进单元测试

python
def test_rewrite_preserves_order_id():
    query = "订单 OD-12345 状态"
    rewritten = rewriter.rewrite(query)
    assert "OD-12345" in rewritten  # 订单号不能丢

def test_rewrite_keeps_comparison():
    query = "A 和 B 的区别"
    rewritten = rewriter.rewrite(query)
    assert "A" in rewritten and "B" in rewritten

def test_rewrite_short_query():
    query = "SSO"
    rewritten = rewriter.rewrite(query)
    assert len(rewritten) > len(query)  # 该扩展
    assert len(rewritten) < 500  # 不该过度

这些测试跑在 CI 里、下次同类 regression 自动挡下。

调试的自动化

长期看、自动化 rewrite regression 检测

python
# 每小时跑一次、对 100 条 canary query
@scheduled(every="1h")
async def rewrite_canary():
    for query in canary_rewrites:
        current = rewrite(query.text)
        expected = query.expected_pattern
        if not matches(current, expected):
            alert(f"Rewrite regression: {query.text}\nGot: {current}\nExpected pattern: {expected}")

Canary 模式提前发现 regression——不等用户投诉。

Rewrite 问题的响应时间

不同严重程度的响应:

  • Regression 影响 < 1% query:日级修复
  • Regression 影响 5%+ query:立即回滚、之后修
  • Regression 影响特定业务(如某大客户):立即修 + 通知业务方
  • Rewrite 挂 / 超时:触发熔断、降级到无 rewrite

响应时间和业务影响成正比——不能一视同仁。

和评估的联动

§20.18 评估驱动优化、§15.9 rewrite 评估——两者结合:

  • 评估看到 recall 降 → 怀疑 rewrite → 调用本节调试流程
  • 调试定位到具体原因 → 回到评估验证修复效果
  • 修复进单元测试 / canary、防止再发

这是一个完整回路——没这个回路、rewrite 问题反复发生。

Rewrite debug 的工具链

生产推荐的工具栈:

  • Prompt playground:手动试 prompt、LLM API 的 playground 或自建
  • Rewrite trace explorer:Grafana / Datadog 上按 request_id 查完整 trace
  • Diff viewer:对比新老 rewrite 的差异(git diff 风格)
  • Canary dashboard:实时显示 canary rewrites 的健康度

投入几个人周搭起来——调试效率提升 3-5 倍。

不要调试"好像不对"

一个反模式:基于直觉调 rewrite——"感觉改写得不好、改个 prompt 试试"。不行:

  • 量化问题(用 gold set 或真实流量)
  • 对比新老(不是只看当前)
  • 假设 → 实验 → 验证(不是拍脑袋)

没有 disciplined debug、rewrite 会越改越糟——每次改看起来修了一个问题、实际引入两个新问题。

调试成本的长期投资

完整 debug 工具链的投入:

  • Trace 字段:1-2 人周
  • Canary + 自动化:1-2 人周
  • 单元测试:持续维护、每次 regression 加一条
  • Playground 和 dashboard:1 人周

总计 1 人月起步——但每次 regression 事故都能省几天人工——回本 2-3 次 regression 就划算。

15.17 训练专用小 rewriter:从 LLM 蒸馏到产线部署

前面讲的 rewrite 都假设用通用 LLM(Claude / GPT)做改写——每次请求 200-500ms、$0.001 成本。大规模 RAG 项目(100+ QPS)这个成本累加起来一年可能几十万美元、延迟占链路 20-30%。一条成熟路径是:用通用 LLM 的 rewrite 数据、蒸馏一个小模型——推理速度 10-50×、成本降 100×。这节给蒸馏 rewriter 的实用指南、和 §14.16 rerank 蒸馏呼应。

为什么要蒸馏 rewriter

蒸馏的本质——用 LLM 的泛化能力、换小模型的推理效率

适合蒸馏的 rewrite 任务

不是所有 rewrite 都适合蒸馏:

任务适合蒸馏原因
同义词扩展模式固定、小模型能学
Query 拆解结构明确
上下文消歧(指代解析)短 input、短 output
HyDE(生成假答案)勉强需要领域知识、小模型弱
Step-Back(抽象)不适合需要推理能力、小模型差
Routing 分类分类任务、小模型擅长

结构化任务适合蒸馏、推理任务不适合——选对任务。

训练数据的构造

通用 LLM 生成大量 rewrite 样本

python
def build_training_data():
    queries = load_query_log(sample_size=50000)
    training = []
    for q in queries:
        # 用 Claude / GPT 做 rewrite
        rewritten = llm.rewrite(q, task="expansion")
        training.append({"input": q, "output": rewritten})
    return training

5-10 万条训练样本—— cost $500-2000。一次投入、长期用

数据质量的把关

LLM 生成的数据不是百分百对——要过滤:

  • 去重:同 input 只保留一份 output
  • 长度过滤:output 太短或太长的丢掉
  • 格式校验:结构化输出的必须合法
  • 人工抽检:随机抽 100 条、review 是否合理

差数据训出来的小模型也差——数据质量是蒸馏的上限

小模型的选型

蒸馏的学生模型——中文场景选择:

  • Qwen-1.5B / Qwen-0.5B:中文强、小
  • Gemma-2B:通用、速度快
  • mT5-small:专门 seq2seq、适合 rewrite
  • 自研小模型:基于 LLaMA / Qwen 微调——需要 GPU

不是越大越好——rewrite 任务 1-3B 参数的模型就够、再大边际收益小。

训练 pipeline

典型训练过程:

python
# 1. 准备数据
train_data = load_distillation_data()  # 5-10 万 (input, output) 对
train_data, val_data = split(train_data, 0.9)

# 2. 加载基础模型
base_model = AutoModelForSeq2SeqLM.from_pretrained("Qwen/Qwen-1.5B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-1.5B")

# 3. 微调
trainer = Trainer(
    model=base_model,
    train_dataset=train_data,
    eval_dataset=val_data,
    args=TrainingArguments(
        num_train_epochs=3,
        per_device_train_batch_size=16,
        learning_rate=1e-5,
    ),
)
trainer.train()

# 4. 保存
trainer.save_model("rewriter-qwen-1.5b")

训练时长:1-2 × A100 × 8-24 小时。成本:$100-500 GPU 费用。

评估蒸馏质量

蒸馏后的小模型 vs 原 LLM:

python
def compare_rewriters(test_queries):
    for q in test_queries:
        llm_result = llm.rewrite(q)
        distilled_result = distilled_model.rewrite(q)
        
        # 用 LLM-as-judge 打分
        judge_score = llm_judge.compare(q, llm_result, distilled_result)
        print(f"Query: {q}, Judge: {judge_score}")

期望:

  • 输出相似度:BLEU 或 semantic similarity > 0.85
  • 下游 recall:用 distilled 的 rewrite 和 LLM 的 rewrite、下游 recall 差距 < 2 点
  • 延迟:distilled < 50ms(vs LLM 200-500ms)

这套指标达标、蒸馏值得。

部署

小模型部署选择:

  • ONNX + CPU:最便宜、延迟 20-50ms、够用
  • GPU 小模型服务:最快、10-20ms、成本适中
  • vLLM / TGI:高并发场景、batch 效率好

推荐:ONNX + CPU——小模型跑 CPU 就够、不浪费 GPU。

监控和 fallback

蒸馏模型上线后、监控对比原 LLM:

  • 日常:distilled 跑大部分请求
  • 采样:1-5% 请求同时跑 distilled 和 LLM、对比差异
  • 差异超阈值:告警、可能 distilled 模型漂移
  • Fallback:distilled 失败时切 LLM

这套机制让蒸馏的风险可控——不是"完全替代 LLM 不管了"。

迭代:持续蒸馏

蒸馏不是一次性——随着业务变化:

  • 新 query 类型出现 → 小模型不会处理 → 加到训练集再蒸馏
  • LLM 升级(Claude 4.8 → 5.0)→ 新 LLM 的 rewrite 更好 → 重新蒸馏小模型
  • Prompt 变了 → 重跑 LLM 生成数据、重训

典型节奏:每 3-6 月重新蒸馏一次——保持质量。

蒸馏的 ROI

投入:

  • 数据生成:$500-2000 一次
  • 训练 GPU:$100-500 一次
  • 工程:1-2 人月
  • 持续维护:每 3-6 月 1 人周

收益(100 QPS 规模):

  • 延迟:-200ms per request
  • 成本:从 $1000/月 降到 $10/月(LLM call)
  • 一年省 $10K+

6 月内回本——大规模项目必做。

什么时候不蒸馏

  • QPS 低(< 10):蒸馏的 ROI 不划算、用 LLM 就行
  • Rewrite 任务太多样:小模型学不会所有类型
  • 业务变化快:需要每月重蒸馏、维护成本高
  • 团队没 ML 能力:蒸馏要模型工程、不是配置 LLM 那么简单

小项目不追蒸馏——用 LLM API 最方便。

蒸馏 vs Fine-tune vs Prompt engineering

方式投入收益适合
Prompt engineering早期
Fine-tune 通用 LLM领域特化
蒸馏小模型大(规模大时)高 QPS
从零训练极高超大独家业务

大多数项目在 prompt engineering → fine-tune → 蒸馏 的路径上——按规模升级。

蒸馏作 RAG 的成本优化主力

Ch14 §14.16 蒸馏 rerank、本节蒸馏 rewriter——加上可能的 embedding 微调、RAG 的"专用小模型"策略成型:

  • Rerank:bge-reranker 微调 / 蒸馏
  • Rewriter:Qwen-1.5B 蒸馏
  • Embedding:bge-m3 on domain data
  • LLM:留给最终 generation、用通用 Claude / GPT

这套组合让 99% 的计算在小模型上、1% 用大 LLM——成本降 20-50×、质量降 2-5 点。规模大的 RAG 几乎都走这条路。

蒸馏是 RAG 工程成熟度的标志

建蒸馏能力的团队 vs 没建的:

  • 成本结构:可控 vs 随 QPS 线性涨
  • 技术自主性:不被厂商锁死
  • 领域适配性:能针对业务微调
  • 持续优化能力:数据飞轮转起来

2026 年大规模 RAG 几乎都要走蒸馏路线——厂商 LLM API 再便宜、也不如自己的小模型便宜。

15.18 跨书关联:Rewrite 和 Agent 的边界

Query Rewrite 和 Agent 多轮检索(第 18 章)在边界上有重叠——后者可以看作"在线交互式 rewrite"。

  • Rewrite:一次 LLM 调用 把 query 变好、继续原流水线
  • Agent:多次 LLM + 检索 循环,Agent 自己决定下一步

边界选择:Rewrite 是 RAG 的线性扩展、Agent 是 RAG 的迭代包装。《LangGraph 设计与实现》讨论的 Agent 编排和 RAG 的 rewrite 在统一的"智能体访问外部知识"框架里。第 18 章会深入。

15.19 本章小结

  1. 真实用户 query 经常不适合直接检索——口语化、多意图、指代、黑话
  2. 五大 Rewrite 模式:扩展 / 拆解 / HyDE / Routing / 上下文消歧
  3. HyDE 是 2023 年后 RAG 领域的 SOTA 技巧——让 LLM 先生成假答案、用假答案 embedding 去检索
  4. 成本和延迟有代价——规则优先 + LLM 兜底是生产原则
  5. Rewrite 不是必做 ——只在归因显示 query 侧是瓶颈时做
  6. 评估 Rewrite 看rewrite 前后 recall 对比——3+ 点提升才值得
  7. 过度改写是反模式——要监控改写率

下一章讨论 Context Packing——把 rerank 后的 top-k chunk 装进有限的 LLM 上下文窗口、保留最高证据密度。

基于 VitePress 构建