B04「RAG 效果调优」这 5 个参数决定了你的知识库好不好用

0 阅读9分钟

首发于「林间昭语」 | 作者:程序员林间 | 阅读时间:约 10 分钟

关联阅读:《从 0 到 1 搭建企业级 RAG 系统》|《向量数据库怎么选》


一、先泼一盆冷水

很多人搭 RAG 知识库,流程是这样的:

搭好系统 → 跑一下 → 效果很差 → 开始疯狂调 Prompt → 调 Embedding 模型 → 换 LLM → 还是不行 → 最后说"RAG 没什么用"。

问题不是 RAG 没用,是你没调对参数。

RAG 系统的效果,60% 由 5 个核心参数决定。把它们调好,效果可以从"鸡肋"直接提升到"真的有用"。

这 5 个参数是:

  1. 块大小(Chunk Size) — 最容易调错
  2. 重叠度(Chunk Overlap) — 最容易被忽略
  3. Top-K 检索数量 — 决定上下文质量
  4. Embedding 模型 — 决定召回率上限
  5. LLM Temperature — 决定回答的稳定性和准确性

二、调参前的准备工作

2.1 先建测试集,再调参

这是最重要的原则,90% 的人都跳过了这一步。

测试集 = 20-50 个真实问答对,必须来自真实用户问题(不是你自己编的)。


# test_set.py

test_queries = [

    {

        "query": "劳动合同最长期限是多久?",

        "expected_topics": ["劳动合同", "期限", "法律规定"]

    },

    {

        "query": "试用期没签合同违法吗?",

        "expected_topics": ["试用期", "合同", "违法"]

    },

    {

        "query": "加班费怎么计算?",

        "expected_topics": ["加班费", "计算", "法律规定"]

    },

    # ... 共 20-50 条

]

为什么这一步不能省? 因为没有测试集,你调参就是"盲调"——每次改参数,你只能凭感觉判断"好像好一点了"。有了测试集,你才能准确判断每次改动是好是坏。

2.2 建立评估指标

| 指标 | 计算方式 | 目标值 |

|------|----------|--------|

| 召回率(Recall) | Top-K 结果中有相关文档的比例 | > 90% |

| 精确率(Precision) | 检索结果中真正相关的比例 | > 85% |

| MRR(平均倒数排名) | 相关文档的平均排名倒数 | > 0.8 |

| 生成质量(人工) | 回答是否有幻觉、是否引用正确 | 90%+ 无幻觉 |


三、五个核心参数逐个拆解

3.1 块大小(Chunk Size)— 最容易调错

原理:块太大,语义混乱,LLM 容易忽略关键信息;块太小,上下文不连贯,AI 看不懂关联。

常见错误:用 1024 或 2048 的超长块,觉得"信息越多越好"。

实测数据对比(同一法律知识库,10个真实查询):

| 块大小 | 平均召回率 | 典型问题 |

|--------|-----------|----------|

| 128 字 | 71% | 块太小,上下文断裂严重 |

| 256 字 | 91% ⭐ | 最佳区间 |

| 512 字 | 87% | 开始有噪音 |

| 1024 字 | 78% | 语义混乱,准确率下降 |

| 2048 字 | 65% | 大多数答案不精准 |

结论:中文 RAG,256-300 字是经验最优值。


# 推荐参数

chunk_size = 256    # 中文字符数

chunk_overlap = 64  # 重叠字数

  

# 代码实现

splitter = RecursiveCharacterTextSplitter(

    chunk_size=256,

    chunk_overlap=64,

    separators=["\n\n", "\n", "。", ","]

)

什么时候调整块大小?

  • 文档结构清晰(手册、报告)→ 可以用 512
  • 文档语义密集(法律条文、合同)→ 用 256
  • 文档内容复杂混合 → 256 是保险起点

3.2 重叠度(Chunk Overlap)— 最容易被忽略

原理:相邻块之间有重叠,可以防止块边界切断语义,让检索更连贯。

实测对比(块大小固定 256 字):

| 重叠字数 | 召回率 | 说明 |

|---------|--------|------|

| 0(无重叠) | 82% | 块边界容易切断语义 |

| 32 字 | 88% | 有改善 |

| 64 字 | 91% ⭐ | 最佳性价比 |

| 128 字 | 90% | 改善有限,反而增加重复 |

| 200 字 | 88% | 重叠过多,检索结果重复 |

推荐:重叠设为块大小的 20-25% (约 1/4 到 1/5)。

进阶技巧:语义重叠

传统重叠是固定字符数,效果有限。进阶做法是按语义分块,让重叠发生在有意义的段落边界。


# 进阶:基于语义的重叠

from langchain.text_splitter import SemanticChunker

