news 2026/9/5 10:51:53

RAG 评测集构建:合成打底、日志校准、人工收口——三层数据流水线落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RAG 评测集构建:合成打底、日志校准、人工收口——三层数据流水线落地

RAGAS、DeepEval 这些评测框架解决的只是"怎么算分",前提是你手里有一份像样的评测集。多数团队的现状是几十条手写 golden 样例打天下:测不出真实用户分布(头部高频问题霸榜,长尾一问一个不中),文档一更新答案就过期,指标掉了也分不清是模型退化还是测试数据失真。业界的落地组合不是某一条单一路线,而是三层流水线:LLM 合成打底解决冷启动,线上日志回流校准真实分布,人工抽检收口保证标注可信。合成集测的是"能力倾向"(模型理论上行不行),日志集测的是"真实分布"(用户实际怎么问),两层各有各的盲区,混着用之前先想清楚每层要回答什么问题。这篇给出三层各自的 schema 设计、可直接抄的采样/生成代码和一组踩坑记录。

先想清楚:评测集里到底该有什么标注

RAG 评测集和普通 QA 数据集最大的区别:它必须分两层标注,因为检索层和生成层要答的是两个不同的问题。

层级要回答的问题需要的标注典型错误
检索层该命中的 chunk 命中了没passage 级:问题 ↔ 相关 chunk id只标了答案,检索 miss 了都不知道
生成层答案对不对、忠不忠实参考答案 / 可判据要点答案对但引用了错误的 chunk(静默失败)

检索层标注可以全自动获得(合成器生成时就知道问题基于哪几个 chunk),生成层标注最贵、最容易污染。先定 schema,所有层共用一套:

@dataclass class EvalCase: case_id: str question: str answer: str | None # 参考答案;is_unanswerable=True 时为 None reference_chunk_ids: list[str] # 检索层标注:只靠这些 chunk 就能答出 keypoints: list[str] # 生成层标注:答案必须覆盖的要点(可判据) is_unanswerable: bool # 拒答类:知识库里根本没有答案 difficulty: str # easy / hard(多跳、长尾实体、强干扰) source: str # synthetic / log_replay / human doc_version: str # 基于哪个文档版本生成,过期检测用

reference_chunk_ids是整套 schema 里最值钱的字段:RAGAS 合成器输出里的reference_contexts就是这个。有了它,检索层回归(recall@k、MRR)和生成层回归(faithfulness)可以各自独立跑,指标掉了能直接定位到层。

第一层:LLM 合成打底(冷启动 100-500 条)

合成路线的主流实现是 RAGAS TestsetGenerator 和 DeepEval Synthesizer,两者思路一致:文档分块 → 提取实体/主题关系 → 按 query 类型分布生成问题 → 自动带出 reference chunks 和答案。

关键在query 分布要按你的真实场景调,别用默认值。RAGAS 默认分布是 50% 单跳具体 + 25% 多跳抽象 + 25% 多跳具体,这是为了论文里展示"全面",不是为你的生产环境调的。客服知识库的真实 query 90% 是单跳具体问题,默认分布会造出一堆用户根本不问的多跳抽象题,把测试集难度整体拉高,回归门禁被迫放宽,等于没测。

from ragas.testset import TestsetGenerator from ragas.llms import LangchainLLMWrapper from ragas.embeddings import LangchainEmbeddingsWrapper from langchain_openai import ChatOpenAI, OpenAIEmbeddings # 中文场景接 DeepSeek,成本能压到可忽略 generator_llm = LangchainLLMWrapper( ChatOpenAI(model="deepseek-chat", base_url="https://api.deepseek.com/v1") ) critic_llm = LangchainLLMWrapper( ChatOpenAI(model="deepseek-chat", base_url="https://api.deepseek.com/v1") ) embeddings = LangchainEmbeddingsWrapper(OpenAIEmbeddings(model="text-embedding-3-small")) generator = TestsetGenerator(llm=generator_llm, critic_llm=critic_llm, embeddings=embeddings) # 自定义 query 分布:贴近真实场景的"单跳为主、少量多跳 + 对抗" query_distribution = [ ("single_hop_specific", 0.65), # 用户怎么问就怎么生成,不做改写 ("multi_hop_specific", 0.20), # 跨文档整合(如"对比 A 和 B 的赔付规则") ("multi_hop_abstract", 0.10), # 需要推理归纳的问题 ("noise_robust", 0.05), # 问题里故意带干扰信息,测噪声敏感度 ] testset = generator.generate_with_langchain_docs( documents, testset_size=300, query_distribution=query_distribution )

