RAG 知识点(二):进阶技术
1. 文档切片策略
定义
文档切片(Chunking)是将长文档切分为适合检索的小块的过程。切片质量直接决定 RAG 系统的效果------切太大检索时混入无关信息,切太小语义不完整。
切片方法对比
| 方法 | 难度 | 适用场景 | 推荐参数 | 语义完整度 |
|---|---|---|---|---|
| 固定字符切片 | 低 | 快速原型 | chunk=500, overlap=50 | 一般 |
| 滑动窗口切片 | 中 | 保持上下文 | window=500, step=250 | 良好 |
| AI 辅助切片 | 高 | 语义完整性 | 规则粗切+AI精切 | 优秀 |
| 摘要切片 | 高 | 长文档检索 | 摘要检索+原文返回 | 优秀 |
1.1 固定字符切片
python
def fixed_char_chunking(text, chunk_size=100, overlap=20):
"""按固定字符数切片,相邻切片保留重叠"""
chunks = []
step = chunk_size - overlap
for i in range(0, len(text), step):
chunk = text[i:i + chunk_size]
chunks.append(chunk)
return chunks
text = "人工智能是模拟人类智能的计算机科学领域..."
chunks = fixed_char_chunking(text, chunk_size=500, overlap=50)
print(f"切片数量:{len(chunks)}")
1.2 滑动窗口切片
python
def sliding_window_chunking(text, window_size=100, step_size=50):
"""固定窗口滑动,相邻切片有重叠,保持语义连续性"""
chunks = []
start = 0
while start < len(text):
chunks.append(text[start:start + window_size])
start += step_size
if start >= len(text):
break
return chunks
text = "人工智能是模拟人类智能的计算机科学领域..."
chunks = sliding_window_chunking(text, window_size=500, step_size=250)
print(f"切片数量:{len(chunks)}")
1.3 AI 辅助切片
python
def ai_chunking_with_llm(text, max_chunks=5, api_key=None):
"""使用 LLM 识别语义边界,按主题自然分段"""
from openai import OpenAI
client = OpenAI(api_key=api_key, base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
prompt = f"""请分析以下文本,找出{max_chunks}个语义边界。
文本:{text}
返回 JSON 格式:{{"split_points": [位置1, 位置2], "reasons": ["原因1", "原因2"]}}"""
completion = client.chat.completions.create(model="qwen-plus", messages=[
{"role": "user", "content": prompt}
])
# 解析返回的切分点,按点分割文本
return parse_and_split(text, completion.choices[0].message.content)
1.4 摘要切片
python
def summary_chunking(chunks, api_key=None):
"""为每个切片生成摘要,用摘要检索,原文回答"""
from openai import OpenAI
client = OpenAI(api_key=api_key, base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
summaries = []
for chunk in chunks:
resp = client.chat.completions.create(model="qwen-plus", messages=[
{"role": "user", "content": f"请用30字概括核心内容:{chunk}"}
])
summaries.append(resp.choices[0].message.content.strip())
return summaries
# 检索时匹配摘要,返回原文
# 短文档(<500字):直接用原文检索
# 长文档(>2000字):用摘要检索
切片参数推荐
| 文档类型 | chunk_size | overlap | 方法 |
|---|---|---|---|
| 短文本(<1000字) | 200 | 50 | 固定/滑动 |
| 中文本(1000-5000字) | 500 | 100 | 滑动窗口 |
| 长文本(>5000字) | 800 | 150 | 滑动窗口 |
| 法律/合同 | 300 | 100 | AI 切片 |
2. 检索方法
定义
检索方法决定了从向量数据库中找到"最相关"文档的策略。不同方法适用于不同场景。
2.1 标量查询(Scalar Query)
python
# 按条件筛选,类似 SQL WHERE
results = client.query(
collection_name="my_docs",
filter="category == 'AI' and views > 500",
output_fields=["title", "content"],
limit=10
)
# 适用:有明确过滤条件(按类别、日期等)
2.2 向量检索(Vector Search)
python
# 语义搜索,理解"意思"而非"字眼"
query_vector = embed("机器学习是什么")
results = client.search(
collection_name="my_docs",
data=[query_vector],
limit=5,
output_fields=["content", "category"]
)
# 适用:用户搜"机器学习是什么"(理解语义,无需精确匹配字眼)
2.3 BM25 关键字检索
python
# BM25 基于 TF-IDF 改进,精确匹配关键词
results = client.search(
collection_name="my_docs",
data=["Milvus 2.4 版本"], # 直接传文本
anns_field="sparse_vector", # 稀疏向量字段
limit=5,
search_params={"metric_type": "BM25"},
output_fields=["title", "text"]
)
# 适用:专有名词需要精确匹配(如产品型号、版本号)
2.4 混合检索(Hybrid Search)--- 生产环境首选
python
from pymilvus import AnnSearchRequest, Function, FunctionType
# 稠密向量检索请求(语义)
req_dense = AnnSearchRequest(
data=[query_vector], anns_field="dense_vector",
param={"nprobe": 10}, limit=5
)
# 稀疏向量检索请求(关键词)
req_sparse = AnnSearchRequest(
data=[query_text], anns_field="sparse_vector", # 传原始文本
param={"metric_type": "BM25"}, limit=5
)
# RRF 融合排序
ranker = Function(name="rrf", function_type=FunctionType.RERANK,
params={"reranker": "rrf", "k": 100}, input_field_names=[])
results = client.hybrid_search(
collection_name="my_docs",
reqs=[req_dense, req_sparse], ranker=ranker,
limit=5, output_fields=["title", "text"]
)
# 适用:语义 + 关键词互补,生产环境首选
检索方法选择指南
精确匹配(ID/类别/日期) → 标量查询
语义相似 → 向量检索
关键词匹配 → BM25 关键词检索
综合最优 → 混合检索(RRF 融合)
精益求精 → 混合检索 + Rerank
3. 结果融合策略
定义
混合检索需要将多路检索的结果合并为最终排名。两种主流融合策略:RRF(倒数排名融合)和加权排序。
3.1 RRF(Reciprocal Rank Fusion)
公式:score(d) = Σ 1/(k + rank_i(d))
python
# RRF 原理示例
vector_ranking = [0, 1, 2, 4, 3, 5] # 向量检索排名
keyword_ranking = [0, 2, 1, 5, 3, 4] # 关键词检索排名
k = 60
rrf_scores = {}
for doc_idx in range(6):
v_rank = vector_ranking.index(doc_idx) + 1
k_rank = keyword_ranking.index(doc_idx) + 1
rrf_scores[doc_idx] = 1/(k + v_rank) + 1/(k + k_rank)
# 按分数降序得到最终排名
final_ranking = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
Milvus 中使用 RRF:
python
ranker = Function(name="rrf", function_type=FunctionType.RERANK,
params={"reranker": "rrf", "k": 100}, input_field_names=[])
RRF 优势:不受原始分数影响,只看排名;不同检索方法的分数可能不可比,但排名可比;鲁棒性好。
3.2 加权排序(Weighted)
公式 :score(d) = w1score1(d) + w2score2(d)
python
# 加权融合示例
alpha = 0.6 # 向量权重 60%
for i in range(len(documents)):
fused = alpha * vector_scores[i] + (1 - alpha) * bm25_scores[i]
Milvus 中使用加权排序:
python
ranker = Function(name="weighted", function_type=FunctionType.RERANK,
params={"reranker": "weighted", "weights": [0.6, 0.4], "norm_score": True})
RRF vs 加权排序对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| RRF | 无需调权重、鲁棒性好 | 无法控制比例 | 通用场景(推荐) |
| 加权排序 | 可精确控制比例 | 需调参、需归一化 | 明确知道权重时 |
4. Rerank 重排序
定义
Rerank 是对检索结果进行精排序的二次处理。典型架构:检索(快,Top-50)→ Rerank(准,Top-5)。
核心原理
-
Bi-Encoder(检索阶段):问题和文档分别编码,计算向量相似度,速度快但精度有限
-
Cross-Encoder(Rerank 阶段):问题和文档拼接后一起编码,精度高但速度慢
向量检索(Bi-Encoder,快)→ Top-50 候选 → Rerank(CrossEncoder,准)→ Top-5 最终
代码示例(BGE-Reranker)
python
# 使用 BGE-Reranker 进行重排序
from FlagEmbedding import FlagReranker
reranker = FlagReranker('BAAI/bge-reranker-large', use_fp16=False)
query = "机器学习需要什么基础?"
candidates = [
"深度学习是机器学习的子集。",
"机器学习通过训练数据让计算机自动学习。",
"机器学习需要数学基础,包括线性代数和概率统计。",
]
scores = reranker.compute_score([[query, doc] for doc in candidates])
ranked = sorted(enumerate(scores), key=lambda x: x[1], reverse=True)
for rank, (idx, score) in enumerate(ranked, 1):
print(f"[{rank}] 分数:{score:.4f} | {candidates[idx]}")
Rerank 模型选择
| 场景 | 推荐模型 |
|---|---|
| 中文 | BAAI/bge-reranker-large |
| 英文 | cross-encoder/ms-marco-MiniLM-L-12-v2 |
| 多语言 | BAAI/bge-reranker-v2-m3 |
| API 方案 | Cohere Rerank API |
参数建议
- 召回数量:50-100 条(给 Rerank 足够选择)
- 返回数量:5-10 条(LLM 上下文限制)
- 分数阈值:过滤掉 <0.3 的低相关结果
5. BM25 Function(Milvus 2.4+)
定义
Milvus 2.4+ 支持在 Schema 中直接定义 BM25 Function,自动从文本字段生成稀疏向量,无需手动计算。
核心步骤
- 文本字段开启
enable_analyzer=True(启用分词) - 文本字段开启
enable_match=True(启用 BM25 匹配) - 定义 BM25 Function:text → sparse_vector
- 为稀疏向量字段创建索引:SPARSE_INVERTED_INDEX + metric_type=BM25
代码示例
python
from pymilvus import MilvusClient, DataType, Function, FunctionType
client = MilvusClient(uri="http://localhost:19530")
# 1. 定义 Schema
schema = client.create_schema()
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True)
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000,
enable_analyzer=True, enable_match=True) # 开关1+2
schema.add_field(field_name="dense_vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
# 2. 定义 BM25 Function
bm25_fn = Function(name="text_bm25", input_field_names=["text"],
output_field_names=["sparse_vector"], function_type=FunctionType.BM25)
schema.add_function(bm25_fn)
# 3. 创建索引
idx = client.prepare_index_params()
idx.add_index(field_name="dense_vector", index_type="AUTOINDEX", metric_type="COSINE")
idx.add_index(field_name="sparse_vector", index_type="SPARSE_INVERTED_INDEX", metric_type="BM25")
# 4. 创建 Collection
client.create_collection(collection_name="hybrid_docs", schema=schema, index_params=idx)
# 5. 插入数据(sparse_vector 自动生成!)
client.insert("hybrid_docs", data=[
{"text": "人工智能是模拟人类智能的计算机科学", "dense_vector": [0.1]*1024}
])
# 注意:不需要手动提供 sparse_vector,BM25 Function 自动生成
6. 查询重写与扩展(Query Rewriting)
定义
用户提出的问题往往不是最适合检索的形式。查询重写通过 LLM 将模糊问题转化为更精确的检索语句,提高检索召回率。
常见策略
6.1 查询改写:将用户口语化问题转为正式检索语句
python
def rewrite_query(question, llm_client):
prompt = f"""将以下问题重写为3个不同角度的检索查询:
原问题:{question}
每行一个查询:"""
response = llm_client.chat.completions.create(model="qwen-plus", messages=[
{"role": "user", "content": prompt}
])
return response.choices[0].message.content.strip().split("\n")
queries = rewrite_query("AI怎么学", client)
# 输出示例:
# 1. 人工智能学习路径和方法
# 2. AI入门需要哪些基础知识
# 3. 如何系统学习人工智能技术
6.2 HyDE(假设文档嵌入):让 LLM 先生成假设答案,再用假设答案去检索
python
def hyde_search(question, llm_client, embed_fn, milvus_client):
# Step 1: 生成假设答案
response = llm_client.chat.completions.create(model="qwen-plus", messages=[
{"role": "user", "content": f"请回答:{question}"}
])
hypothetical_answer = response.choices[0].message.content
# Step 2: 用假设答案的向量去检索真实文档
query_vector = embed_fn(hypothetical_answer)
results = milvus_client.search("knowledge", data=[query_vector], limit=5)
return results
6.3 子问题分解:将复杂问题拆分为多个子问题分别检索
python
def decompose_query(question, llm_client):
prompt = f"""将以下复杂问题分解为2-3个简单的子问题:
问题:{question}
子问题(每行一个):"""
response = llm_client.chat.completions.create(model="qwen-plus", messages=[
{"role": "user", "content": prompt}
])
return response.choices[0].message.content.strip().split("\n")
# 示例:"RAG和微调有什么区别,各自适合什么场景?"
# → 子问题1: "RAG是什么,有什么优缺点?"
# → 子问题2: "微调是什么,有什么优缺点?"
# → 子问题3: "RAG和微调的选择标准是什么?"
7. Contextual Retrieval(上下文检索)
定义
Anthropic 在 2024 年提出的技术。在切片时,为每个 chunk 添加其所属文档的上下文摘要,减少因切片导致的语义丢失。实验表明可减少 67% 的检索失败率。
代码示例
python
def contextual_chunking(document, chunk_size=500, overlap=50):
"""为每个切片添加文档级上下文摘要"""
# 1. 先生成整个文档的摘要
doc_summary = llm_generate(f"用一句话概括:{document[:2000]}")
# 2. 切片
chunks = sliding_window_chunking(document, chunk_size, overlap)
# 3. 为每个切片添加上下文
contextual_chunks = []
for i, chunk in enumerate(chunks):
# 在 chunk 前添加:文档摘要 + 当前位置
context_prefix = f"[文档摘要:{doc_summary}]\n[第{i+1}/{len(chunks)}段]\n"
contextual_chunks.append(context_prefix + chunk)
return contextual_chunks
# 检索时,context_prefix 帮助向量模型理解 chunk 的上下文
# 效果:减少"Lost in the Middle"和语义切断问题