from langchain_openai.embeddings import OpenAIEmbeddings

  

# 按语义断裂点分割,比固定重叠更精准

semantic_splitter = SemanticChunker(

    embeddings=embedding,

    breakpoint_threshold_amount=0.95  # 相似度阈值

)

chunks = semantic_splitter.create_documents(texts)

3.3 Top-K 检索数量 — 决定上下文质量

原理:K 值决定给 LLM 多少上下文。太多,噪音干扰;太少,相关内容漏掉。

常见错误:K=10 或 K=20,觉得"多给 LLM 一些信息总没错"。

实测对比(块大小 256,向量检索):

| Top-K | 精确率 | 幻觉率 | 回答质量(人工评分) |

|-------|--------|--------|-------------------|

| K=1 | 94% | 3% | 65分(上下文太少,答不全)|

| K=3 | 91% ⭐ | 5% | 88分(最佳平衡) |

| K=5 | 85% | 11% | 82分(开始有噪音)|

| K=10 | 72% | 22% | 71分(上下文太多,LLM 迷失)|

推荐:K=3 是大多数场景的最优值。

进阶:动态 K

不同问题的复杂度不同,可以用动态策略:


def smart_retrieval(query, k_base=3):

    # 简单事实型问题 → 少给上下文

    if any(kw in query for kw in ["多久", "多少", "哪个"]):

        k = 2

    # 复杂分析型问题 → 多给上下文

    elif any(kw in query for kw in ["分析", "对比", "原因"]):

        k = 5

    else:

        k = k_base

    return vectorstore.similarity_search(query, k=k)

3.4 Embedding 模型 — 决定召回率上限

这是最重要的组件,一旦选错,算法调破天花板也上不去。

中文场景选型结论

| 模型 | 中文召回率 | 速度 | 部署 | 推荐度 |

|------|-----------|------|------|--------|

| BAAI/bge-large-zh | ⭐⭐⭐⭐⭐ | 中等 | 本地/云 | 首选 |

| BAAI/bge-base-zh | ⭐⭐⭐⭐ | 快 | 本地/云 | 备选 |

| shibing624/text2vec-base-chinese | ⭐⭐⭐ | 快 | 本地 | 老项目迁移 |

| text-embedding-3-large(OpenAI)| ⭐⭐ | 慢 | 云 | 国内不推荐 |

测试召回率的方法


from langchain_community.embeddings import HuggingFaceBgeEmbeddings

  

def evaluate_embedding_model(model_name, test_queries):

    embedding = HuggingFaceBgeEmbeddings(model_name=model_name)

    recalls = []

  

    for q in test_queries:

        query_vec = embedding.embed_query(q["query"])

        results = vectorstore.similarity_search_by_vector(query_vec, k=3)

  

        # 检查 Top-3 是否命中相关文档

        relevant = any(

            q["expected_topics"][0] in r.page_content

            for r in results

        )

        recalls.append(1 if relevant else 0)

  

    return sum(recalls) / len(recalls)

  

# 对比两个模型

model_a_recall = evaluate_embedding_model("BAAI/bge-large-zh", test_queries)

model_b_recall = evaluate_embedding_model("moka-ai/m3e-base", test_queries)

  

print(f"BGE-large 召回率: {model_a_recall:.1%}")

print(f"M3E 召回率: {model_b_recall:.1%}")

我的实战结论

  • BGE-large-zh 在几乎所有中文场景下都是首选
  • M3E 在某些特定领域(金融、医疗)可能略优,需要实际测试
  • Embedding 模型一旦确定,轻易不要换——换模型要重建整个向量库

3.5 LLM Temperature — 决定回答稳定性

原理:Temperature 控制 LLM 输出的"创造性"。值越高,答案越多样化但越容易随机;值越低,答案越稳定但可能过于死板。

实测对比(同一检索结果,不同 Temperature):

| Temperature | 幻觉率 | 回答多样性 | 适用场景 |

|-------------|--------|-----------|----------|

| 0.0(确定) | 2% | 几乎相同 | 知识库问答首选 |

| 0.3 | 8% | 略有变化 | 通用对话 |

| 0.7 | 18% | 多样 | 创意写作 |

| 1.0+ | 30%+ | 随机 | 不适合 RAG |

推荐:知识库问答用 temperature = 0.0 ~ 0.1


from langchain_openai import ChatOpenAI

  

llm = ChatOpenAI(

    model="deepseek-chat",

    temperature=0.1,  # 知识库场景必须低

    max_tokens=500,

)

更重要的 Prompt 技巧