DeepEval Synthesizer 的生成后演化(reasoning / multi-context / concretizing / constrained / comparative / hypothetical / in-breadth 七种)本质是同一件事:在基础 QA 对上做变换,把"能力倾向"测全。用哪个工具不重要,重要的是记住合成集的边界——它只能证明模型"有能力答对",证明不了"真实用户问的问题里你能答对多少"。合成器从你的文档里挑 chunk 生成问题,天然回避了用户问得最多但文档里没有的场景(这类 query 恰恰是拒答和幻觉的重灾区)。

第二层:线上日志回流校准分布(上线后持续做)

真实分布只有一条来源:生产日志。Langfuse、LangSmith 都支持把线上 trace 一键转成 dataset,但直接倒进去不能用——真实 query 的分布是极端幂律,头部 20 个问题能占掉 80% 的流量,不去重直接灌,测试集会被"查余额""改密码"这种简单问题淹没,指标被拉高到失真,长尾复杂问题永远测不到。回流要做三步:过滤、聚类、分层抽样。

import numpy as np from sklearn.cluster import KMeans def sample_replay_queries(queries, embeddings, n_target=150, top_k=12, tail_quota=0.4): # 1) 过滤:PII / 过短无意义 / 纯指令("继续""谢谢")/ 与知识库无关的寒暄 queries = [q for q in queries if is_valid_qa_query(q)] # 2) 聚类:query embedding + KMeans,簇代表"一类问法" X = np.array([embeddings.embed_query(q) for q in queries]) n_clusters = min(top_k, len(queries) // 5) labels = KMeans(n_clusters=n_clusters, n_init=10, random_state=42).fit_predict(X) # 3) 分层抽样:每个簇按容量比例抽,同时给长尾簇保底配额 # 头部簇("查余额"类)抽 1-2 条代表即可,长尾簇多抽,防止霸榜 picked, per_cluster = [], {} for q, c in zip(queries, labels): per_cluster.setdefault(c, []).append(q) n_tail = int(n_target * tail_quota) n_head = n_target - n_tail # 簇按大小排序,大簇(高频)只抽少量代表,小簇(长尾)占配额主体 clusters_sorted = sorted(per_cluster.values(), key=len, reverse=True) for cluster in clusters_sorted[:3]: # 头部 3 个高频簇 picked += list(np.random.choice(cluster, min(3, len(cluster)), replace=False)) tail_candidates = [q for c in clusters_sorted[3:] for q in c] picked += list(np.random.choice(tail_candidates, min(n_tail, len(tail_candidates)), replace=False)) return picked

抽样出来的 query 需要人工标参考答案和 reference chunks(这一步省不了,真实 query 没有现成答案)。人力不够就分优先级:头部簇全标、长尾簇按季度轮换抽标。日志回流的产出不是"一次性测试集",是持续校准——每个迭代周期回流一次,测试集的分布才会跟着真实用户一起漂移,不会越测越偏。

第三层:人工收口与质量闸门

合成和回流都会产出脏数据,收口层做四件事:

  1. 参考答案防污染(最关键)。生成 golden answer 时 LLM 很容易掺入知识库之外的常识,导致 faithfulness 假阳性——模型照着污染答案答,忠实度满分,实际是错的。约束生成:prompt 里强制"只能使用给定 chunks 的信息,不得补充外部知识",且答案必须回链 keypoint。更稳的做法是双模型交叉生成:模型 A 生成、模型 B 独立复述/校验,不一致的进人工。
  2. 拒答类配额。评测集里要放 10-15% 的不可回答问题(知识库没有答案的 query),专门测"会不会硬答"。合成器默认不会生成这类样本,需要手工构造或从日志里捞"转人工"的 query。
  3. 相似度去重。embedding 余弦相似度 > 0.92 的合成样本只留一条。阈值别拍脑袋,先对全量算一遍相似度分布再定,取分布拐点。阈值过低(比如 0.85)会把语义相近但检索难度不同的样本误杀,多样性塌缩。
  4. 抽检审核 SLA。合成集至少抽 10-20% 人工过一遍 grounding(答案是否真的被 reference chunks 支撑);日志回流的新条目全量过人工。抽检发现某类错误率 > 5%,整批回炉。

