详解重排序、混合搜索、上下文压缩等九种提升RAG系统检索质量的具体技术方案,附各技术在流水线中的适用位置。
检索增强生成(RAG)通常被描述为一个简单的流水线:
Query → Retrieve documents → Send context to an LLM → Generate answer
然而在生产环境中,检索很少这么简单。
检索器可能返回无关文档。重要信息可能埋藏在文档中间。查询可能对于语义搜索来说过于模糊。检索到的块可能丢失其周围上下文。有时模型根本不需要检索。
因此,RAG 系统的质量很大程度上取决于信息如何被检索、过滤、排序、压缩以及呈现给模型。
本指南涵盖九种技术,分别解决 RAG 流水线的不同环节:
多查询检索(Multi-Query Retrieval)
父文档检索(Parent Document Retrieval)
检索候选文档。重排序找出最佳。
向量数据库可能搜索数百甚至数千份文档,并返回前 20 个候选块。
但向量搜索的第一个结果不一定就是最佳结果。
例如,假设正确答案排在第 19 位。
如果应用程序只向 LLM 发送前 3-5 个块,正确信息永远无法触达模型。
100 Pages
↓
Vector Search
↓
20 Candidate Chunks
│
┌───────────┴───────────┐
↓ ↓
Without Reranking With Reranking
↓ ↓
Top 3–5 Chunks Reranker
↓ ↓
│ Top 5 Relevant
│ Chunks
↓ ↓
↓ ↓
LLM LLM
没有重排序时,最相关的块可能排在第 19 位,永远无法触达模型。
有了重排序,重排序器使用查询和每个块的内容来评估检索到的候选文档,将最相关的结果提升到顶部。
重排序器可以将之前排名较低但高度相关的块移到顶部。
重排序器做什么?
重排序器本质上在问:
这些检索到的块中,哪个最能回答用户的问题?
与基本的向量相似度不同,重排序器可以检查整个查询与检索到的文档之间的关系。
更好地理解上下文
发现隐藏的相关性
提高最终答案的质量
常见方法包括:
Cross-Encoder 高准确率 较慢
Bi-Encoder + Rerank Model 均衡的性能
Bi-Encoder + Rerank Model
LLM-based Reranker 潜在最高质量 成本更高
潜在最高质量
假设开发者问:
如何让 Node.js API 处理数千个并发连接?
基本的向量搜索可能最初返回关于 HTTP 状态码、API 认证或通用 Node.js 语法的块。
重排序器可以直接将每个候选文档与问题进行比较,优先考虑讨论连接处理、异步 I/O、事件循环、连接池和水平扩展的内容。
检索更多。重排序更智能。让 LLM 看到最佳上下文,而不仅仅是首个上下文。
语义 + 关键词 = 更好的检索
语义向量搜索和关键词搜索解决不同的问题。
向量搜索理解含义。
关键词搜索理解精确词语。
仅使用一种可能导致重要文档被遗漏。
"Redis connection timeout"
语义搜索可能返回:
诊断缓存连接失败
分布式缓存故障排除
应用基础设施中的网络延迟
这些文档可能在语义上相关,但确切的短语"Redis connection timeout"可能不会出现。
关键词搜索(如 BM25)可以找到:
Redis 连接超时配置
修复 Redis 客户端超时错误
Redis 套接字超时设置
但当文档使用不同术语时,关键词搜索可能失败。
混合搜索结合两种方法:
User Query
│
┌─────────┴─────────┐
↓ ↓
Vector Search Keyword Search
(Semantic) (BM25)
│ │
└─────────┬─────────┘
↓
Merge & Rank
↓
Final Results
两个搜索的结果被合并并排序。
常用排序方法
流行方法包括:
Reciprocal Rank Fusion (RRF)
加权分数组合
相对分数融合
为什么混合搜索有效
向量搜索擅长理解意图:
"cache performance troubleshooting"
关键词搜索擅长精确术语:
"Redis MISCONF"
生产搜索系统通常两者都需要。
何时使用混合搜索
混合搜索特别适用于:
技术文档
精确技术术语
自然语言查询
需要高召回率和高精确率的系统
不要在含义和关键词之间选择。两者都用。
好的块 → 更好的检索 → 更好的答案
分块是 RAG 系统中最重要的决策之一。
文档通常太大,无法作为单个单元进行嵌入和检索,因此必须将它们分成更小的片段。
但块大小很重要。
如果块太大:
重要信息可能被埋没
检索变得不精确
更多无关上下文到达 LLM
如果块太小:
可能引入更多噪声
单个块可能不包含足够信息来回答问题
块要尽可能小以保证精确度,但也要足够大以保证完整性。
文档被分成固定 token 数量的块。
Document
↓
400 tokens
↓
400 tokens
↓
400 tokens
↓
...
可以在块之间添加重叠。
对一般文档效果合理
可能在句子或概念中间拆分
系统不是在任意 token 边界分割,而是在句子边界周围分割。
Sentence 1
Sentence 2
Sentence 3
Sentence 4
Sentence 5
Sentence 6
更好地保留含义
更自然、更易读
句子长度可能差异很大。
语义分块根据含义对句子或段落进行分组。
Topic A
├── Authentication configuration
├── Token validation
└── Session management
Topic B
├── Database indexing
├── Query planning
└── Connection pooling
将相关内容保持在一起
使用小块进行检索,但将更大的父 section 返回给 LLM。
Large Parent Document
│
┌──────────┼──────────┐
↓ ↓ ↓
Small Small Small
Chunk Chunk Chunk
小块提供检索精确度,而父文档提供上下文。
使用移动窗口创建重叠块。
Window 1
████████
Window 2
████████
Window 3
████████
这在块边界之间保留更多上下文。
保持上下文流动
更多块意味着更多存储和潜在的更高检索成本。
利用文档结构来确定块边界。
# Authentication
↓
Chunk 1
## Token Validation
↓
Chunk 2
- Access token
- Refresh token
↓
Chunk 3
Configuration Table
↓
Chunk 4
Code Block
↓
Chunk 5
这特别适用于:
如何选择?
没有通用的最佳分块策略。
正确的策略取决于数据。
生产系统可能结合多种方法:
Structure-aware splitting
+
Semantic grouping
+
Parent document retrieval
还应该尝试不同的块大小,例如:
256 tokens
512 tokens
1024 tokens
并测量实际检索性能。
好的块带来正确的上下文。正确的上下文帮助 LLM 生成正确的答案。
一个问题。多个视角。更好的结果。
单个查询可能失败,因为文档可能使用完全不同的语言描述同一概念。
即使查询扩展改进了措辞,仅在一个方向上搜索仍可能遗漏相关文档。
不要只搜索一次,而是让 LLM 生成多个版本的查询。
How does OAuth token refresh work?
系统可能生成:
What is OAuth token refresh?
How does a refresh token work?
What happens when an access token expires?
How does an application obtain a new access token?
What is the OAuth refresh-token flow?
每个查询都独立搜索。
Original Question
↓
Generate Queries
↓
┌────────┬────────┬────────┐
↓ ↓ ↓ ↓
Search Search Search Search
└────────┴────────┴────────┘
↓
Merge & Rerank
↓
Final Chunks
不同文档使用的术语也不一样。
某份文档可能说:
OAuth token refresh
而另一份可能说:
renewing an expired access token
或者:
obtaining a new bearer token using a refresh credential
多查询方式为检索器提供了更多找到相关信息的机会。
Multi-Query 与 Query Expansion 的区别
这两个概念相关,但并不完全相同。
多查询检索尤其适用于:
大型知识库
技术文档
生产环境 RAG 系统
不要只问一次。用多种聪明的方式去问。
更多的角度意味着检索器有更多机会找到正确的信息。
小块 = 更精准的搜索。父文档 = 更好的理解。
小块之所以有用,是因为它们让检索更精确。
但小块也有问题:
它们会丢失上下文。
考虑检索到这样一个块:
"... it automatically retries failed operations ..."
这个块可能是相关的,但仅凭它并不能告诉我们"it"指的是什么。
原始段落可能是这样写的:
"The job processor automatically retries failed operations when a worker temporarily loses access to the message queue."
父文档提供了缺失的上下文。
Parent Document Retrieval 的工作原理
Document
↓
Chunk 1
Chunk 2
Chunk 3
Chunk 4
...
向量数据库检索最相关的块。
Top Chunks:
1
2
8
9
10
每个块都存储了对其父段落或父文档的引用。
Chunk 8
↓
Parent Document / Section
不要只给 LLM 提供那个小块的文本,而是提供相关的父段落。
Small chunks → Search
Parent document → Context
这创造了一种有用的分离:
检索用小的。阅读用大的。
Parent document retrieval 在以下情况下很有用:
块非常小
文档包含大量引用
答案需要周围上下文
代词和指代很常见
含义依赖于段落中其他位置的信息
为每个块存储一个 parent_id。
Chunk:
{
id: "chunk_123",
parent_id: "section_42",
embedding: [...]
}
检索后,用 parent_id 获取更大的上下文。
块帮助你找到信息。父文档帮助模型理解信息。
太多上下文可能和太少一样糟糕。
假设检索器返回了 40 个块,但你的 LLM 只能有效处理其中 8 个有用的块。
发送全部 40 个块会带来几个问题:
更多无关信息
答案可能更差
这与"中间丢失"问题相关:重要信息在大量无关上下文中时,模型可能更难使用。
Context compression 的工作原理
40 Retrieved Chunks
↓
Compress
↓
Keep Relevant Information
↓
8 Clean Chunks
↓
LLM
压缩器尝试移除所有对回答问题没有实质性贡献的内容。
什么可以被压缩?
常见目标包括:
低相关性信息
冗长的解释
常用的压缩技术
将每个块总结成更小的表示。
Large chunk
↓
1–2 sentence summary
保留最重要的术语和短语。
移除在检索到的文档中反复出现的信息。
只保留直接有助于回答查询的句子。
对单个句子或块打分,只保留高分内容。
假设检索返回了 40 个块。
40 chunks
↓
8 chunks
↓
~75% token reduction
↓
Better focused context
具体改进取决于数据和压缩方法,但目标是让上下文变小而不丢失有用信息。
更多上下文并不总是更好。相关的上下文才更好。
搜索前先思考。
HyDE 代表 Hypothetical Document Embeddings。
它解决了一个常见的检索问题:
用户的查询可能太短或太模糊,无法产生一个强嵌入。
"message queue retries"
这个查询只包含几个词。
更好的搜索信号可以是由 LLM 生成的一个假设性答案。
不是嵌入原始问题:
User Question
↓
Embedding
↓
Vector Search
HyDE 引入了一个中间生成步骤:
User Question
↓
Generate Hypothetical Answer
↓
Embed Hypothetical Answer
↓
Vector Search
↓
Retrieve Documents
↓
LLM
例如,用户问:
How does a message queue retry failed jobs?
LLM 可能生成一个假设性答案,例如:
A message processing system can retry a failed job when the worker encounters a temporary error. Retry policies commonly use a maximum attempt count and exponential backoff before moving permanently failed messages to a dead-letter queue.
假设性答案比原始问题包含更多有意义的领域术语。
系统对该假设性答案进行嵌入,并用这个嵌入来搜索知识库。
生成的答案可能包含:
更多领域特定的术语
更好的用户意图表达
可能在相关文档中出现的术语
这可以改善语义匹配。
HyDE 不是随机猜测
假设性答案不用作最终答案。
它主要是一种搜索表示。
真实答案仍然来自检索到的文档。
Question
↓
Hypothetical Answer
↓
Embedding
↓
Retrieve Real Documents
↓
Generate Final Answer
HyDE vs Query Expansion
Query expansion 通常创建多个替代查询。
HyDE 生成一个假设性文档或答案,并嵌入该表示。
Query Expansion
→ Multiple queries
HyDE
→ One hypothetical answer
→ One embedding
适用于:
医学文档搜索
企业知识库
模糊或复杂的查询
HyDE 将一个弱问题转化为更强的搜索信号。
为什么要每次都搜索?先让模型决定。
传统 RAG 通常为每个查询都检索文档。
但并非每个问题都需要外部检索。
What is the square root of 144?
从向量数据库检索文档是不必要的。
总是检索会导致:
额外的基础设施成本
不必要的向量数据库负载
Self-RAG 引入了一个决策步骤。
User Question
↓
Should I retrieve?
↓
┌────────┴────────┐
NO YES
↓ ↓
Answer Retrieve
Directly ↓
Generate
模型首先考虑:
我需要外部信息吗?
我已经知道答案了吗?
问题是领域特定的吗?
信息可能是最新的吗?
我需要私有或内部文档吗?
模型使用其内部知识来回答。
What is 15 × 8?
不需要检索。
系统检索相关文档。
What changed in our company's API documentation this week?
检索很有用,因为信息是最新的且是内部的。
Self-RAG 应该在什么时候检索?
典型情况包括:
领域特定知识
私有/内部文档
复杂的多跳问题
什么时候可以跳过检索?
典型情况包括:
模型有足够置信度的问题
减少不必要的检索
减少向量数据库负载
改善响应延迟
在真正需要时才使用检索
传统 RAG 每次都检索。Self-RAG 在行动前决定是否需要检索。
并非每个检索到的块都有用。CRAG 在信任之前检查质量。
检索器不是完美的。
可能存在误导性信息
信息不完整
如果 LLM 盲目信任这些块,它可能产生一个自信但错误的答案。
CRAG 引入了一个质量控制步骤。
User Question
↓
Retrieve Documents
↓
Evaluate Retrieved Documents
↓
┌───────┴───────┐
GOOD BAD
↓ ↓
Use Docs Correct Retrieval
↓
Refine / Re-query
↓
Retrieve Again
↓
Final Context
↓
LLM
检索到的文档在被信任之前会进行评估。
评估器检查什么?
可能的标准包括:
文档是否实际支持问题
如果文档足够好,可以传递给 LLM。
如果它们很差,系统可以尝试纠正行动。
从另一个来源检索
假设用户问:
Which database is a good choice for high-volume event analytics?
检索器返回:
1. Introduction to relational databases
2. Key-value cache configuration
3. Columnar database architecture for analytics
4. Basic SQL CRUD operations
评估器可以判断出,只有部分文档直接回答了该问题。
系统随后可以移除弱相关结果,并在必要时执行额外检索。
检索
↓
检查
↓
纠正
↓
回答
减少自信的错误答案
提升检索质量
减少幻觉
处理复杂查询
过滤噪声检索结果
提升最终上下文质量
CRAG 尤其适用于:
复杂多跳问题
低质量检索系统
领域特定技术搜索
企业知识系统
检索 → 回答
检索 → 评估 → 纠正 → 回答
根本区别在于 CRAG 不会盲目信任检索器。
CRAG 在允许模型依赖检索结果之前,会先验证所获取的上下文。
将各项技术组合使用
这些技术不必独立使用。
生产级 RAG 系统可以组合使用其中多项。
用户查询
│
▼
Self-RAG 决策
/ \
NO YES
│ │
▼ ▼
直接回答 多查询检索
│
▼
混合搜索
向量 + BM25
│
▼
检索
│
▼
重排序
│
▼
CRAG 评估
/ \
GOOD BAD
│ │
│ 重新查询/纠正
│ │
└───────┬─────────┘
▼
父文档检索
│
▼
上下文压缩
│
▼
LLM
│
▼
最终回答
并非每个应用都需要所有组件。
正确的架构取决于:
准确性要求
信息变更频率
一个实用的心智模型
每种技术解决的是不同的失败模式。
一个强大的生产级 RAG 流水线
一个实用的系统可以从相对简单的架构起步:
文档
↓
结构感知分块
↓
嵌入 + 关键词索引
↓
混合搜索
↓
重排序
↓
父上下文
↓
上下文压缩
↓
LLM
然后仅在测量显示需要的地方才添加更先进的技术。
Self-RAG
可以减少不必要的检索。
多查询检索
可以提升困难问题的召回率。
HyDE
可以帮助处理模糊查询。
CRAG
可以添加验证和纠正循环。
构建 RAG 系统时最大的错误,是把检索当作一个单一操作:
查询 → 向量数据库 → LLM
现实世界的检索更像是一系列决策组成的流水线:
我应该检索吗?
↓
我应该搜索什么?
↓
我应该在哪里搜索?
↓
我应该如何分割文档?
↓
哪些结果实际上相关?
↓
哪些结果应该排在最前面?
↓
我应该提供多少上下文?
↓
检索到的上下文可信吗?
↓
LLM 能基于这个上下文回答吗?
最终答案的质量,往往在 LLM 生成第一个 token 之前就已经被决定了。
更好的检索 → 更好的上下文 → 更好的答案。
目标不是构建最复杂的 RAG 流水线。
目标是构建最简单的检索架构,使其能够可靠地为你的工作负载提供正确的上下文。