M9 · Benchmark 方法论与质量保证#
一句话定位: 用 6 个评测脚本 + 182 条标注数据 + 532 个单元测试 + 5 种压测流量模型,构建”LLM 应用的质量不是靠感觉,是靠数据”的评测体系——覆盖 RAG 检索质量、路由准确率、降噪率消融、处置安全红队和并发压测。
开场钩子#
场景#
V3 开发中期,Skill Router 做了一次”优化”——把路由 prompt 从整页 Skill 正文换成了只含目录行的精简版,理由是”prompt 更短 → 延迟更低 → 成本更低”。改完感觉没问题——手动测了几条告警,路由结果看着合理。上线后跑了两周才发现:有一类”跨域故障”(网络 + 主机同时出问题)的路由准确率从 75% 掉到了 50%——精简 prompt 把 Skill 之间的差异信息丢掉了,LLM 分不清该走哪个 Skill。
这个教训的核心不是”优化做错了”,而是没有 benchmark 就不知道做错了。手动测几条只能验证 happy path,覆盖不了边缘情况。从那以后每次改 prompt/模型/参数,都先跑一遍 benchmark 对比 baseline,有回归就不上。
面试官切入#
“你的评测数据从哪来的?16 条 / 50 条这种量级够吗?“
一、模块运作流程#
1.1 一句话定位#
本模块不是一个运行时模块——它是一套评测工具链和方法论,回答”系统各层做得有多好”和”改了之后有没有变差”。覆盖 4 个维度:检索质量(RAG)、路由准确度(Router)、系统容量(压测)、安全健壮性(红队 + 消融)。
1.2 全景流程图(文字版)#
评测维度 1: 检索质量
scripts/eval_ragas.py + benchmark/run_benchmark.py
→ 50 条 QA + 50 条 R@K
→ 4 项 RAGAS 指标 + 2 项 OpenEvals
评测维度 2: 路由准确度
scripts/eval_router.py
→ 16 条标注(含域外)
→ 准确率 + A/B 对比 + 回归检测
评测维度 3: 系统容量
scripts/loadtest.py
→ 5 种流量模型(flat/ramp/sustained/burst/mixed)
→ 成功率 / P50-P99 / 吞吐 / 429 / 队列快照
评测维度 4: 安全健壮性
scripts/eval_remediation_safety.py
→ Part A: 意图红队(注入/越权/保护资源) → block_rate
→ Part B: 审批 CAS 并发 → "恰好 1 个成功"
评测维度 5: 降噪率消融
scripts/eval_dedup_layers.py
→ 逐层 monkeypatch 失效 → 量化每层边际贡献
评测维度 6: 证据覆盖
scripts/eval_deep_evidence.py
→ 12 条标注 → coverage + 并行取证收益
单元测试: 532 个 test 函数 / 59 个文件plaintext1.3 分步详解#
评测 1: RAG 检索质量
走真实 RAG 管线(混合检索 + rerank + LLM 生成),数据源:
data/eval/qa.jsonl(10 条快速冒烟)benchmark/ragas_qa_50.jsonl(50 条,覆盖 10 个故障场景各 5 条)benchmark/retrieval_rk_50.jsonl(50 条纯检索侧评测)
4 项 RAGAS 指标:
| 指标 | 测什么 | 判什么 |
|---|---|---|
| Faithfulness | 无编造 | 答案的每个 claim 是否都能在检索上下文中找到依据 |
| AnswerRelevancy | 切题 | 答案是否回答了问题(不跑题) |
| ContextPrecision | 排序准确 | 检索返回的 chunks 中,相关的排在前面吗 |
| ContextRecall | 召回覆盖 | ground truth 里的关键信息是否被检索到了 |
benchmark/run_benchmark.py 额外接入 OpenEvals 的 groundedness + helpfulness。
评测 2: Skill Router 准确度
16 条标注数据集(benchmark/router_skill_16.jsonl),含域内匹配 + 域外拒绝(OUT_OF_SCOPE)。
核心特色是 A/B 对比:
full模式:Router prompt 包含整页 Skill 正文index模式:Router prompt 只含目录行(精简版)
自动判定”无回归”或”出现回归”。用临时 wiki monkeypatch 隔离,不需要 DB/向量库,只需 LLM。
评测 3: 并发压测
5 种流量模型覆盖不同场景:
| 模型 | 场景 | 验证什么 |
|---|---|---|
| flat | 固定并发冒烟 | 基本可用性 |
| ramp | 阶梯加压 | 找到拐点(QPS 多少时 P95 开始飙升) |
| sustained | 恒定 QPS 稳态 | 长时间运行是否有内存泄漏 / 连接池耗尽 |
| burst | 平稳-突发-回落 | 限流回压是否正确(突发时 429,回落后恢复) |
| mixed | submit + webhook 同时 | 多入口同时打的真实场景 |
输出指标:成功率、P50/P95/P99 延迟、吞吐 req/s、429 限流命中率、队列快照(depth/pending/lag/dlq)。
评测 4: 处置安全红队
- Part A:意图红队——构造注入/越权/保护资源等攻击输入,走
validate_intent+check_policy链路,统计 block_rate(拦截率)和 false_positive_rate(误拦率)。纯函数不需 LLM。 - Part B:审批 CAS 并发——N 个并发
claim_for_execution打真实 Postgres,验证”恰好 1 个成功”的原子性。
评测 5: 降噪率消融
V4 新增。三层去重消融 benchmark:L1 fingerprint 幂等 → L2 correlation 聚合 → L3 诊断任务级去重。分别 monkeypatch 失效各层,量化每层对收敛比(总告警数 / 实际创建 task 数)的边际贡献。
评测 6: 证据覆盖
12 条标注数据集,两组实验:
- coverage:证据覆盖率 + RCA 关键词命中率
- parallel-benefit:并行 vs 串行 subagent 取证耗时对比
1.4 数据流 trace#
场景:跑一次 RAG 评测
输入: benchmark/ragas_qa_50.jsonl
[{question: "MySQL 主从延迟超过 10s 怎么排查?",
ground_truth: "检查 Seconds_Behind_Master、IO/SQL 线程状态、网络延迟...",
contexts: ["expected_chunk_1", "expected_chunk_2"]}, ...]
Step 1 — 检索:
对每条 question 走真实 RAG 管线:
pgvector cosine → Top-K candidates
pg_search BM25 → Top-K candidates
rrf_fuse → 排序后 Top-N
rerank → 最终 chunks
Step 2 — 生成:
LLM 根据检索到的 chunks 生成 answer
Step 3 — 评测:
RAGAS 框架计算 4 项指标:
Faithfulness: answer 的 claims → 逐条检查是否在 context 中有依据
AnswerRelevancy: 从 answer 反向生成问题 → 和原问题比相似度
ContextPrecision: ground_truth 中的关键信息 → 在检索结果的第几位
ContextRecall: ground_truth 的 claims → 有多少被检索到了
Step 4 — 输出:
JSON 报告 + 每条 QA 的分项得分 + 整体均值plaintext1.5 技术选型决策表#
| 选了什么 | 备选方案 | 为什么选它 | 什么情况下换 |
|---|---|---|---|
| RAGAS | DeepEval / LangSmith | RAGAS 的 4 项指标覆盖 RAG 管线的核心关注点;开源且可本地跑 | DeepEval 支持更多指标(如 hallucination 细分),但依赖其平台 |
| 脚本评测 | CI 自动化 | 当前数据集小(50 条),脚本足够;CI 化需要先有稳定的 baseline | 数据集超过 500 条 + 有稳定 baseline → 集成进 CI |
| 手工标注 | LLM 生成标注 | 50 条手工标注保证质量;LLM 生成的标注有幻觉风险 | 扩大数据集时用 LLM 生成 + 人工审核 |
1.6 口述脚本#
开场(20s):“LLM 应用的质量不能靠感觉——改了一个 prompt 感觉变好了,但实际上路由准确率从 75% 掉到了 50%。没有 benchmark 就不知道做错了。”
评测体系(30s):“我建了 6 个评测脚本覆盖 4 个维度:RAG 检索质量用 RAGAS 4 项指标,Router 准确度用 A/B 对比自动回归检测,处置安全用红队测试,系统容量用 5 种流量模型压测。”
方法论重点(20s):“最关键的不是数字本身,而是每次改动都跑 benchmark。有了 baseline,任何变化——prompt 改了、模型换了、参数调了——都能量化影响。”
留钩子(10s):“但 50 条数据集够不够?老实说——不够。“
二、踩坑实录#
坑 1:Router A/B 对比发现精简 prompt 导致隐性回归#
- 现象:把 Router prompt 从整页 Skill 正文换成目录行精简版后,手动测了几条没问题。两周后发现跨域故障的路由准确率下降。
- 根因:精简 prompt 丢掉了 Skill 之间的差异信息——目录行只有 “Host Resource Diagnosis” / “Network Diagnosis” 等标题,LLM 分不清”CPU 高但同时有网络超时”该走哪个。
- 修法:(1) 建了 16 条标注数据集覆盖域内 + 域外 + 跨域场景;(2)
eval_router.py加 A/B 对比,每次改 prompt 自动检测回归。 - 教训:LLM 的行为变化是隐性的——相同的输入,不同的 prompt 可能产生不同的结果,而你感知不到。只有用固定数据集跑固定指标,才能发现变化。
坑 2:RAGAS 的 Faithfulness 对中文支持不佳#
- 现象:Faithfulness 指标在中文 QA 上波动很大——同一条 QA 跑两次分数差 0.2。
- 根因:RAGAS 内部用 LLM 做 claim 提取和验证(“答案里有哪些 claim?每个 claim 在 context 里有没有依据?”),中文的 claim 拆分边界不稳定——LLM 对中文的句子切分不一致。
- 修法:(1) 增大评测轮次(每条跑 3 次取均值)降低随机性;(2) 结合 OpenEvals 的 groundedness 做交叉验证。
- 教训:LLM-as-Judge 的评测本身也有非确定性——用 LLM 评价 LLM,评价者的幻觉和被评价者的幻觉叠加。评测指标不能只看绝对值,要看趋势变化。
坑 3:压测的 P95 数据被长尾离群值干扰#
- 现象:200 并发压测时 P95 延迟飙到 3795ms,看起来系统扛不住。但 P50 只有 221ms。
- 根因:少数请求碰到了 LLM RPM 限流被 retry,retry 的等待时间拉高了 P95。大部分请求正常返回。
- 修法:压测报告同时展示 P50/P95/P99 + 成功率 + 429 数量,不单看 P95。P95 高但成功率 100% 说明”慢了但没错”——需要区分”慢”和”错”。
- 教训:延迟百分位和成功率必须一起看。P95 高可能是限流回压的正常表现,不一定是系统 bug。
坑 4:降噪率消融的 monkeypatch 目标要选对#
- 现象:
eval_dedup_layers.py对 L2 correlation 做消融时,monkeypatch 失效了关联引擎但没失效时间窗聚合——结果降噪率几乎没变,误以为 L2 关联没有价值。 - 根因:L2 的降噪有两个独立机制:时间窗聚合(同 service 的告警归并)和拓扑关联(跨 service 的因果归并)。只关掉拓扑关联时时间窗聚合仍然在降噪。
- 修法:消融实验改为逐层完整失效——L2 消融关掉整个预处理引擎(
engine.py),而不是只关某个子功能。同时加”单因子消融”看每个子功能的边际贡献。 - 教训:消融实验的粒度选择影响结论。太粗(关掉整层)看不到哪个子功能有价值;太细(只关一个函数)可能遗漏交互效应。需要两种粒度都做。
三、面试问答#
基础题(必问级)#
Q1: 50 条 RAG 评测数据集够吗?怎么保证覆盖度?#
答: 不够,但在当前阶段是可接受的起点。50 条覆盖 10 个故障场景各 5 条——磁盘/CPU/内存/网络/容器/服务/数据库/部署/日志/综合。覆盖度的保证不在于数量多,而在于场景设计——每个场景内的 5 条变体覆盖不同的表达方式(正式描述 / 口语化 / 关键词缺失 / 噪声干扰 / 多条件组合)。
扩大数据集的计划:(1) 从生产告警中采样(目前没有生产环境);(2) 用 LLM 生成 + 人工审核扩充到 200 条;(3) 每次有新的误诊案例,加入数据集作为回归用例(“bug-driven benchmark growth”)。
追问: 数据集是你手工标注的——有没有 bias? 答: 有。标注人就是开发者,对系统的知识库内容非常熟悉——标注的 ground_truth 可能偏向”知识库里有的内容”,而不是”运维工程师真正关心的内容”。理想情况应该找不了解系统的运维工程师做盲标。但当前团队只有一个人,这是资源限制。
Q2: RAGAS 的 4 项指标你觉得哪个最重要?#
答: ContextRecall 最重要——因为它直接衡量”检索有没有把该找到的东西找到”。如果 recall 低,后面的生成再好也没用——LLM 基于不完整的上下文生成答案,必然有盲区。Faithfulness 排第二——它衡量”LLM 有没有编造”,这是 AIOps 场景的硬约束(运维人员不能接受编造的根因分析)。AnswerRelevancy 和 ContextPrecision 重要性稍低——前者是生成质量,后者是排序优化。
追问: 如果 Recall 高但 Faithfulness 低怎么办? 答: 说明检索把相关内容都找到了,但 LLM 在生成时”添油加醋”了。修法在 prompt 层面——约束 LLM “只根据提供的上下文回答,不要添加额外信息”。RAGAS 的 Faithfulness 分数可以作为 prompt 优化的指导指标。
进阶题(区分度)#
Q3: Router A/B 对比——你怎么判定”有回归”?#
答: 不是简单的”准确率下降就是回归”。判定逻辑:两个配置各跑一遍 16 条数据集,统计准确率。如果 B(新配置)的准确率 ≥ A(baseline),判定”无回归”。如果 B 的准确率 < A,进一步看哪些 case 变了——如果只是域外拒绝(OUT_OF_SCOPE)的判定变了,可能是可接受的;如果核心域内路由变了,那就是真回归。自动判定只看总准确率,细粒度分析需要人工审核变化的 case。
Q4: 处置安全红队测试——你的红队测试有多”红”?#
答: 比较初级。Part A 是手工构造的攻击输入——注入("; rm -rf /")、越权(保护进程 kill)、保护文件写入。走的是 validate_intent + check_policy 的纯函数链路,不经过 LLM。这验证的是安全规则的覆盖度,不是”LLM 能否被诱导绕过规则”。
真正有效的红队测试应该是端到端的——构造恶意告警(annotation 里注入攻击指令),看 LLM 会不会被诱导产出危险操作意图,再看安全规则能不能拦住。这需要跑真实 LLM,当前没做——因为每次红队测试消耗大量 token,而且 LLM 的行为不确定(同一个攻击可能有时成功有时失败)。
压力题(面试官挑战设计决策)#
Q5: 你的评测全是离线的——怎么知道线上效果?#
答: 不知道。当前所有评测都是离线跑的(固定数据集 + 脚本),没有线上 A/B 实验或金丝雀发布。线上效果需要两个前提:(1) 有生产环境在跑(当前没有);(2) 有 ground truth 数据(人工标注每次诊断的根因是否正确)。这两个都需要实际上线后才能积累。
离线评测的价值在于防止回归——改了 prompt 之后跑 benchmark,至少确保”比之前不差”。但”不差”不等于”好”——离线 benchmark 的 case 可能不覆盖线上的真实场景。长期来说应该建”Golden Incident Set”(从生产中采样的标注数据集),这在 REDESIGN 总纲的 §12 里列为待办。
Q6: 532 个测试函数听起来很多——但你有集成测试吗?单元测试能保证系统端到端正确吗?#
答: 不能。532 个测试绝大部分是单元测试(mock 外部依赖)。端到端集成测试的覆盖很薄——主要靠 eval 脚本(eval_ragas.py 走真实 RAG 管线、eval_deep_evidence.py 走真实 LLM)和真机验证(本地 Postgres + Redis)。
单元测试保证的是”每个组件按预期工作”,但不保证”组件之间的交互按预期工作”。举例:test_investigation_ladder.py 验证了 Tier 1→2→3 的递进逻辑,但它 mock 了 LLM 和 MCP 工具——真实环境下 LLM 返回的格式可能和 mock 不一致、MCP 工具可能超时——这些交互问题只有集成测试能发现。这是已知的短板。
四、前沿概念与延伸#
4.1 LLM 评测方法论(RAGAS / DeepEval / LLM-as-Judge)#
是什么:用 LLM 作为评价者(LLM-as-Judge),或用专门的评测框架(RAGAS/DeepEval),自动化评估 LLM 应用的生成质量——替代大规模人工评审。
为什么要这么做:人工评审是 gold standard 但无法规模化——50 条 QA 人工评审要半天,500 条要一周。LLM-as-Judge 可以秒级跑完,且对 Faithfulness/Relevancy 这类指标的判定与人工高度一致(RAGAS 论文报告 >0.8 Spearman 相关性)。
这么做的理由:相比传统 NLP 指标(BLEU/ROUGE),LLM-as-Judge 能捕捉语义层面的质量——“答案措辞不同但意思正确”会被正确评为高分,BLEU 会给低分。
举例说明:本项目用 RAGAS 的 4 项指标评测 RAG 管线,用 OpenEvals 的 groundedness 做交叉验证。评测本身也是 LLM 调用——评测的成本不低(50 条 QA × 4 指标 = 200 次 LLM 判定),但比人工审核便宜得多。
延伸问答:
面试官:“LLM-as-Judge 的评价可靠吗?LLM 自己都会幻觉,怎么评价别的 LLM?” 答:不完全可靠——这是已知局限。缓解措施:(1) 用更强的模型做 judge(如用 GPT-4 评价 Qwen);(2) 交叉验证(RAGAS + OpenEvals 两套框架比对);(3) 在小数据集上先和人工评审对标,确认相关性后再规模化。评测不是 ground truth——它是”比没有好得多”的自动化近似。
4.2 混沌工程与故障注入#
是什么:在受控环境中有意引入故障(杀进程、注入延迟、填满磁盘),验证系统的容错能力。Netflix Chaos Monkey 是经典实现。
为什么要这么做:单元测试验证的是”正常路径是否正确”,混沌工程验证的是”异常路径是否不崩溃”。AIOps 系统面对的就是异常——如果系统自身在故障下也挂了,那就是”医生比病人先倒下”。
这么做的理由:相比被动地等线上出故障再看系统反应,混沌工程主动在测试环境制造故障——成本更低、可重复、可控制爆炸半径。
举例说明:本项目的 Runbook auto_regression(用历史告警回放检查匹配率 + 处置步骤校验)是一种受限的混沌工程——它不实际执行处置,但验证了”如果这条告警来了,Runbook 会匹配吗?匹配后步骤合法吗?“。完整的混沌工程(在测试环境里真的杀一个容器、看系统能不能自动诊断 + 修复)是 backlog。
延伸问答:
面试官:“你的系统自己也可能挂——比如 Worker 崩了、Redis 挂了。你怎么保证 AIOps Agent 自身的高可用?” 答:四个机制:(1) Worker 崩溃时 XAUTOCLAIM 回收 stale 任务;(2) Redis 挂了限流器 Fail-Open + 锁降级内存锁;(3) Postgres 挂了只影响落库不影响诊断(LangGraph 的 state 在内存里);(4) LLM 挂了 Tier 1/2 纯规则/检索仍工作。但坦白说没做过系统性的 chaos testing——只是手动模拟过 Redis 断连的场景。
五、诚实边界#
- 数据集太小。50 条 RAG QA、16 条 Router、12 条证据覆盖——统计意义有限。改一个参数可能只影响 1-2 条 case 的结果,但在 16 条数据集里这就是 6-12% 的变化——噪声太大。
- 没有 Golden Incident Set。理想的评测应该有”从生产中采样的真实 Incident + 人工标注的正确根因”组成的黄金数据集。当前没有生产环境,黄金集列为待办。
- 评测不在 CI 里。每次改代码需要手动跑
scripts/eval_*.py,忘了跑就可能带着回归上线。应该集成进 CI pipeline,但需要先有稳定的 baseline 和可接受的运行时间(当前一次完整评测约 30 分钟,LLM 调用成本约 5 元)。 - 没有线上评测。所有评测都是离线的固定数据集。线上效果需要 A/B 实验或至少”每次诊断后收集人工反馈”——这需要产品化的反馈机制,目前没有。
- 红队测试太初级。安全红队只测了规则层(validate_intent + policy),没测端到端的 LLM 提示注入。真正的红队应该用对抗性 prompt 打 LLM,看它能不能被诱导产出危险意图。
- 降噪率 / Tier 命中分布的数字未实测。埋点已就位(suppressed/dedup_count/investigation_tier 都落库),但没有跑过告警风暴回放。基线数字要用
scripts/loadtest.py+scripts/eval_dedup_layers.py实测后再写。