质量闸门用 LLM-as-Judge 做粗筛、人工做复核,别让 Judge 单独放行——它自己就是被测体系的一部分,自产自销会系统性放水。

路线对比与落地节奏

路线单条成本分布真实度规模速度维护负担定位
公开基准(BEIR/CRAG 等)领域不匹配即取即用起步摸底、横向对比
LLM 合成中(能力倾向)小时级上千条需季度抽检冷启动打底
日志回流高(标注人力)最高随流量积累持续回流上线后校准分布
人工编写最高最高核心场景维护回归门槛 + 抽检基准

落地节奏:冷启动先合成 200-500 条 + 手写 30-50 条核心场景 golden 当回归门槛;上线后日志回流按迭代周期灌入;每个季度对合成集做一轮抽检,把文档已更新导致答案过期的条目淘汰掉。规模参考:一个客服知识库项目,300 条合成打底,回流两个月后测试集到 1200 条,其中日志类占 40%——之后每次发版前全量回归,指标波动超过 ±2pp 才值得查,而之前 80 条手写样例时,改一行 prompt 指标就跳 5pp,根本分不清是变好还是噪声。

踩坑记录

  1. golden answer 污染是合成集头号质量问题。LLM 生成答案时掺外部知识,忠实度指标全线假阳性。解法:约束生成 + 双模型交叉校验 + grounding 抽检,三层缺一不可。
  2. 测试集不标doc_version,文档一更新就集体过期。知识库改版后旧答案失效,回归全红,你以为是模型坏了,其实是数据死了。chunk 内容 hash 变了就把对应用例标记过期,重生成。
  3. 日志不去重直接灌,指标被高频简单问题拉高。头部 query 霸榜时,retrieval recall 虚高 5-8 个点,长尾真实短板被掩盖。聚类 + 分层抽样是必做项。
  4. 合成和回流用同一个模型。合成器、Judge、被测模型全用 DeepSeek 的话,自偏好偏差会把分数系统性抬高。合成用 A 模型、抽检 Judge 用 B 模型、被测是 C,至少保证评测链路里有一个模型和被测不同。
  5. 把拒答类样本漏掉。没有 unanswerable 用例的测试集,测不出"没答案硬编"——这是 RAG 生产事故里最贵的一类错误,10-15% 配额别省。

总结与进阶方向

评测集不是一次性资产,是一条数据流水线:合成打底管冷启动和能力覆盖,日志回流管真实分布,人工收口管标注可信度,doc_version+ 季度抽检管时效性。三层各司其职,指标的可信度才有根基。进阶方向:把 badcase 自动回流做成闭环(线上判分低的回答 → 进人工复核 → 确认后自动进测试集),以及按切片(query 类型、文档簇、难度)监控测试集自身的难度漂移——这两件事做扎实了,评测集才会跟着产品一起进化,而不是半年后变成一堆没人信的过期数字。


如果你也在做 AI 应用(RAG / Agent / LLM),不知道质量怎么测——我最近在给 AI 应用做免费质量体检,出一份可执行的测评报告(检索命中率、回答忠实度、噪声敏感度等维度),感兴趣可以直接私信我。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 10:51:47

AI图像生成技术实践:角色场景创作与Stable Diffusion部署指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 10:49:13

Python+OCR+GPU加速实现毫秒级倒计时精准识别

简介:本资源是一套面向Python进阶学习者与游戏自动化实践者的《三角洲行动》限时皮肤抢购专用工具,聚焦解决人工抢购中倒计时识别不准、响应延迟高、多分辨率适配难等核心痛点。程序基于Python 3.9构建,深度融合GPU加速图像处理(P…

作者头像 李华
网站建设 2026/9/5 10:48:49

GPU加速OCR实时倒计时识别技术实践

简介:本资源是一套面向Python进阶学习者与游戏自动化实践者的《三角洲行动》限时皮肤抢购专用工具,聚焦解决人工抢购中倒计时识别不准、响应延迟高、多分辨率适配难等核心痛点。程序基于Python 3.9构建,深度融合GPU加速图像处理(P…

作者头像 李华
网站建设 2026/9/5 10:42:10

SolidWorks 2020工程级施肥播种机三维模型:参数化设计与应用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 10:41:40

AI数字人舞蹈动作生成与视频合成技术实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华