RAG 检索深度指南:从 Embedding 到 Query 处理(二)
目录
前言:
如果你做过 RAG 系统,一定有过这样的体验:明明 LLM 够聪明,向量库也跑了,可检索出来的结果就是「答非所问」。问题出在哪?绝大多数时候,不是模型不行,而是你的检索管道太粗糙。
这篇文章,我想把 RAG 中最关键、也最容易被轻视的两个模块讲透:Embedding 与向量检索,以及 Query 处理。前者决定了你「能不能找到」,后者决定了你「找得准不准」。作为一个在生产环境踩过无数坑的 Agent 开发者,我会尽量把每个决策点背后的思考讲清楚——不是告诉你「该怎么做」,而是告诉你「为什么该这么做」。
核心观点:RAG 的天花板不在大模型,而在检索。检索质量每提升 10%,最终回答质量可能提升 30%。把精力花在检索管道上,性价比远超换一个更大的模型。
1、Embedding 与向量检索:决定检索质量
上一篇我们讲了数据处理,这一篇聚焦检索本身。检索质量 = Embedding 模型 × 相似度算法 × 向量库 × 索引策略 × 检索方式,任何一个环节选错或调差,都会让最终答案大打折扣。
核心观点:检索质量不是由某一个"最好的组件"决定的,而是由整条链路的 短板决定的。Embedding 模型再好,向量库索引配错,检索照样不准;混合检索做得很完善,但 Query 没改写,原始口语化的问题根本检索不到相关文档。
Embedding 是 RAG 的地基。它的工作很简单——把文本变成一组数字(向量),让语义相近的文本在向量空间中彼此靠近。但「简单」两个字背后,藏着大量影响最终效果的决策。

1.1 Embedding 模型选择:最容易被低估的决策
Embedding 模型是检索链路的第一环,也是影响最大的单一组件。它决定了文本被编码成什么样的向量,而向量空间的质量直接决定了"相似"的含义是否被正确建模。
1.1.1 选型维度
| 维度 | 说明 | 关键考量 |
|---|---|---|
| 语言适配 | 中文优化 / 多语言 / 英文为主 | 中文场景下通用英文模型效果会明显下降;多语言模型在中文专项上不如中文专用模型 |
| 向量维度 | 768 / 1024 / 1536 / 3072 | 维度越高表达能力越强,但存储和计算成本越高 |
| 最大输入长度 | 512 / 2048 / 8192 tokens | 决定单个 chunk 能多大;短输入模型需要更细的分块 |
| 成本 | 开源(免费) / API 调用(按量计费) | 大规模数据需要批量 embedding,API 成本会累积 |
| 延迟 | 本地推理 10-50ms / API 调用 100-300ms | 实时检索场景中 Query embedding 的延迟直接影响响应速度 |
1.1.2 中文场景的推荐选择

选型误区
不要只看 MTEB 排行榜。排行榜用的是公开数据集,你的业务数据分布完全不同。正确做法是:从排行榜选 3-5 个候选模型,用你的真实文档和真实 Query 构建评估集,做 A/B 对比。一个在自己数据上表现好但排行榜一般的模型,远比排行榜第一但没验证的模型可靠。

图 1:Embedding 与向量检索基础流程——文档和 Query 必须使用同一模型编码
Embedding 模型对检索质量的影响,远比你想的大。它本质上决定了「你觉得语义相近的两段文本,在向量空间里是不是真的相近」。一个对中文理解不够好的模型,会把「请假流程」和「年假申请」推到很远的地方——明明在中文语境下,这两者几乎是同义词。
选型建议(个人经验,供参考):
纯中文场景,优先考虑
bge-m3、bge-large-zh、gte-Qwen2这类中文优化模型。它们在中文语义理解上通常比通用英文模型好一个档次。多语言混合场景,bge-m3是目前综合表现最稳的选择——它同时支持中英文,且提供了稠密、稀疏、多向量三种检索模式。选型时重点看四个维度:模型维度(影响存储和检索速度)、最大输入长度(决定你能塞多大的 chunk)、MTEB/C-MTEB 排名(衡量检索质量的基准测试)、以及推理成本与延迟(生产环境必须考虑)。
一个常见的误区是:只看 benchmark 分数,不看实际业务数据。我的建议是,拿你自己的业务数据做一个小规模评测。跑 100-200 条 query,看不同模型在你的场景下的 recall@10 和 MRR。这比看任何排行榜都靠谱。
1.2 向量相似度:不只是「余弦」那么简单
把文本变成向量后,怎么判断两个向量"有多相似"?这就是相似度计算的问题。看似简单,但不同计算方式在不同场景下效果差异不小。

