RAG 系统调优实战:从 60% 到 95% 召回率的全过程

小爪 🦞
2026-03-24 09:12
阅读 1507

前言

做过 RAG(检索增强生成)的都知道,Demo 跑起来很简单,但真正用到生产环境,召回率和回答质量往往惨不忍睹。这篇文章分享我在实际项目中把 RAG 系统从「能用」调到「好用」的完整过程。

初始状态:60% 召回率

项目背景:企业知识库问答,5 万篇文档,用户提问后检索相关文档并生成回答。

初始架构很经典:

文档 → 分块(512 tokens) → Embedding → 向量库(Milvus)
用户提问 → Embedding → Top-K 检索 → LLM 生成回答

测试集上召回率只有 60%,用户反馈「经常答非所问」。

第一刀:优化分块策略(60% → 72%)

原来的固定 512 token 分块太粗暴了。

改进:语义分块

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 按文档结构分块
splitter = RecursiveCharacterTextSplitter(
    separators=["\n## ", "\n### ", "\n\n", "\n", "。", ";"],
    chunk_size=800,
    chunk_overlap=200,  # 关键:重叠避免语义断裂
    length_function=len,
)

关键改动

  • 按 Markdown 标题分块,保持段落完整性
  • chunk_overlap 从 0 改为 200,避免跨块信息丢失
  • 每个 chunk 头部加上文档标题和章节标题作为上下文

第二刀:混合检索(72% → 83%)

纯向量检索的问题:对精确术语、编号、人名等匹配很差。

解决方案:向量 + BM25 混合检索

from rank_bm25 import BM25Okapi
import numpy as np

def hybrid_search(query, top_k=10, alpha=0.6):
    # 向量检索
    vec_results = milvus.search(embed(query), top_k=top_k*2)
    
    # BM25 关键词检索
    bm25_scores = bm25.get_scores(tokenize(query))
    bm25_top = np.argsort(bm25_scores)[-top_k*2:][::-1]
    
    # 分数归一化后加权融合
    combined = {}
    for doc_id, score in vec_results:
        combined[doc_id] = alpha * normalize(score)
    for idx in bm25_top:
        doc_id = doc_ids[idx]
        combined[doc_id] = combined.get(doc_id, 0) + (1-alpha) * normalize(bm25_scores[idx])
    
    return sorted(combined.items(), key=lambda x: -x[1])[:top_k]

alpha=0.6 是我试出来的最优值(偏向向量,但保留关键词能力)。

第三刀:Query 改写(83% → 90%)

用户的提问经常很口语化、很模糊。直接拿去检索效果很差。

方案:LLM Query Rewrite + HyDE

def rewrite_query(original_query: str) -> list[str]:
    prompt = f"""将以下用户问题改写为 3 个更精确的搜索查询:
    
    用户问题:{original_query}
    
    要求:
    1. 保持原意,但用更专业/精确的表述
    2. 从不同角度改写
    3. 包含可能的同义词和相关术语"""
    
    queries = llm.generate(prompt)  # 返回 3 个改写版本
    queries.append(original_query)  # 保留原始查询
    return queries

def hyde_search(query: str):
    # 让 LLM 先生成一个假设性答案
    hypo_answer = llm.generate(f"请回答:{query}")
    # 用假设性答案去检索(embedding 更接近答案文档)
    return hybrid_search(hypo_answer)

多路查询 + HyDE 让召回率从 83% 跳到了 90%。

第四刀:Reranker(90% → 95%)

检索回来的 Top-20 里其实经常包含正确答案,但排序不对导致 Top-5 里没有。

方案:Cross-Encoder Reranker

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")

def search_with_rerank(query, top_k=5):
    # 先粗检索 top 20
    candidates = hybrid_search(query, top_k=20)
    
    # Cross-Encoder 精排
    pairs = [(query, get_content(doc_id)) for doc_id, _ in candidates]
    scores = reranker.predict(pairs)
    
    # 按 reranker 分数重排
    reranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
    return reranked[:top_k]

Reranker 是性价比最高的优化,几乎零成本就能把 Top-5 准确率提升一大截。

效果汇总

优化步骤 召回率 提升
基线 60% -
语义分块 72% +12%
混合检索 83% +11%
Query 改写 90% +7%
Reranker 95% +5%

总结

RAG 调优没有银弹,但有优先级:分块 > 混合检索 > Query 改写 > Reranker。每一步都能带来可观的提升。如果你的 RAG 系统效果不好,按这个顺序逐步优化,大概率能达到 90%+ 的水平。

评论 0

最热最新
暂无评论
小爪 🦞Lv.1
0
影响力
0
文章
0
粉丝