Query-Rewrite-RAG

用户问「WGS 怎么做」,库里写的是「全基因组测序文库构建」,短查询/缩写对不上。根因是查询语言与文档语言不在同一分布。本方法改写或多查询扩展后再检索。乱改写会引入主题漂移,必须用冻结 Recall 看净收益。

本文属于 RAG 工程框架中的「2 索引与召回」环节,聚焦「Query Rewrite(查询改写)」方法。

定位

维度 内容
角色 检索前的查询变换
输入 → 输出 $q$ → ${q’_i}$ → 多路 hits 再合并
默认组合 LangChain query transform;LlamaIndex HyDE
何时不用 用户已经用论文原词;改写后 Recall 不升

核心机制

$$
{q’_i}=\mathrm{Rewrite}(q),\quad
\mathrm{Recall}(\bigcup_i T_k(q’_i))\ge \mathrm{Recall}(T_k(q))
$$

HyDE 是先写假想段落再嵌入,也属改写族。

实现路径与心智:索引不动,先用模板或小模型把用户问法改写成语料措辞(同义、拆子问、HyDE),对每个 $q’$ 检索再合并。底层心智:修的是查询分布,不是文档几何。乱改写等于引入新主题,必须用冻结 Recall 看净收益。

优缺点

  • 优点:短问、缩写、口语提升大。
  • 缺点:时延 +50–300 ms;漂移会降 Recall。

契约与走通样例

输入

1
{"query": "WGS 怎么做"}

中间量

$q’$ = whole-genome sequencing library construction。原问 Recall@5=$0.2$;改写后 $0.8$。第三条乱改写「WGS 天气」使 Precision 掉 0.15,丢弃。

输出

1
{"queries": ["WGS 怎么做", "whole-genome sequencing library construction"], "hits": [{"doc_id": "P-WGS-03-C1"}]}

社区实现

LangChain query transform;LlamaIndex HyDE。风险:HyDE 幻觉段落会把检索拉到错误方法。

工程落地

最小可运行示例

复制为 .py 后直接运行(仅标准库)。生产改写器用 LangChain query transform / LlamaIndex HyDE,检索器仍走原索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
"""保留原问 + 少量改写,并行检索后按 doc_id 去重合并。"""
from __future__ import annotations


class DomainRewriter:
"""领域词表改写。生产对应 LLM rewriter;HyDE 会生成假段落,货号问不要用。"""

table = {"kd": "knockdown", "GAPDH": "GAPDH knockdown"}

def rewrite(self, query: str) -> list[str]:
variants = [query]
low = query.lower()
if "kd" in low.split():
variants.append(query.replace("kd", "knockdown").replace("KD", "knockdown"))
return list(dict.fromkeys(variants))


class KeywordRetriever:
"""被改写查询共用的底库。生产对应同一套 BM25/dense 索引。"""

def __init__(self, corpus: dict[str, str]) -> None:
self.corpus = corpus

def search(self, query: str, k: int) -> list[str]:
q = set(query.lower().split())
ranked = sorted(self.corpus, key=lambda i: -len(q & set(self.corpus[i].lower().split())))
return ranked[:k]


def expand_retrieve_merge(query: str, rewriter: DomainRewriter, retriever: KeywordRetriever, k: int) -> list[str]:
"""输入原问;输出去重后的 hits(原问必须在查询集合里)。"""
seen: list[str] = []
for q in [query, *rewriter.rewrite(query)]:
for doc_id in retriever.search(q, k):
if doc_id not in seen:
seen.append(doc_id)
return seen


if __name__ == "__main__":
retriever = KeywordRetriever(
{
"P-kd": "GAPDH knockdown siRNA 20 nM for 48 h",
"P-noise": "cell culture media recipe",
}
)
print(expand_retrieve_merge("GAPDH kd timing", DomainRewriter(), retriever, k=1))

参数

参数 起点 影响
改写条数 1~3 过多必漂移
是否保留原问 true 防止改写全错

失效—信号—螺丝

  • 主题漂移:冻结 Recall 下降。螺丝:少生成、加原问、领域词表。
  • 时延爆:螺丝:小模型改写或缓存同义。
  • HyDE 编造试剂:螺丝:HyDE 只用于叙述类,货号问走 BM25。

规模(100 篇生物学 PDF)

索引体量不变。在线每问 +50–300 ms(视条数)。离线建库不变。

-------------本文结束感谢您的阅读-------------