项目记录
质量门禁与评测闭环
用入库、效果、性能三类门禁和线上错例回流,建立 RAG 系统持续评测与发布验收闭环。
本文目录 · 8 节
第 1 篇开头留了一个问题:改一行阈值,FAQ 直出准确率从 92% 掉到 71%,没人发现。
这一篇就是回答它。RAG 系统的退化是静默的——不报错、不崩溃、接口 200, 只是答案慢慢变得不对。所以必须有一套不依赖人的机制,在它变坏的时候亮红灯。
先给结论
一道闸管入库 :脏数据不许进库
二道闸管效果 :召回、排序、关键词覆盖、命中路径不能掉
三道闸管性能 :首 token 和总耗时不能劣化
一道闭环管演进 :线上错例 -> 回归用例 -> 下一轮被门禁拦住
门禁负责拦住退化,闭环负责让系统变好。 两者缺一不可。
一、评测集:三种问题,三套数据集
| 评测集 | 回答什么问题 | 文件 |
|---|---|---|
| 主链路回归 | 本来能答对的问题现在还答得对吗 | eval_sets/enterprise_knowledge_regression.json |
| 多轮追问 | 带上下文的追问还接得住吗 | eval_sets/enterprise_knowledge_followup.json |
| 性能基线 | 延迟有没有劣化 | eval_sets/enterprise_knowledge_performance.json |
回归集的样本是带契约的,不是一堆问题文本:
{
"case_id": "enterprise_vpn_faq",
"source_filter": "it",
"tenant_id": "default", "dataset_id": "default",
"visibility": "public", "user_role": "public",
"query": "VPN 连不上应该怎么处理?",
"expected_hit_type": "faq_direct",
"expected_keywords": ["网络", "账号", "重启", "IT 工单"],
"expected_source_contains": ["VPN 连不上应该怎么处理", "it_support.md"]
}
注意 expected_hit_type 和 expected_source_contains:它不只断言答案包含什么,
还断言走了哪条路径、来源是哪个文件。 否则「答案碰巧对了,但路径错了」这种退化会被漏掉——
而路径错误往往意味着下一步就会出错。
二、四道闸的默认阈值
效果门禁(scripts/quality/check_evaluation_gate.py):
Recall@K >= 0.8 预期来源有没有被召回
MRR >= 0.6 正确来源排得够不够前
关键词覆盖率 >= 0.7 答案关键事实是否覆盖
命中路径准确率 >= 0.7 FAQ / RAG / 边界判断是否正确
FAQ 直出准确率 >= 0.7
Prompt Profile 路由准确率 >= 0.7
场景隔离准确率 == 1.0 越权这类错误零容忍
错误率 == 0
注意最后两项的特殊待遇:场景隔离必须 100%,错误率必须为 0。 其余指标可以设阈值容忍波动,但「跨租户串数据」和「样本直接报错」不可以。 门禁阈值按指标性质分开对待,比一刀切更有意义。
入库门禁(前一篇讲过)和性能门禁同理:
平均总耗时 <= 15s 首 token 平均 <= 8s
P95 总耗时 <= 30s 首 token P95 <= 15s
错误率 == 0
首 token 单独设阈值是刻意的:流式问答的用户感知发生在首 token,不在总耗时。 总耗时 3 秒但首 token 2.5 秒,体感比总耗时 8 秒、首 token 0.8 秒差得多。
三、闭环:让错例变成资产
没有闭环的评测是消耗品——每次发布都要重新发现同一批问题。这里的闭环是三段:
1. 从评测报告里抽取失败样本 scripts/extract_bad_cases_from_report.py
2. 人工确认(判断是真错还是标注问题)
3. 提升为回归用例 scripts/promote_bad_cases_to_regression.py
-> 下一轮门禁自动拦住它
用户反馈也有对应出口:scripts/export_feedback_bad_cases.py 把差评反馈导出成候选错例。
关键是第 2 步不能省——自动提升会把标注错误一起固化进评测集,
之后你会花时间优化一个本来就不该是问题的样本。
scripts/quality/run_v1_quality_cycle.py 把这一整套串成无人值守循环:
跑评测 → 出门禁判定 → 归档报告。适合放在 CI 里每日跑一次。
四、测试体系:30 个用例文件,覆盖主链路每一环
意图与策略 test_intent_and_scenarios / test_intent_decision_gateway / test_intent_model_governance
检索与生成 test_retrieval_and_prompt / test_answer_confidence / test_stream_query_events
缓存与性能 test_enterprise_cache / test_retrieval_cache / test_retrieval_warmup_wait
入库与质量 test_ingestion_incremental_contract / test_quality_gates / test_threshold_calibration
治理与版本 test_mysql_metadata_stores / test_v1_maintenance_bindings
这些用例能跑得快,是因为关键逻辑都是纯函数或带假依赖的(tests/conftest.py),
不需要真的起 Milvus 和 LLM。第 6 篇说的「把置信度抽成纯逻辑」,回报就在这里。
五、交付验收:发布前跑一遍
# 1. 单元与契约测试
python -m pytest tests -q --tb=short
# 2. 项目护栏:目录结构、约定、禁止事项
python scripts/check_project_guardrails.py
# 3. 主链路评测 + 效果门禁
python scripts/evaluate_core_chain.py --dataset eval_sets/enterprise_knowledge_regression.json --limit 20 --output reports/evaluation/enterprise_knowledge_latest.json
python scripts/quality/check_evaluation_gate.py --report reports/evaluation/enterprise_knowledge_latest.json
# 4. 追问门禁与性能门禁
python scripts/evaluate_followup_chain.py --dataset eval_sets/enterprise_knowledge_followup.json
python scripts/quality/collect_performance_baseline.py --dataset eval_sets/enterprise_knowledge_performance.json --limit 6 --output reports/performance/enterprise_knowledge_latest.json
python scripts/quality/check_performance_gate.py --report reports/performance/enterprise_knowledge_latest.json
# 5. 发布校验
python scripts/verify_v1_release.py
五步全绿,才叫「这次改动没有把系统改坏」。所有报告都落盘到 reports/,
管理端还有 /api/admin/gate_reports 直接查看历史门禁结果。
取舍与边界
- 评测集小则统计意义弱:
--limit 20适合日常快速回归, 正式发布应该跑全量;样本少于几十条时,1% 的波动可能只是一个样本。 - 阈值太严会「红灯疲劳」:天天失败的门禁等于没有门禁。 阈值应该跟着当前真实水平走,每次优化后上调一点,而不是一开始就设在理想值。
- 评测集必须跟着业务演进:业务新增了知识分类,评测集要同步补样本, 否则你会为一条过期的预期反复调试。
收尾:这一整套东西到底买到了什么
回头看这 8 篇,SHUN RAG 的每一个设计都在回答同一个问题: 怎么让系统在没人盯着的时候也不出错。
架构分层 让链路可读、可调试、无旁路
启动校验 让环境问题在启动时暴露,而不是在用户面前
数据契约 让知识入库有边界
切分与指纹 让检索准、更新省
意图与策略 让不同问题走不同的路
混合检索重排 让正确答案进得了上下文
置信度与引用 让不该答的就不答
缓存版本隔离 让多租户、多版本 coexist
门禁与闭环 让退化被拦住,让错例变资产
如果只带走一句话:RAG 的工程量,绝大部分不在模型上,而在这些「不回答」和「不通过」的判断里。