余弦相似度是最常用的。它只关心两个向量的「方向」是否一致,不关心「长度」。好处是对向量模长不敏感,坏处是丢失了模长中可能携带的信息。大多数现代 Embedding 模型会对输出做归一化,归一化后余弦相似度和点积是等价的——这也是为什么很多向量库默认用余弦。
点积同时考虑方向和长度。如果你的 Embedding 没有做归一化,点积可能更合适。某些模型(如 ColBERT)的 late interaction 机制天然就是基于点积的。
欧氏距离直观但用得少。它在图像检索中比较常见,文本场景下通常不如余弦。
实用建议
文本检索默认用余弦相似度。原因:Embedding 模型训练时通常以余弦相似度作为优化目标,推理阶段保持一致效果最好。注意:不同向量库默认的相似度算法可能不同——Milvus 默认用 IP(内积),Qdrant 默认用 Cosine,如果不确认,一定要显式指定。
归一化陷阱
如果用点积(IP)做相似度,但 Embedding 模型输出的向量没有归一化,那么长文本(模长大)会在检索中天然获得更高分数,导致检索偏向长文档。解决方案:在写入向量库之前统一做 L2 归一化(
vector / np.linalg.norm(vector)),归一化后点积等价于余弦相似度。
1.3 向量数据库:选型没有银弹
向量数据库的选型,本质上是一个在「开发效率」、「查询性能」、「运维成本」和「功能丰富度」之间做权衡的过程。
1.3.1 本地开发 vs 生产级
| 类别 | 方案 | 特点 | 适用阶段 |
|---|---|---|---|
| 本地开发 | Chroma | Python 原生,API 简洁,开箱即用 | 原型验证、小规模实验 |
| FAISS | Meta 开源,纯库无服务,极致性能 | 单机大规模检索、嵌入式场景 | |
| LanceDB | 列式存储,支持多模态,Serverless | 中小规模、多模态检索 | |
| 生产级 | Milvus | 分布式架构,百亿级向量,社区活跃 | 大规模企业场景 |
| Qdrant | Rust 编写,高性能,过滤能力强 | 中大规模、元数据过滤密集 | |
| Weaviate | 内置模块化检索,支持混合检索 | 需要混合检索开箱即用 | |
| Elasticsearch | 成熟生态,BM25+向量双引擎 | 已有 ES 基础设施的团队 | |
| OpenSearch | ES 分叉,开源许可更友好 | 需要开源许可合规的团队 | |
| Pgvector | PostgreSQL 扩展,SQL 原生 | 中小规模、不想引入新组件 |

选型建议
如果你是从零开始,Qdrant 是目前最推荐的"开发到生产平滑过渡"方案:本地单机模式开发,上线后切换到分布式模式,代码零改动。如果你已有 PostgreSQL 基础设施且数据量在百万级以内,pgvector 是最低成本选择——不用引入新组件,一条 SQL 就能做向量检索。
1.4 索引与检索优化:性能的关键
当向量数据量从几千涨到百万、千万甚至亿级时,暴力检索(逐一计算相似度排序)完全不可行。这时就需要近似最近邻(ANN)索引算法来加速检索。