system_prompt = """你是一个企业知识库助手。

【严格规则】

1. 只基于提供的参考资料回答,不要编造任何信息

2. 如果参考资料中没有相关信息,明确回复"我无法从现有资料中找到答案"

3. 回答必须包含参考来源(文档名或章节)

4. 回答用「」标注关键法律条款名称

  

【参考资料】

{context}

  

【用户问题】

{question}

  

【回答】"""

四、效果调优的完整流程

按这个顺序走,每一步都用测试集验证:


Step 1:建测试集(20-50 条真实问答)← 不能跳过

     ↓

Step 2:选 Embedding 模型(固定不动)

     ↓

Step 3:调块大小 + 重叠(用测试集测召回率)

     ↓

Step 4:调 Top-K(综合精确率和召回率)

     ↓

Step 5:设置 LLM Temperature(固定 0.1)

     ↓

Step 6:加 Prompt 约束(减少幻觉)

     ↓

Step 7:加混合检索(BM25 + 向量,召回率再+10%)

     ↓

Step 8:上线后监控,持续优化

五、进阶调优技巧

5.1 混合检索:召回率再提升 15%

单一向量检索有局限,配合关键词检索效果更好。


from langchain_community.retrievers import EnsembleRetriever

  

# 向量检索

vector_retriever = vectorstore.as_retriever(

    search_kwargs={"k": 3}

)

  

# 关键词检索(BM25)

bm25_retriever = BM25Retriever.from_documents(chunks)

  

# 混合:权重各 50%

ensemble = EnsembleRetriever(

    retrievers=[vector_retriever, bm25_retriever],

    weights=[0.5, 0.5]

)

  

results = ensemble.get_relevant_documents("加班费怎么计算?")

实战效果:混合检索比纯向量检索,召回率提升 12-18%

5.2 Re-rank:对 Top-K 结果二次排序

用更精准的模型对初筛结果重新排序。


from cohere import Client as CohereClient

  

cohere_client = CohereClient(api_key="your-key")

  

def rerank(query, documents, top_n=3):

    results = cohere_client.rerank(

        query=query,

        documents=[d.page_content for d in documents],

        top_n=top_n,

        model="rerank-multilingual-v2.0"

    )

    return [documents[r.index] for r in results.results]

  

# 使用:向量检索 20 条 → Re-rank → 取 Top-3

initial_results = vectorstore.similarity_search(query, k=20)

final_results = rerank(query, initial_results, top_n=3)

六、踩坑清单

| 问题 | 原因 | 解决方案 |

|------|------|----------|

| 调了很久没效果 | 没有测试集,不知道改完是好是坏 | 先建测试集再调参 |

| 块太小,答不全 | 上下文断裂 | 增大块 + 重叠 |

| 块太大,答不准 | 语义混乱,LLM 注意力分散 | 缩小块到 256 |

| K=5 比 K=3 效果差 | 上下文太多,噪音干扰 | 减少 K 值 |

| 答案总是幻觉 | Temperature 太高 | 降到 0.1 |

| Embedding 召回率低 | 模型没选对 | 换成 BGE-large-zh |

| 上线后效果慢慢变差 | 知识库没更新 | 定期重建向量索引 |


总结

五个核心参数,调优优先级

| 优先级 | 参数 | 目标值 | 影响力 |

|--------|------|--------|--------|

| ⭐⭐⭐ | Embedding 模型 | BGE-large-zh | 决定召回上限 |

| ⭐⭐⭐ | 块大小 | 256 字 | 影响最大 |

| ⭐⭐ | Top-K | K=3 | 影响精确率 |

| ⭐⭐ | 重叠度 | 块大小 25% | 减少语义断裂 |

| ⭐ | Temperature | 0.1 | 减少幻觉 |

记住调参顺序:测试集 → Embedding → 块大小 → K 值 → Temperature → Prompt


下一步

RAG 调优看起来参数很多,但核心就是这 5 个。把这 5 个调好,大多数知识库项目就能从"不好用"变成"真的有用"。

调优完了,下一步就是交付。下一篇《上线!把知识库接入企业微信/钉钉》会讲怎么把知识库真正用起来。

如果你现在正在搭一个知识库,效果还不理想,欢迎扫码聊一聊。

我可以帮你诊断现有系统,给出具体的调优建议。

备注"调优",送你一份《RAG 调优参数手册》,包含本文所有参数的具体数值参考 👇


📌 关联阅读

关注「林间昭语」公众号,回复以下关键词领取资料:

  • 回复"知识库" → 领取《RAG 交付自检清单》
  • 回复"分割" → 领取《常见文档分割策略对比表》
  • 回复"选型" → 领取《向量数据库选型评估表》
  • 回复"调优" → 领取《RAG 调优参数手册》

关注「林间昭语」,用技术创造可能。

点击上方蓝色公众号名称 → 设为星标 🌟,第一时间收到干货。