AI FaultLab:给 Hybrid RAG 补上检索评测闭环

📅 2026/8/2 5:37:43 👁️ 阅读次数 📝 编程学习
AI FaultLab:给 Hybrid RAG 补上检索评测闭环

AI FaultLab:Hybrid RAG Evaluation 的补齐

今天主要补的是 AI FaultLab 里 RAG 的评测闭环。之前项目已经完成了 Hybrid Retrieval:Milvus 向量召回、BM25-like 关键词召回、RRF 融合、轻量 rerank,以及 Runbook 索引治理。但做到这里,其实还差一个关键问题没有回答:

现在的检索效果到底怎么样?

如果只是看一次 AI 报告里有没有引用到 Runbook,很容易误判。某一次召回对了,不代表整体稳定;某一次 Hybrid 表现好,也不代表它在所有故障类型上都优于单路召回。所以这次补的重点,不是继续堆检索策略,而是给 RAG 检索链路加一套基础评测能力。

当前评测只覆盖 retrieval,不调用 LLM,也不改诊断主流程,这一点比较关键。评测的目标是单独观察“检索器有没有把正确的 Runbook section 找回来”,避免被大模型生成效果干扰。


一、为什么这一步必须做

RAG 链路如果没有评测,很容易变成“看起来能用”。

比如 MQ 消息堆积场景,模型最终报告里出现了“增加消费者并发”“排查慢 SQL”“检查队列积压”,这些建议看起来都合理。但真正要追问的是:

这些建议是不是来自正确的 Runbook section? Hybrid 召回是不是比单独 Milvus 更稳? 修改 query construction 后效果有没有变差? rerank 权重调整后有没有误伤某些 case?

如果没有评测集,这些问题都只能凭感觉判断。
所以这次我把 RAG Evaluation 单独抽出来,做成一套小型但可扩展的评测链路。


二、评测集怎么设计

当前评测数据集放在:

faultlab-ai-service/evaluation/rag_eval_cases.json

第一版一共 9 个 case,覆盖项目里的三类故障:

MQ_BACKLOG THREAD_POOL_SATURATION IDEMPOTENCY_CONFLICT

每个 case 主要包含四类信息:

caseId scenarioCode query 中的 ruleResult 和 metrics expected docId + section

这里最重要的是 expected 的定义。

我没有只判断“召回到同一个文档就算命中”,而是使用严格的:

docId + section

也就是说,如果期望是:

{ "docId": "mq-backlog", "section": "核心指标" }

那么只召回mq-backlog这个文档,但 section 是“常见原因”,不能算完整命中。

这个约束比只看 docId 更严格,也更符合实际诊断场景。因为 Runbook 里的“核心指标”“常见原因”“修复建议”虽然都属于同一个文档,但对 Prompt 的帮助是不一样的。诊断证据分析阶段需要“核心指标”,根因分析阶段需要“常见原因”,最后给解决方案时才更依赖“修复建议”。


三、指标选择:Hit@K、Recall@K、MRR

这次没有一开始就上很复杂的指标,而是先实现了三个最基础、最容易解释的指标。

Hit@K

Hit@K 关注的是:

topK 里有没有命中任意一个期望 section

只要命中一个 expected ref,这个 case 就算 hit。

它适合回答:

这个问题有没有至少召回一个有用的 Runbook section?

Recall@K

Recall@K 更细一些,计算的是:

topK 命中的 expected refs 数量 / expected refs 总数

如果一个 case 期望召回两个 section,结果只召回了一个,那么 Recall@K 就是 0.5。

它适合回答:

该找回来的内容找回来了多少?

MRR

MRR 关注第一个命中结果的位置。

如果第一个结果就命中:

MRR = 1.0

如果第二个结果才命中:

MRR = 0.5

如果完全没命中:

MRR = 0

它适合观察排序质量。召回到了但排在很后面,和排在第一位,实际效果是不一样的。

这三个指标组合起来,基本可以覆盖第一版 RAG 检索评测的核心需求:有没有命中、命中了多少、命中位置靠不靠前。


四、支持多种 Retriever 对比

当前评测接口支持:

bm25 milvus hybrid all

也就是说,可以单独评测 BM25-like,也可以单独评测 Milvus,还可以评测 Hybrid。retriever=all会分别跑多种 retriever,然后返回各自结果。

接口是:

POST /ai/runbooks/evaluate

常见请求:

{"retriever":"bm25","topK":3}
{"retriever":"hybrid","topK":3}
{"retriever":"all","topK":3,"report":true}

这里有一个细节:如果某个 retriever 失败,不会影响其他 retriever 的结果。

比如 Milvus 没启动,milvus评测可能返回结构化错误,但bm25仍然可以正常评测。这和诊断链路里的 fallback 思路是一致的:外部组件失败,不应该把整个流程拖垮。


五、CLI 和 API 都保留

这次没有只做接口,也补了 CLI。

本地可以直接跑:

.\.venv\Scripts\python.exe scripts\evaluate_retrieval.py --retriever bm25 --top-k 3

也可以输出 Markdown 报告:

.\.venv\Scripts\python.exe scripts\evaluate_retrieval.py --retriever all --top-k 3 --report

还可以写入文件:

.\.venv\Scripts\python.exe scripts\evaluate_retrieval.py --retriever all --top-k 3 --report --output evaluation/reports/rag_eval_report.md

CLI 的好处是方便本地调试,也适合后续接 CI。API 的好处是方便以后做可视化页面或者运维入口。