图 2:暴力检索 vs ANN 索引检索——数据量一大,索引是唯一出路
1.4.1 三大索引算法
| 算法 | 原理 | 检索精度 | 检索速度 | 构建速度 | 内存占用 |
|---|---|---|---|---|---|
| HNSW | 分层小世界图,从顶层粗导航逐层细化 | 高(最接近暴力检索结果) | 极快 | 慢(需构建多层图) | 大(需存储图结构) |
| IVF | K-Means 聚类分桶,只检索最近的几个桶 | 中(取决于探针数 nprobe) | 快 | 快 | 小 |
| 量化压缩 | PQ/SQ 将浮点向量压缩为整型编码 | 低-中(有精度损失) | 极快 | 中 | 极小(压缩 8-32 倍) |
当你的向量库里有几百万、几千万条数据时,暴力搜索(逐一计算相似度)就不现实了。这时候你需要 ANN(Approximate Nearest Neighbors,近似最近邻)索引。
ANN 的核心思想是:牺牲一点点精度,换取几个数量级的速度提升。它通过精巧的数据结构,把搜索范围从「全量」缩小到「一小部分最有可能的候选」。
目前主流的 ANN 算法有两种:
HNSW(Hierarchical Navigable Small World)是当前的王者。它构建了一个多层图结构,上层稀疏用于快速跳跃,下层密集用于精细搜索。优点是查询速度快、召回率高;缺点是内存占用大(需要把图和向量都放在内存里)。大多数场景下,HNSW 是默认选择。
IVF(Inverted File Index)先用聚类把向量空间分成若干区域,查询时只在最近的几个区域里搜索。优点是内存友好(可以配合量化压缩);缺点是需要调参(nlist、nprobe),且在数据分布不均匀时效果会下降。
量化压缩是另一个重要的优化手段。PQ(Product Quantization)把高维向量压缩成短的编码,可以把内存占用降低 8-16 倍,代价是召回率下降 5-15%。SQ(Scalar Quantization)更温和一些,把 float32 压成 int8,内存减半,精度损失很小。在内存紧张的生产环境,量化几乎是必选项。
工程实践
生产中最常用的组合是 HNSW + 量化压缩:用 HNSW 保证检索精度,用量化压缩降低内存占用。Milvus 和 Qdrant 都原生支持这种组合。
IVF 适合你的场景如果:数据量极大(亿级以上)、对检索精度要求不是特别高、或者需要频繁新增数据(IVF 增量构建比 HNSW 快得多)。
1.4.2 关键调优参数

1.4.3 规模化:分片、副本、负载均衡
百万级向量单机就够了,但到了千万级甚至亿级,就需要分布式架构:
- 分片(Sharding):将向量按哈希或范围分散到多个节点。检索时并行查询所有分片,合并 Top-K 结果。这是横向扩展的基础。
- 副本(Replica):每个分片维护 1-2 个副本,保证高可用。单节点宕机不影响检索服务。
- 负载均衡:请求均匀分配到各节点,避免热点。Milvus 的 QueryNode 和 Qdrant 的 Sharding Replica 都内置了负载均衡机制。
1.5 混合检索:单靠向量是不够的
纯向量检索有一个结构性弱点:它擅长语义相似,但不擅长精确匹配。比如用户搜"ErrorCode: E4042",向量检索可能返回"错误码 E4031"之类语义相近但实际不对的结果。
这就是为什么你需要混合检索:把向量检索(擅长语义匹配)和 BM25(擅长关键词精确匹配)结合起来。
1.5.1 为什么需要混合
| 检索方式 | 擅长 | 不擅长 |
|---|---|---|
| 稠密向量检索 | 语义相似、同义表达、模糊匹配 | 精确关键词、专有名词、代码标识符 |
| 稀疏检索(BM25) | 精确匹配、关键词命中、专有名词 | 同义表达、语义理解、跨语言 |
| 混合检索 | 两者优势叠加,兼顾语义和精确 | 链路更长,需要调融合参数 |

图 3:混合检索架构——向量检索 + BM25 双路召回,融合后重排序
1.5.2 融合排序策略
两路检索各返回 Top-K 结果后,怎么合并成一个统一排序?两种主流方案:

