忠实度 0.25 → 0.99:一次先怀疑评测、再怀疑模型的排查
忠实度只有 0.25,与人工观察明显不符。这是一次把嫌疑人先放在评测口径上的排查复盘:引用预览被截断污染了判分上下文,以及为什么我坚持给 RAG 项目先建可信的评测,再谈优化。
- 评测
- RAG
- 复盘
- 调试
指标异常时,我给自己定过一条排查顺序:先怀疑评测口径,再怀疑模型能力。忠实度 0.25 这件事,就是这条顺序被真正用上的一次。文中的事实与测量方式都来自项目记录,超出记录的解释会标注为「分析」或「推断」,与事实分开。
背景:为什么我要先建一套评测
RAG 的链路很长:分块 → 嵌入 → 检索 → 重排 → 生成。任何一环出问题,肉眼都很难发现,改动一次「感觉更好了」也没有依据。所以我把评测做成了优化的一部分:52 条测试集 × 20 篇语料(85 块)× 4 档检索配置,生成质量用 LLM-as-Judge(DeepSeek 判分)打分,报告落盘。
我要的不是一个好看的分数,而是一个能对齐人工观察的分数。而这次要讲的,正是这个假设几乎被打破的一次。
问题:忠实度只有 0.25
某次评测里,忠实度(faithfulness)只有 0.25。按这个分数的含义,等于「回答的大部分内容没有依据」。但我人工翻看这些回答时,绝大多数答案是贴着引用原文写的 —— 分数与观察明显不符。
排查:我把范围缩小到判分输入
这一步我没有动模型,而是把注意力放在评测本身。围绕「判分到底看到了什么」,我做了三件事:
- 对齐人工观察:先确认「分数说的问题」和「人看到的现象」是否一致。不一致时,评测本身进入嫌疑范围。
- 检查判分输入:把送去判分的上下文打印出来逐条核对 —— 不像在查代码,更像在查证据链。
- 定位到截断:引用预览被截断到 200 字符,判分模型拿到的是一段残缺的出处,于是把「有依据的回答」判成了「没有依据」。
根因与修复:把展示口径和评测口径拆开
修复方式是新增 citation_preview_len 参数:前端仍然用 200 字符,评测改用 2000 字符。
验证:重跑全部配置,看指标回到什么水平
| 指标 | 结果 | 测量方式 |
|---|---|---|
| 忠实度 | 0.99(修复前为 0.25) | LLM-as-Judge,DeepSeek 判分,52 题 |
| 引用命中率 | 100% | LLM-as-Judge,DeepSeek 判分,52 题 |
| 相关性 | 0.95 | LLM-as-Judge,DeepSeek 判分,52 题 |
| 检索命中率(top-1) | 96.2% → 98.1% | 52 题测试集 / 20 篇语料 / 85 块,bge-m3 真实嵌入,四档检索配置对比 |
这张表里只有忠实度有明确的修复前数值 —— 项目记录里就是这么写的,其余指标只记录了修复后的结果与测量方式。没有记录的前后对比,我不补。
边界:我没有把 0.99 当成「问题解决了」
同一套评测还留下了一条不那么好看、但很有用的记录:有 2 道题在所有检索配置下都会漏。我没有把 0.99 理解成忠实度问题已经解决 —— 它只说明「在 52 题这个范围内,测量结果可信了」。把边界如实写出来,读的人才知道这套东西在什么范围内可靠,这比只展示成功案例更有说服力。
复盘:如果重来一次
- 评测先于优化:没有可信的基线,任何「提升」都无从判断。
- 口径在设计时就分开:展示用与评测用的参数不应该共用一个值。
- 边界主动记录:所有配置都会漏的题如实写下,比只展示成功更能说明适用范围。
- 离线可跑是可验证性的前提:后端 127 个 pytest 用例全部离线可跑,评测与测试都不依赖外部 API Key 也能稳定执行。
这个项目的完整架构、取舍与实测指标在项目页里:查看 DocWise 项目详情。
韦坤计算机科学本科生 · 后端开发 / AI 应用开发方向
相关项目
上传文档即可问答的 RAG 平台:标题感知分块 + BM25/向量混合检索 + Rerank + SSE 流式回答,自带 52 题评测体系与 Agent 工具调用。
- Python 3.11+
- FastAPI(异步)
- httpx
- 还有 21 项技术
相关文章
纯向量检索漏掉了「Transformer 为什么是大模型的基石」
项目约 3 分钟一个真实的跨主题漏检:为什么语义近邻不等于答案就在附近,以及我最终为什么选择自研 BM25 + 向量加权融合 + Rerank,代价是什么、边界在哪里。
容器内 200,线上 404:商品图片全部失联的一次排查
复盘约 4 分钟上传接口返回 200、容器内直连对象存储也返回 200,只有从边缘入口访问图片是 404。记录我用二分法把范围压到反代层、最终定位到 nginx location 匹配顺序的排查过程。
Agent 反复填同一个输入框:一个属性读取方式引起的静默循环
AI / Agent约 4 分钟WebRunner 里最难查的一个缺陷:观察层读的是 HTML 初始属性,动作改的却是运行时状态,于是模型永远看不到「已经填过」。以及它如何变成一条写进设计里的约束。