生成的报告文件默认不提交,只保留evaluation/reports/.gitkeep。这个处理比较干净,避免把每次本地运行出来的报告都混进 Git。


六、Markdown Report 做了什么

原来的 Evaluation 结果是 JSON,适合程序读,但不适合人看。今天补了 Markdown Report,主要是为了复盘和展示。

报告包含这些部分:

Overview Overall Metrics Retriever Comparison Metrics By Fault Type Case Details Miss Cases Optimization Suggestions

我觉得最有价值的是后面两块:Miss CasesOptimization Suggestions

因为评测不是为了证明“我这个 RAG 很强”,而是为了发现哪里没召回、为什么没召回、下一步应该怎么改。

比如 BM25-like 某个 case 没命中,可能说明:

Runbook keywords 不够 section 标题没有覆盖关键指标名 query 里的字段名和 Runbook 表达没有对齐

这时优化方向就比较明确:

补充 Runbook keywords 调整 section 标题 在正文中补充指标名、英文别名、字段名 优化 query construction 调整 RRF k、rerank 权重和 topK

这些建议目前是规则型的,不调用 LLM。这样做的好处是稳定、可解释,也不会把评测报告本身再变成一个不可控的大模型输出。


七、今天开发完成后的链路

现在 RAG Evaluation 的链路大概是:

rag_eval_cases.json -> RetrievalEvaluator -> 指定 retriever -> bm25 / milvus / hybrid -> retrieved chunks -> docId + section 命中判断 -> Hit@K / Recall@K / MRR -> Markdown Report -> Miss Case Analysis

它和诊断主链路是分开的:

Evaluation 不调用 LLM Evaluation 不走 diagnosis workflow Evaluation 不改 DiagnosisResponse Evaluation 不影响 Java 后端和前端

这一点很重要。评测就是评测,不掺杂生成链路。这样才能更准确地看 retrieval 本身的问题。


八、这次的工程取舍

1. 先做小评测集,不追求大而全

当前只有 9 个 case,数量不多,但覆盖了三个核心故障类型。第一版不需要追求数据量,重点是把评测结构搭起来。

后面扩 case 比改评测框架容易得多。

2. 使用 docId + section 严格命中

只看 docId 太粗,容易让结果虚高。
所以这里用docId + section判断命中,能更真实地反映检索质量。

这个设计会让初始指标没那么好看,但更有参考价值。

3. 不调用 LLM

Evaluation 只评 retrieval,不评 generation。

原因很简单:如果把 LLM 也放进来,最后结果不好时,很难判断是检索问题、Prompt 问题,还是模型生成问题。

第一阶段先把 retrieval 单独量化清楚。

4. 先用 Hit@K、Recall@K、MRR

没有一开始做 nDCG,因为当前 case 数量少,expected 也比较简单。
Hit@K、Recall@K、MRR 已经足够支撑第一版对比和回归。

后续 case 多了,再加 nDCG 更合适。

5. Markdown Report 面向人读

JSON 适合程序,Markdown 适合复盘。

这次补 Markdown Report,主要是为了让结果能直接用于:

技术博客 面试展示 PR 说明 后续优化记录

这比单纯返回一堆 JSON 指标更实用。


九、目前能回答哪些问题

做到这一步后,这套 RAG 已经不只是“能跑”,而是开始能回答一些工程问题。

比如:

Hybrid 的整体 Hit@K 是否高于 bm25? Milvus 在某些 faultType 上是不是更容易 miss? BM25-like 对指标名是否更敏感? 哪个故障类型召回最弱? 哪些 case 经常只召回到正确 docId,但 section 不对?

这些问题一旦能被量化,后续优化就不再是凭感觉。


十、当前限制

这套 Evaluation 还只是基础版。

目前的限制主要有:

case 数量少,目前只有 9 个 没有 nDCG 没有 CI regression gate Milvus evaluation 依赖 Milvus 和 embedding 可用 优化建议是规则型,不调用 LLM BM25-like 不是标准搜索引擎级 BM25 lightweight rerank 不是真实 rerank 模型

这些都不是问题,只是阶段边界。

当前目标是把评测闭环先跑通,而不是一次性做成完整搜索评测平台。


十一、后续可以怎么演进

后面可以沿着几个方向继续补。

第一是扩充 case。
现在每类故障只有少量 case,后面可以按故障现象、指标组合、边界场景继续扩。

第二是按 faultType 做趋势统计。
比如每次调 rerank 权重后,看 MQ、线程池、幂等三类故障的 Recall@K 有没有变化。

第三是加 nDCG。
当 expected section 有强弱相关性时,nDCG 会比 Recall@K 更细。

第四是做 CI regression gate。
比如 Hybrid 的 Hit@3 不能低于某个阈值,否则 PR 不允许合并。

第五是接入真实 rerank 模型后做对比。
现在是 lightweight rerank,后续可以把真实 rerank 的效果和当前规则 rerank 做横向比较。


十二、总结

今天这一步看起来不是“新增一个酷功能”,但对 RAG 工程化很关键。

之前的链路是:

Runbook -> Milvus / BM25-like / Hybrid -> Prompt -> LLM

现在补上了:

Eval Cases -> Hit@K / Recall@K / MRR -> Markdown Report -> Miss Case -> Optimization Suggestions

也就是说,RAG 不再只是一个检索增强模块,而是开始有了评测和反馈闭环。

这个阶段完成后,AI FaultLab 的 RAG 可以更准确地描述为:

具备 Hybrid Retrieval、索引治理和检索效果评测的故障诊断 RAG 链路。

这比单纯说“用了 Milvus 做 RAG”要扎实得多。