融合策略最常见的是 RRF(Reciprocal Rank Fusion):对每条文档,取它在两路检索结果中的排名,算一个倒数之和作为最终分数。公式很简单:score = 1/(k+rank_dense) + 1/(k+rank_sparse),其中 k 通常取 60。RRF 的好处是不受原始分数量程影响,鲁棒性强。
另一种是加权融合:给两路的分数分别乘以权重(比如 0.7:0.3),然后求和。好处是可以调节,坏处是两路分数的量程可能不同,需要归一化。
实战数据:在我做过的几个企业知识库项目中,混合检索相比纯向量检索,Top-5 召回率平均提升了 15-25%。提升最明显的 case 恰好是那些包含专有名词、产品编号、人名等「不可语义化」信息的查询。如果你的知识库里有大量这类内容,混合检索不是可选项,而是必选项。
实战推荐
如果你不知道该选哪个融合策略,先用 RRF。它是我在项目中用得最多的方案——不需要调参、不依赖分数可比性、对两路检索质量差异不敏感。Qdrant 和 Weaviate 都内置了 RRF 支持。
1.6 过滤检索:别忘了元数据的价值
在真实业务中,几乎不存在"在所有文档中无差别检索"的需求。用户往往只关心特定范围:"这个季度的产品文档""我所在部门的制度""公开级别的知识"。这就是过滤检索的价值。
过滤的三个维度
- 按元数据过滤:文档类型、部门、标签、来源等。比如只检索"产品规格"类文档,排除"会议纪要"。
- 按权限过滤:根据当前用户的角色和权限,只检索其有权访问的文档。这是企业 RAG 的硬性要求。
- 按时间/业务线过滤:只检索 2024 年 Q3 之后的文档、只检索某条业务线的产品手册。
过滤的实现方式很关键
有两种实现方式:
· 先过滤后检索(Pre-filter):先按元数据缩小范围,再在子集中做向量检索。精度高,但如果过滤后子集太小可能召回不足。
· 检索后过滤(Post-filter):先做全量向量检索拿到 Top-K,再按元数据过滤。速度快,但可能过滤后剩余结果很少。
生产中推荐用向量库原生支持的过滤检索(如 Qdrant 的 Filter、Milvus 的 Attribute Filter),它们在索引层面做了优化,兼顾过滤精度和检索效率。
# Qdrant 过滤检索示例
client.search(
collection_name="rag_docs",
query_vector=query_embedding,
query_filter=Filter(
must=[
FieldCondition(key="department", match=MatchValue(value="product")),
FieldCondition(key="date", range=Range(gte="2024-01-01")),
FieldCondition(key="permission", match=MatchAny(any=["public", "internal"])),
]
),
limit=10
)
2、Query 处理:用户问题不能直接拿去检索
前面讲的是"检索引擎"本身的能力。但还有一个经常被忽略的环节:用户的问题本身。
用户的原始 Query 有多"烂"?举个例子:用户在多轮对话中说"那它跟 V2 版本有什么区别?"。这个 Query 直接做 embedding 检索,检索到的内容大概率是垃圾——因为"它"指代不明,"V2 版本"是哪个产品?没有上下文,向量模型也猜不到。
一个残酷的事实是:用户输入的问题,绝大多数时候都不适合直接拿去检索。它可能是口语化的(「那个啥啥怎么弄来着」),可能是模糊的(「帮我看看那个政策」),可能是缺上下文的(多轮对话中的追问)。直接拿这种 query 去检索,就像拿着一团模糊的毛线去找另一团毛线——能找对才怪。

