什么是 RAG 查询改写(Query Rewriting)?它怎么提升检索效果的?

#RAG#Agent#面试八股

什么是 RAG 查询改写(Query Rewriting)?它怎么提升检索效果的?

结论:查询改写(Query Rewriting)是在检索之前把用户原话重写成更适合检索的表达,专门治四类病: 指代不清、口语与术语不匹配、一条 query 藏多个意图、以及"问题太具体而文档太抽象"。 它不改知识,只提高"query 与文档字面/语义对齐"的概率,所以对召回率的影响是直接的。

1. 四类该改写的查询

类型原始查询改写后手法
指代省略"它的保修期多久"(上一轮在说 iPad)"iPad 的保修期是多久"多轮历史压缩
口语 vs 术语"东西坏了能换吗""质量问题 退换货政策"术语归一、同义词扩展
多意图"报销和请假流程"["报销流程", "请假流程"]子查询分解
抽象度错位"0x0000007B 是什么""Windows 蓝屏 0x0000007B 原因 处理"关键词补全

2. 主流改写手法一览

手法做什么额外成本适用
指代消解用对话历史把"它/这个"替换成实体1 次小模型调用多轮问答
多查询扩展(Multi-Query)生成 3~5 个同义问法并行检索1 次 LLM + N 次检索召回不足
查询分解(Decomposition)拆成多个子问题分别检索1 次 LLM + N 次检索对比、多跳
回溯提问(Step-back)先问更抽象的上一层问题1 次 LLM + 1 次检索具体问题需背景原理
术语归一词典把黑话映射到标准词0内部缩写、别名多
HyDE先写假设答案,用答案去检索1 次 LLM短 query、语义鸿沟大

3. 链路位置

用户 query + 最近 N 轮历史
        │
        ▼
   ┌─ 改写器(规则/小模型)─┐
   ▼            ▼            ▼
 原 query    同义 query    子问题
   │            │            │
   └──── 各检索一次,合并去重 ────┐
                                  ▼
                           重排 → 生成

关键设计:原始 query 永远保留一路。改写器一旦跑偏(把"退货"改成"退款"),还有原查询兜底。

4. 代码:一次调用产出多路查询

import json

REWRITE = """把用户问题改写成便于检索的查询。输出 JSON:
{"queries": ["改写1", "改写2"], "keywords": ["关键实体"]}
规则:补全指代;口语换成文档常用术语;只改表达不改意图;无需改写时原样返回。"""

def rewrite(q: str, history: list[str]) -> list[str]:
    ctx = "\n".join(history[-4:])           # 只带最近 4 轮,避免上下文过长
    resp = llm.chat.completions.create(
        model="gpt-4o-mini", temperature=0,
        response_format={"type": "json_object"},
        messages=[{"role": "system", "content": REWRITE},
                  {"role": "user", "content": f"对话历史:\n{ctx}\n\n当前问题:{q}"}],
    )
    parsed = json.loads(resp.choices[0].message.content)
    return list(dict.fromkeys([q, *parsed.get("queries", [])]))[:4]   # 原 query 必留,最多 4 路

def retrieve_multi(q: str, history: list[str], k: int = 20) -> list[dict]:
    rankings = []
    for sub_q in rewrite(q, history):
        hits = store.query(query_embeddings=embed([sub_q]), n_results=k)
        rankings.append(hits["ids"][0])
    merged = rrf(rankings, k=60)             # 多路结果用 RRF 合并,无需比较分数
    return [doc_index[i] for i in merged[:20]]

5. 收益与风险的账

项目说明
延迟+1 次 LLM(0.1~0.5 s),可用小模型或与检索并行预热压缩
成本每请求多几百 token,可缓存同 query 的改写结果
主要风险语义漂移:改写把范围放大或缩小("3 天"变"一周")
兜底保留原 query 一路;改写结果打日志,定期抽检
评估单变量对比 recall@5,别和换模型一起做

6. 常见追问

Q:简单 query 也会被改坏吗? 会。所以提示词里必须写"无需改写时原样返回",并且原 query 一路不能删。

Q:用小模型还是大模型? 指代消解和术语归一,小模型加 few-shot 就够;子问题分解建议用大一点的模型,拆错了整条链路都白费。

Q:多轮历史怎么带? 滑窗 3~5 轮 + 摘要。全量历史会撑爆上下文,还会把早期话题污染到当前问题。

Q:怎么评估改写有没有用? 固定生成模型与重排,只对比"改写前/后"的召回率与最终答案正确率;两个指标都不涨就下线。

小结

查询改写是"用一次便宜的模型调用,换召回率提升"的典型交易。做它是为了让检索看见用户真正想问的东西——不是让 query 变得漂亮。

笔记加载中…