RAG系统怎么进行评测?
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
开篇引言
前面 18 篇,咱们一步步把 RAG 链路跑通了——从分块、向量化、检索策略到生成策略,从 Function Call、MCP 到会话记忆、Query 改写、意图识别,最后还讲了评估与优化的基本思路。到这里,一套 RAG 系统已经能跑起来、能回答用户问题了。
Ragent 的测评项目地址 ragenteval,放在了咱们星球内部的代码仓库,如果之前申请过牛券、oneThread 等项目,无需重复申请。如果还没有申请过,请参考 内部项目权限申请流程 进行申请,审核开放后即可查看。
但跑通只是起点。
你改了一行 system prompt,检索效果到底是变好还是变差?你换了 embedding 模型,答案质量有没有提升?你加了 rerank,有没有引入回归?这些问题,靠抽几条 case 看看效果是回答不了的。
评测系列就是来解决这个问题的。前 18 篇回答的是问答链路怎么跑通,接下来这 11 篇回答一个完全不同的问题——跑通之后,怎么证明它跑得好?
之前的并发项目,比如 12306、牛券这些,如何让面试官感觉是有亮点的?重点考量的是项目的并发、数据以及架构设计,对于现在的 AI 智能体项目,更多是测评、真实业务场景得出来的指标。
不评的代价:三个场景
在讲评测体系之前,先看三个场景,都是项目里经常踩的坑。
场景一:改 chunk size,抽查还行,上线就崩。 你把 chunk size 从 512 改到 1024,抽了 10 个常见问题跑了一下,检索结果看着还行,于是上线了。过了两天,客服反馈售后类问题全崩了——退货政策和退款流程原本在相邻段落,改大 chunk 之后被合进同一个 chunk,但保修条款那段被截断了。这类问题在你抽查的 10 个里恰好没有覆盖到。
场景二:换 embedding 模型,几条 case 验证就上线。 同事推荐了一个新的 embedding 模型,你换上去跑了几个典型问题,效果确实好——语义理解更准了。结果过了两周,产品经理说有用户投诉,一查发现包含英文型号名的问题(iPhone 16 Pro Max、AirPods Pro 2)检索效果全面退化。新模型中文语义能力强,但对中英混合场景的处理不如旧模型。
场景三:改 prompt 减幻觉,但兜底率飙了。 你在 system prompt 里加了一条“如果上下文中没有明确提到的信息,不要回答,请告知用户无法回答”。幻觉确实少了,但兜底率从 5% 飙到 30%——很多检索到了相关 chunk、本该正常回答的问题,模型也开始说“找不到相关信息”了。约束太 强,误杀了一大片正常场景。
三个场景,同一个问题:没有评测体系,每次改动都是在赌没问题。你不知道改好了什么,也不知道改坏了什么。
评估与优化那篇讲过基本思路——分层评估、Hit Rate、MRR、LLM-as-Judge。但那篇是讲概念的,从这篇开始,咱们讲的是一个已经跑通的、完整的评测项目——有评估集、有 runner、有指标、有报告。不是规划将来打算怎么做,而是讲实际怎么做、为什么这么做、踩了哪些坑。
评测的全景图:从评估集到报告
1. 两个仓库各管什么
评测体系涉及两个独立的仓库:
| 仓库 | 角色 | 语言 | 关键产物 |
|---|---|---|---|
ragent | 被评系统 | Java 17 + Spring Boot | /rag/v3/chat(SSE 流式接口)、/rag/eval(JSON 评测旁路接口) |
ragenteval | 评测项目 | Python 3.11 | 评估集、runner、指标脚本、报告 |
为什么要分成两个仓库?三个理由:
- 评测逻辑不应该侵入生产代码。:评测需要的字段(标准答案、期望文档 ID、难度分级)跟生产接口完全无关,混在一起会让代码难以维护。
- Python 生态更适合做评测: RAGAS 是 Python 库,数据处理用 pandas 更方便,评测脚本不需要 Spring Boot 那套重框架。
- 两个仓库独立迭代互不影响:改了评估集不用重新部署被评系统,改了被评系统的检索逻辑也不用动评测代码。
2. 四段流程:init → run → score → report
整个评测从头到尾分四段,每段有明确的输入和产出。用一张图来看数据是怎么流的:

逐段拆开看:
init(初始化)——把被评系统搭到可评测的状态。在 ragent 里建 4 个知识库(商品库、使用手册库、政策库、FAQ 库),批量灌入 115 篇 Markdown 文档并触发分块,再构建一棵多叶子节点的意图树。跑一次就行,后面除非换数据否则不用重跑。
run(录制)——把 150 条评估样本逐条喂进 ragent,每条调两个接口:SSE 接口拿真实链路的 response 和首字耗时,JSON 旁路接口拿检索证据。结果聚合成一份 runs/v1_<ts>.jsonl,一行一条样本。
这里有一个核心设计:录制一次,评分 N 次。runner 跑一次成本最高——要真实调接口、等 LLM 生成,150 条串行跑下来要几十分钟。但录好之后,score 阶段基于本地文件反复跑,自建指标秒级出结果。改了评分逻辑、加了新指标,都不需要重新录制。
score(评分)——基于录制结果算指标。自建指标是纯集合运算,秒级出结果。RAGAS 指标要调 LLM 做 judge,慢但能评语义层面的东西。两套一起跑,互不依赖。
report(报告)——把指标结果整理成人能看的产物。给研发看的 markdown 和 CSV,给人工 review 的失败样例 JSONL。
3. 被评系统的两个接口
ragent 为评测暴露了两个接口,各有分工:
| 接口 | 方式 | 拿什么 | 为什么需要 |
|---|---|---|---|
/rag/v3/chat | SSE 流式 | response、thinking、TTFT、整流耗时 | 走真实生产链路,拿到用户实际会看到的答案和性能数据 |
/rag/eval | JSON 同步 | docIds、chunkIds、contexts、intentLeafIds | 纯检索旁路,跳过 LLM 生成,直接拿检索证据用于算命中率等指标 |
为什么不能只用一个接口?/rag/v3/chat 是真实生产链路,拿得到 response 和 TTFT,但 SSE 流里不含检索中间产物(召回了哪些文档、命中了哪些意图),这些数据算 Hit@K、Recall@K 必须有。/rag/eval 专门返回检索证据,但它只跑检索不跑 LLM,拿不到最终答案。两个接口配合,才能把答案 + 证据 + 性能数据一次性收齐。
评测旁路通过 app.eval.enabled=true 配置开关控制,生产环境关闭后 EvalController 不注册,零开销。
这个设计有一个已知的妥协:两次请求是两次独立检索,召回结果不保证完全一致(受随机性、缓存、改写抖动影响),但实践上足够接近,基本上不会出现问题。