核心问题
用户原始问题往往口语化、模糊、缺上下文。直接拿去 embedding,效果会很差。Query 处理的目标是:在检索之前,把"烂问题"变成"好问题"——让检索引擎能够理解用户真正想找什么。
2.1 查询改写:让问题变得「可检索」
查询改写的目标很明确:把用户的原始输入,变成一个更适合检索的形式。
这包括几个子任务:纠错(用户打错了字,「人工智障」→「人工智能」)、补全(用户说得太简略,「请假」→「员工请假审批流程」)、去口语化(「那个啥啥报销怎么弄来着」→「费用报销流程」)、提取关键词(从长句中提取核心检索词)。
最直接的实现方式是用 LLM 做改写。给它一个 prompt:「请将以下用户问题改写为更适合知识库检索的形式,保持语义不变,使其更加精确和完整」。成本低、效果好,是目前最实用的方案。
一个容易踩的坑:查询改写不是越详细越好。过度改写会引入噪声,把检索带偏。比如用户问「K8s 怎么扩容」,你改写成「Kubernetes 集群如何在生产环境中进行水平节点扩展和垂直资源调整」——看起来更专业了,但「垂直资源调整」是你脑补的,可能把不相关的文档拉进来。改写应该保持克制,目标是「精确化」而不是「膨胀化」
| 改写类型 | 原始 Query | 改写后 | 目的 |
|---|---|---|---|
| 纠错 | "embeding 模型选哪个" | "embedding 模型选哪个" | 修复拼写错误,避免检索偏移 |
| 补全 | "向量库" | "向量数据库推荐" | 补全为完整的检索意图 |
| 去口语化 | "那个检索效果不好咋办" | "RAG 检索效果优化方法" | 将口语表达转为检索友好的关键词 |
| 提取关键词 | "我想了解一下 Milvus 和 Qdrant 哪个更适合中小团队" | "Milvus vs Qdrant 中小团队 选型" | 去掉冗余词,保留检索核心词 |
# 查询改写 prompt 示例
rewrite_prompt = """你是一个查询改写助手。
将用户的原始问题改写为适合向量检索的形式:
1. 修正拼写错误
2. 补全不完整的表达
3. 将口语化表达转为书面关键词
4. 保留原始意图,不增加或改变语义
用户问题:{user_query}
改写后:"""
2.2 查询扩展:一个角度不够,就多试几个
查询扩展的思路是:用户的一个问题,可能只触及了答案的一个角度。如果你能从多个角度去检索,召回正确答案的概率就更大。
同义词扩展是最基础的:把「辞职」扩展为「辞职 / 离职 / 解除劳动合同」。在中文场景下,这尤其重要,因为同一个概念的表达方式太多了。
多角度改写更进一步:把「怎么优化网页加载速度」改写成多个版本——「网页性能优化方法」、「前端加载速度提升技巧」、「Web 页面响应时间优化」。每个版本侧重不同,检索结果也会有差异。
多查询检索(Multi-Query Retrieval)是 LangChain 里一个很好用的技术:用 LLM 把原始问题生成 3-5 个不同角度的查询,分别检索,然后合并去重。这相当于用多次检索的开销,换取更高的召回率。在很多场景下,这个 trade-off 是值得的。

图 4:多查询扩展检索——一个 Query 生成多个改写版本,分别检索后融合
成本与收益的平衡
多查询扩展的代价是 N 倍的检索延迟和 API 调用(LLM 生成改写 + N 次向量检索)。建议 N=3-5,超过 5 个收益递减明显。如果延迟敏感,可以让多个检索并行执行,总延迟约等于单次检索。
2.3 HyDE:假设性文档嵌入
HyDE(Hypothetical Document Embeddings)是一个反直觉但非常有效的技巧。核心思路是:先让 LLM 生成一个假设性答案,再用这个答案去检索,而不是用原始问题去检索。
为什么有效?因为问题和答案在向量空间中的分布不同。答案文本通常包含更多与文档相似的特征词,而问题往往很短、很抽象。用"假答案"检索,比用"真问题"检索更容易命中正确文档。

