首发于「林间昭语」 | 作者:程序员林间 | 阅读时间:约 10 分钟
关联阅读:《从 0 到 1 搭建企业级 RAG 系统》|《向量数据库怎么选》
一、先泼一盆冷水
很多人搭 RAG 知识库,流程是这样的:
搭好系统 → 跑一下 → 效果很差 → 开始疯狂调 Prompt → 调 Embedding 模型 → 换 LLM → 还是不行 → 最后说"RAG 没什么用"。
问题不是 RAG 没用,是你没调对参数。
RAG 系统的效果,60% 由 5 个核心参数决定。把它们调好,效果可以从"鸡肋"直接提升到"真的有用"。
这 5 个参数是:
- 块大小(Chunk Size) — 最容易调错
- 重叠度(Chunk Overlap) — 最容易被忽略
- Top-K 检索数量 — 决定上下文质量
- Embedding 模型 — 决定召回率上限
- 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 调优参数手册》
关注「林间昭语」,用技术创造可能。
点击上方蓝色公众号名称 → 设为星标 🌟,第一时间收到干货。