
📰 科技要闻
• 8月1日起国行苹果AI功能完成备案,多方消息称DeepSeek已开始筹备IPO,几家大厂共享单车同步上调起步价。
• 自动驾驶重卡公司斯年智驾完成3亿元C轮融资,2026年上半年该赛道公开融资已达5起,累计超70亿元人民币。
• iPadOS 27首个公开测试版放出,多任务处理与窗口管理相关的新特性成为讨论焦点。
上周我们知识库项目的检索命中率从Neo4j图检索接入后,产品经理在会上问了我一句话,把我问懵了:
"你说效果变好了,能拿出数据吗?不是'我感觉',是具体好了多少。"
说实话,我当时就纳闷了——过去九篇文章我们一路加了LangGraph、Milvus、Chunking策略、Embedding选型、混合检索、Graph RAG,每次改动我都是靠"跑几个case,人肉看一眼答案"来判断好坏。说白了就是拿着"自我感觉良好"当体检报告在用。这套评估方式在demo阶段够用,但一旦要跟老板汇报ROI、跟团队立KPI,光凭感觉就顶不住了——你得有一份真正的体检报告,不是自我感觉。这一篇,就是补上这个致命的短板。
一、没有评估的RAG,就是在开盲盒
我们踩过的坑,本质上都是同一个问题:改了A模块,B模块悄悄变差了,但没人知道。举几个真实场景:
换了Reranker模型之后,检索Top-3的准确率确实涨了8个点,可我们两周后才发现,答案的"忠实度"反而掉了——模型开始更爱脑补,因为召回的片段更精炼、更容易被大模型"过度信任"进而自由发挥。
这种此消彼长的现象,靠肉眼抽查十个case是完全看不出来的。你需要的是一套可量化、可回归、可对比的评估体系——每次改动跑一遍,出一份分数报告,涨了跌了一目了然。这就是本篇要建立的东西。
RAG评估的四个核心维度
业界(也是我们内部实践下来)比较认同的评估拆解方式,是把"答案好不好"拆成四个相对独立的维度——有点像餐厅评分拆成口味、卫生、服务、环境四项,总分再高也不代表哪一项都行,得拆开看才知道问题在哪:
维度 | 回答的问题 | 典型故障 |
|---|---|---|
忠实度 Faithfulness | 答案有没有编?是不是完全基于召回内容 | 幻觉,答案里出现召回片段没提到的细节 |
相关性 Relevancy | 答案是不是真的在回应用户的问题 | 答非所问,或者绕了一圈才切入正题 |
完整性 Completeness | 该覆盖的知识点是否都覆盖了 | 召回片段本身就漏了关键信息,答案自然残缺 |
简洁性 Conciseness | 有没有说废话、绕圈子 | 大段复述召回内容,用户要的一句话答案变成小作文 |
这四个维度里,前两个(忠实度、相关性)几乎是"生死线"——不达标就是不能上线的问题;后两个(完整性、简洁性)更多是"体验分",决定用户会不会觉得你的产品好用。我们内部把前两个设为P0阈值,跌破就自动拦截发布。
二、用Ragas把评估落到代码里
Ragas(Retrieval Augmented Generation Assessment)是目前RAG评估里最成熟的开源框架,核心思路是用另一个LLM当裁判,去判断答案是否忠实、是否相关。听起来有点"用魔法打败魔法",但只要裁判模型选得够强、Prompt设计得够严谨,这套方法的一致性是可以接受的。
先装包,跑一个最小示例:
# pip install ragas datasets
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
from datasets import Dataset# 每一条数据都是一次真实的RAG调用记录
data = {
"question": [
"Milvus索引类型该怎么选?"
],
"answer": [
"百万级向量推荐用IVF_"
"FLAT,十亿级考虑HNSW"
],
"contexts": [[
"IVF_FLAT适合中等规模,"
"HNSW适合超大规模且要"
"求低延迟..."
]],
"ground_truth": [
"中等规模选IVF_FLAT,"
"超大规模选HNSW"
],
}dataset = Dataset.from_dict(data)
result = evaluate(
dataset,
metrics=[
faithfulness,
answer_relevancy,
context_precision,
context_recall,
],
)
print(result)
这四个指标各自在回答什么问题,我把它翻译成人话:
Faithfulness → 答案里每句话,能不能在召回内容中找到依据?分越低,幻觉越多
Answer Relevancy → 反过来问:从答案能不能猜出原始问题?猜不出说明答偏了
Context Precision → 召回的片段里,有多少是真正被用上的?分低说明召回太"水"
Context Recall → 该召回的知识点,召回了多少?分低说明检索环节漏了东西
注意 Context Precision/Recall 依赖 ground_truth,也就是"标准答案"。这意味着你必须先有一份靠谱的评测数据集——这是下一节要解决的问题,也是大多数团队卡住的地方。
说个我的真实判断:Ragas这套"用LLM当裁判"的方案,我不认为它是银弹。裁判模型自己也会有偏好——我们实测发现它对"说得漂亮但略有偏离"的答案打分经常比人看还高,对简洁但直接的答案反而打分偏低。我们的应对方案是:裁判模型必须比被评模型强一个档位,否则宁可不评,也不要相信一个同档模型互评的结果——这一条是经验之谈,比任何文档里写的都管用。
三、评测数据集:人工标注 vs LLM自动生成
Ragas这套指标再漂亮,没有靠谱的评测集,一切都是空谈。这里我们踩过一个坑:最开始想图省事,全部用LLM自动生成Q&A对,结果生成出来的问题特别"标准"、特别"客气",跟真实用户在客服场景里输入的口语化、带错别字、跳着问的问题完全不是一回事。跑出来的分数很高,上线后用户投诉却没见少。
后来我们改成了混合方案,效果好了很多:
1. LLM自动生成打底(覆盖广度)
from ragas.testset.generator import (
TestsetGenerator,
)
from ragas.testset.evolutions import (
simple, reasoning, multi_context,
)generator = TestsetGenerator.from_langchain(
generator_llm, critic_llm, embeddings,
)# 按比例混合三类问题难度
testset = generator.generate_with_langchain_docs(
documents,
test_size=200,
distributions={
simple: 0.4,
reasoning: 0.4,
multi_context: 0.2,
},
)
这里的关键是 reasoning 和 multi_context 这两类进化策略——前者生成需要推理才能回答的问题,后者生成需要跨多个片段才能拼出答案的问题。这两类才是真正考验你RAG系统的地方,简单问题谁都能答对。
2. 人工标注补真实场景(覆盖深度)
我们从生产环境的真实用户问题里,每周抽样30条,人工标出"标准答案该长什么样",攒了三个月,攒出了一份300条的"黄金测试集"。这份数据集不用于日常CI跑分(跑一次成本较高),但每次大版本迭代前,必须过一遍,这是我们的发布门禁。
血泪教训:人工标注一定要标"多个可接受答案"而不是"唯一标准答案"。RAG的答案表达方式本身就有多样性,用一个死板的ground_truth去比对,会把很多"答得对但说法不一样"的case误判成失败。
四、A/B测试框架:让每次改动有据可依
有了评测集和评分指标,下一步是把它们串成一套可以对比的实验框架,有点像临床试验的对照组——不能只改一个参数就说"感觉变好了",得让对照组和实验组同时跑在同一套评测集上。我们的做法很朴素:把RAG Pipeline的每个环节参数化,跑一个"网格对比"脚本,自动把结果落表。
class RagExperiment:
def __init__(
self, name, chunk_size,
retriever, reranker=None,
):
self.name = name
self.chunk_size = chunk_size
self.retriever = retriever
self.reranker = rerankerdef run(self, golden_set):
results = []
for item in golden_set:
ctx = self.retriever.search(
item["question"]
)
if self.reranker:
ctx = self.reranker.rerank(
item["question"], ctx
)
answer = generate_answer(
item["question"], ctx
)
results.append({
"question": item["question"],
"answer": answer,
"contexts": ctx,
"ground_truth": item["answer"],
})
return ragas_score(results)# 三组对比:chunk大小 + 是否加rerank
experiments = [
RagExperiment(
"baseline_512", 512, retriever_a
),
RagExperiment(
"chunk_256", 256, retriever_a
),
RagExperiment(
"rerank_on", 512,
retriever_a, bge_reranker,
),
]for exp in experiments:
score = exp.run(golden_set)
log_to_dashboard(exp.name, score)
跑完之后你会得到类似这样的对比表——这是我们某次真实实验的简化版本:
方案 | 忠实度 | 相关性 | 召回率 |
|---|---|---|---|
baseline_512 | 0.81 | 0.85 | 0.72 |
chunk_256 | 0.79 | 0.88 | 0.81 |
rerank_on | 0.76 | 0.91 | 0.83 |
看到了吧——rerank_on方案的相关性和召回率都涨了,但忠实度反而是三者最低的。这正是文章开头我提到的那个坑:加rerank让召回更精炼,但也让模型更容易"过度自信"地脑补。如果没有这张表,我们绝对不会意识到这个代价。最终我们选择了chunk_256而不是rerank_on,因为忠实度是我们的P0红线。
五、线上评估:把用户反馈变成信号
离线评估解决"上线前放不放心",但真正的效果好坏,最终还得看线上。我们线上跑的是一套更轻量的"隐式信号"采集,而不是逼用户去点赞点踩(转化率低得可怜):
用户提问 → 得到回答
↓
用户下一步做了什么?
↓
✅ 结束对话/换新话题 → 隐式正信号,视为回答满足需求
❌ 换个说法重问同一问题 → 隐式负信号,回答没解决问题
❌ 转人工客服 → 强负信号,直接标记为badcase
所有的负信号case,会自动进入一个每日复盘队列,我们每天花20分钟人工过一遍,把真正有价值的case补进"黄金测试集"。这套闭环跑了两个月,黄金测试集从最初的80条自然膨胀到了300多条,而且全都来自真实故障,含金量比LLM生成的高得多。
六、评估驱动优化:分数下降之后怎么排查
建立评估体系的终极目的,不是拿一个分数去邀功,而是当分数下降时,能快速定位是Pipeline哪个环节出的问题。我们总结了一个简单的排查顺序:
Context Recall 低?
✅ 是 → 检索环节问题,排查Embedding模型/索引参数/Chunking策略
↓ 否,召回没问题
Faithfulness 低?
✅ 是 → 生成环节问题,排查Prompt约束/模型温度/是否引导模型"只用给定材料回答"
↓ 否
→ Answer Relevancy低但前两者正常,多半是问题理解出了偏差,回头看Query改写/意图识别逻辑
这个排查顺序帮我们把一次"线上答案质量下降"的定位时间,从过去平均一整天的人肉排查,压缩到了一小时以内——因为你不再需要靠猜,四个指标已经把问题范围圈出来了。
写到这里,这个系列已经走完了从LangChain入门到RAG评估的十篇,正好是整个知识库项目地基的一半。我想给一个可能不好听但我确信的判断:没有评估体系的团队,根本没资格谈"RAG优化"这个词——你只是在瞎改参数,然后把运气好的那几次包装成"优化成果"。回头看,评估体系其实应该更早搭——如果part 4接入Milvus的时候我们就有这套东西,后面几次调优的决策会更有底气,也能少走一些回头路。剩下的十篇,会往可观测性、多Agent协作和生产部署上走,这些环节没有量化指标兜底,风险只会更大。下一篇聊LangSmith的全链路观测,到时候见。