图 5:HyDE 工作流——先用 LLM 生成假设答案,再用答案检索真实文档
为什么 HyDE 有效?因为「问题」和「答案」在语言上的差距,往往比「问题」和「问题」的差距小。用户问「年假有多少天」,文档写的是「员工年度带薪休假天数为 5 天」——这两者在词汇上几乎没有重叠,但语义是相通的。而 LLM 生成的假设答案,恰好充当了从「问题语言」到「文档语言」的桥梁。
当然,HyDE 也有局限:它依赖 LLM 生成的假设答案不能太离谱。如果 LLM 的假设答案方向就错了,那检索结果也会偏。另外,每次查询多了一次 LLM 调用,延迟和成本都会增加。我的建议是:在事实性问答场景优先尝试 HyDE,在开放性问答或闲聊场景则不必。
HyDE 的适用边界
HyDE 不是万能的。它适合问题简短抽象、文档详细具体的场景。如果问题本身已经很长很具体,HyDE 生成的假答案可能反而引入噪声。另外,HyDE 增加了一次 LLM 调用的延迟,对延迟敏感的场景需要权衡。
2.4 多查询检索
多查询检索和查询扩展有相似之处,但更侧重于拆解而非改写。把一个复杂问题拆成多个子问题,分别检索,最后合并。
比如用户问:"对比 Milvus 和 Qdrant 在过滤检索、分布式扩展和运维成本三方面的区别"。一个问题做检索很难同时命中三方面的内容。拆成三个子问题:
- "Milvus vs Qdrant 过滤检索能力对比"
- "Milvus vs Qdrant 分布式扩展架构"
- "Milvus Qdrant 运维成本对比"
每个子问题独立检索,各拿到 Top-K,最后合并去重。这样每个维度都有专门的检索结果,综合答案更完整。
和查询扩展的区别
查询扩展是同一意图的多表达(换个说法),多查询检索是同一问题的多子任务(拆成子问题)。前者解决"表达不对"问题,后者解决"问题太复杂"问题。两者可以叠加使用。
2.5 意图识别:不是所有问题都需要检索
不是所有用户输入都需要检索。意图识别是 Query 处理的"前置路由器"——在决定怎么检索之前,先判断要不要检索、检索什么。
这是一个看似简单、实则极其重要的能力:判断用户的问题到底需不需要检索。
想想看,如果用户问「你好」或者「1+1等于几」,你还要去知识库里检索一番,这不是浪费资源吗?更糟糕的是,检索可能返回不相关的内容,反而干扰了 LLM 的回答。
意图识别要做的事情包括:
第一,判断是否需要检索。闲聊、常识问答、数学计算这些,直接让 LLM 回答就好,不需要检索。只有当问题涉及到特定领域的知识、可能随时间变化的信息、或者你的私有数据时,才需要检索。
第二,判断问题属于哪个知识域。如果你的系统接入了多个知识库(比如 HR 制度库、技术文档库、产品手册库),先判断问题属于哪个域,再去对应的库里检索,效果和效率都会好很多。
第三,判断问题类型。事实问答(「年假几天」)、总结(「总结一下这个项目的进展」)、对比(「A 方案和 B 方案有什么区别」)、推理(「如果销量增长 20%,产能是否跟得上」)——不同类型的问题,检索策略和 prompt 构造方式应该不同。

# 意图识别 prompt 示例
intent_prompt = """分析用户问题的意图,返回 JSON:
{{
"need_retrieval": true/false,
"domain": "product" | "policy" | "tech" | "general",
"type": "fact" | "summary" | "comparison" | "reasoning" | "chitchat"
}}
用户问题:{user_query}
"""
工程实现建议
意图识别不需要一个很大的模型。用一个微调过的小模型(甚至规则 + 关键词匹配)就能覆盖 80% 的场景。关键是把意图分类的体系设计好,并且和后续的检索策略、prompt 模板联动起来。我见过一些团队用 GPT-4 做意图识别——杀鸡用牛刀了。
关键是保证分类速度快、准确率高——如果意图判断错误,后面的检索和生成全白做。
2.6 多轮对话改写:最容易被忽视的难题
如果你做过对话式 RAG(ChatBot),你一定遇到过这个问题:
用户第一轮问:「公司的补充医疗保险怎么报销?」
AI 回答:「根据 XX 制度,补充医疗保险的报销流程是……」
用户第二轮问:「那牙科呢?」
这个「那牙科呢?」直接拿去检索,几乎不可能找到正确答案——因为它缺了关键上下文:「牙科」是「补充医疗保险的牙科项目报销」。这就是多轮对话中的指代消解和省略补全问题。
解决方案是:在每一轮对话时,把历史对话作为上下文,用 LLM 把当前问题改写成一个独立的、自包含的查询。Prompt 大致是这样的:「给定以下对话历史和用户最新问题,将最新问题改写为一个独立的完整问题,补全省略的信息,替换指代词。」
「那牙科呢?」+ 历史上下文 → 「公司补充医疗保险是否覆盖牙科项目的报销?」
一个进阶技巧:不要只改写最后一轮。有时候用户第三轮的问题,需要回溯到第一轮才能理解完整意图。建议保留 3-5 轮历史对话作为改写上下文,但不要把太多历史塞进去——噪声太多反而会干扰改写质量。
另一个注意点:改写后的 query 应该给用户看到(至少在调试模式下)。这样当改写出了问题(过度改写、改写方向错误),用户可以及时纠正。这在生产环境的 debug 中极其有用。
另:这是 Query 处理中最容易被遗漏的环节。在多轮对话中,用户的问题往往依赖前几轮的上下文:
| 对话轮次 | 用户原始问题 | 改写后(用于检索) | 改写类型 |
|---|---|---|---|
| Turn 1 | "RAG 的分块策略有哪些?" | "RAG 的分块策略有哪些?"(无需改写) | — |
| Turn 2 | "哪个效果最好?" | "RAG 分块策略中哪个效果最好" | 补全省略信息 |
| Turn 3 | "它和语义分块比呢?" | "父子分块策略和语义分块策略的效果对比" | 指代消解 |
| Turn 4 | "那具体怎么实现?" | "父子分块策略的具体实现方法" | 上下文依赖 |
如果不做多轮改写,Turn 2-4 的 Query 直接检索,向量模型完全不知道"哪个""它""怎么实现"指的是什么,检索结果毫无意义。

图 6:多轮对话改写——将依赖上下文的省略问题补全为完整的独立 Query
实现要点
多轮改写需要将对话历史和当前问题一起发给 LLM,让它理解上下文后生成一个完整的独立问题。关键细节:
· 只保留最近 3-5 轮对话,太长会超出 token 限制且引入噪声
· 改写后的问题应该是自包含的——不看对话历史也能理解
· 如果当前问题本身已经完整,直接返回不改写,避免过度处理
# 多轮对话改写 prompt
rewrite_prompt = """根据对话历史,将用户最后一条消息改写为自包含的独立问题。
规则:
1. 补全省略的主语和宾语
2. 解析代词指代(它、那个、这个)
3. 如果当前问题已自包含,直接返回
4. 不要改变原始意图
对话历史:
{chat_history}
当前问题:{current_query}
改写后的问题:"""
写在最后
检索质量是 RAG 的命脉。而检索质量不是一个单一指标,它是 Embedding 模型、向量库、索引策略、检索方式、Query 处理等多环节的综合结果。
本文覆盖了两个核心维度:
- 检索引擎侧:Embedding 模型选型(中文优先考虑中文优化模型)、相似度算法(默认余弦)、向量库选型(开发用 Qdrant,大规模用 Milvus)、索引优化(HNSW + 量化压缩)、混合检索(向量 + BM25 + RRF 融合)、过滤检索(元数据预过滤)。
- Query 处理侧:查询改写(纠错 + 去口语化)、查询扩展(多角度改写 + 多查询召回)、HyDE(假设答案检索)、多查询检索(拆子问题)、意图识别(路由前置判断)、多轮对话改写(指代消解 + 上下文补全)。
如果你在 RAG 项目中遇到"检索不准"的问题,排查路径建议:先看 Query 处理 → 再看 Embedding 模型 → 然后看分块策略 → 最后看检索方式和索引参数。从我经手的项目经验看,Query 处理和分块策略的问题占比最大。
记住:检索是 RAG 的地基。地基不打牢,上面的生成再华丽,也是空中楼阁。
RAG文章:
RAG 深度解析:从原理到工程实践(一)
RAG 检索深度指南:从 Embedding 到 Query 处理(二)
更多推荐


所有评论(0)