RAG、检索与 Agent Eval

Agent 答对了,为什么 Benchmark 仍然失败?——一次 AIOps Agent 的评测调优实战

区分诊断能力、评分口径与 Validator 失败,展示一次有证据的 Benchmark 调优。

BenchmarkValidatorAIOps

Agent 的根因判断正确,不代表 Validator 可用、Scorer 正确,也不代表恢复动作可以自动执行。

本文数字均来自项目中已持久化或已文档化的 Snapshot/Live 评测 Run,只用于解释调优过程,不代表生产 环境 SLA 或业务收益。

一个反直觉的 89 分

一次 PostgreSQL 死锁诊断中,Agent 已经给出了正确方向:故障组件是 order-service,机制是 opposite_order_transaction_deadlock。它收集的三条必要证据也全部通过,component、mechanism 和安全边界都没有问题。

结果却只有 89 分。

更反直觉的是,运行记录里还出现了 model_call_failed。这是否意味着 Agent 根本没有答对?并不是。 失败的是 LLM Validator 的调用;候选结论本身已经通过十项公开的确定性检查。此外,评分器把一次 knowledge_retrieval 也算进了“必须产生诊断 Observation 的工具调用”,分母比真正的诊断工具多了 一个。与此同时,Agent 对 trigger 和 causal chain 的表达又确实不够完整。

同一张 89 分答卷里,其实混合了三类问题:

  1. Agent 生成的因果表达不完整;
  2. Validator 的模型调用不可用;
  3. Scorer 的统计口径有偏差。

所以,看到低分后立刻改 Prompt,往往改错了地方。问题不一定出在“Agent 有没有答对”,而可能出在 Observation 如何变成 Evidence、Decision 是否满足结构约束、Validator 能否完成调用,以及 Scorer 是否按正确口径读取 Artifact。调优对象因此不是一句答案,而是整条可审计证据链。

先把 Agent Benchmark 看成一条证据链

先约定几个容易混淆的概念。假设 Agent 调用数据库等待图工具:

  • Observation 是工具返回的原始观测,例如“事务 A 等待事务 B 持有的行锁”;
  • Evidence 是经过相关性、归属和可信度评估后,能够支持或反驳某个 hypothesis 的证据;
  • Decision 是 Agent 基于 Evidence 形成的候选根因与因果链;
  • Validator 检查候选结论有没有被引用的 Evidence 支撑,并满足结构与安全约束;
  • Scorer 在 Agent 运行结束后,读取与 Agent 隔离的 Ground Truth 并计算分数;
  • Policy Gate 不负责判断答案像不像标准答案,它只决定某项恢复动作是否可以执行。

原始日志不是自动成为证据,正确结论也不是自动获得执行权限。完整链路更接近下面这样:

flowchart LR
    S[Scenario] --> A[Agent]
    A --> T[Tools]
    T --> O[Observation]
    O --> E[Evidence]
    E --> D[Decision]
    D --> V[Validator]
    V --> SC[Scorer]
    GT[Evaluator-only Ground Truth] --> SC
    SC --> R[Scorecard]
    V --> P[Policy Gate]

这里最重要的安全边界是:Ground Truth 只在 Agent 返回结果后进入评测侧。Agent、工具、RAG 和诊断 报告都不能读取它。否则测到的就不是诊断能力,而是答案泄漏后的复述能力。

参与者主要输入它回答的问题失败时的安全行为
Agent场景、工具、RAG、已收集 Evidence“目前最可能的根因是什么?”证据不足时继续调查或停止,不猜答案
Validator候选 Decision 与其引用的 Evidence“这个结论是否有依据且结构完整?”不可用时只能安全降级,不能扩大恢复权限
Scorer运行 Artifact 与 evaluator-only Ground Truth“这次运行满足多少评测要求?”记录失败项,不反向影响 Agent 决策
Policy Gate已核验结论、风险和恢复策略“这项动作现在允许执行吗?”默认拒绝或转人工复核

这张表带来一个很实用的判断:诊断正确、核验可用、评分准确和恢复获批,是四件不同的事。

案例一:根因正确,Validator 与 Scorer 却各自失败

现象:结论正确,仍然丢了 11 分

这次真实 APY-013 运行开启了 RAG,知识库中有 30 个 ready/indexed 文档。Agent 用时 336,212 ms,得到 89 分;三个必要证据里程碑、独立正证据、根因 component/mechanism 和安全边界 全部通过。

如果只看最终分数,很容易得出一个错误假设:根因判断还不够准,应该继续调 Planner 或 Prompt。

但 Artifact 把扣分拆得很清楚:

  • trigger_wrongcausal_chain_incomplete 合计损失 6 分,确实属于 Decision 的语义表达问题;
  • observations_evaluated 损失 5 分,却不是少做了 Evidence Evaluation;
  • LLM Validator 记录 model_call_failed,但这也不等于候选根因错误。

根因一:Scorer 的分母混入了非诊断工具

本次执行了四个需要形成 Observation 的诊断工具,也完成了四次 Evidence Evaluation。此外还有一次 knowledge_retrieval。旧 Scorer 直接统计所有 completed tool calls,于是分母变成五;再拿四条 Observation 去比较,便判定“并非所有工具结果都被评估”。

问题出在指标定义:知识检索返回的是辅助诊断的参考内容,不是本场景中必须生成 Observation 的运行时 诊断工具。修复不是补造第五条 Observation,而是让 Scorer 只统计会产生诊断 Observation 的工具类别。

根因二:Validator 不可用和结论不可信被混成一件事

候选 Decision 已经通过十项公开的确定性检查,但 LLM Validator 调用失败。如果 Workflow 把“模型不可用” 解释成“证据不足”,它可能进入无关 Replan,重复调用工具,甚至把已经有依据的候选丢掉。

修复后的策略将两类状态分开:

  1. 确定性 Validator 检查 Evidence 归属、唯一 trigger、因果角色和引用完整性;
  2. LLM Validator 失败只记录允许列表内的阶段与错误类别,不保存原始异常或模型响应;
  3. 候选通过全部确定性检查时,保存为 deterministic_grounded_fallback
  4. fallback 强制进入 manual_review,Policy Gate 保持 executionPermitted=false

这不是用确定性规则“假装 LLM 通过”,而是在供应商不可用时保留一个已被公开证据支持的结论,同时明确 禁止自动恢复。最终审计能够同时回答:“我们认为根因成立吗?”以及“语义核验这次真的成功了吗?”

验证与边界

这次运行证明了安全降级闭环有效,但 89 分不应被包装成满分:trigger 和 causal chain 的语义缺口仍然存在。 同样,修正 Observation 分母也只是在修复评分准确性,不代表 Agent 能力突然提升。

这个案例的要点是:正确诊断、Validator 可用性和 Scorer 正确性必须分别验收。

案例二:修正评分口径后,为什么反而只剩 50 分

评分口径修正后,新 Run 的 observations_evaluated 果然变成了 5/5。但总分没有上升,反而降到了 50 分,失败项是 missing_root_cause_decision

这里还有一个很容易犯的复盘错误:仓库中有两次 50 分运行,它们暴露的是不同问题。把两次记录揉成一条 时间线,会得到一个从未真实发生过的失败故事。

第一次 50 分:有 Observation,却没有唯一 trigger

Run eval-528fbe19193743b18cb90fb6f1eaf0c7 中,三个必要证据、安全边界和工具预算都已通过, knowledge_retrieval 也不再占用诊断 Observation 配额。

真正的问题发生在 Observation → Evidence:模型把三条 Observation 全部标成了 mechanism,没有任何 一条承担 trigger 角色。没有唯一 trigger,就无法构造完整 Root Cause Decision。Decision 解析最终记录 invalid_model_output,Validator 因为根本没有候选而记录 candidate_missing——这次甚至没有调用 LLM Validator,因此不能拿它评价 Validator 供应商是否稳定。

系统也没有从 hypothesis 描述里猜一个 trigger。这个 fail-closed 行为很重要:50 分虽然难看,却比“补出” 一个没有证据绑定的满分答案可信。

第二次 50 分:角色正确了,状态却被越权改写

接下来的 Run eval-247b1751764e40c08fd9a7f0b4cde4f0 已经不是同一个问题。Planner 为步骤形成了 完整角色覆盖:PostgreSQL Error 对应 impact,Wait Graph 对应 mechanism,Resource Order 对应 trigger, Metrics 对应 context;前三条 Observation 也正确形成了 impact/mechanism/trigger。

但 Sufficiency 仍继续执行 Metrics。该步骤的模型输出越权 refute 了当前步骤没有测试的三个 hypothesis, 把之前的唯一支持假设一起抹掉。与此同时,Decision 的 structured call 又出现 model_call_failed/provider_4xx。所以这次 50 分包含两个独立故障:

  • Evidence Evaluation 修改了自己无权修改的 hypothesis 状态;
  • structured-output 方法与当前模型 capability 不兼容。

修复:让计划声明意图,让持久化状态拥有最终解释权

Planner 现在为每个步骤保存 causalIntent 和它实际测试的 testsHypotheses。Evidence Evaluation 只能在 这个集合内更新状态,不能因为一句模型总结就 refute 其他竞争假设:

{
  "causalIntent": "trigger",
  "testsHypotheses": ["hypothesis-from-current-plan-step"],
  "evidenceRole": "trigger"
}

Sufficiency 的 supportedrefutedunresolved 不再由模型重新宣布,而是从公开 hypothesis 全集与 持久化 Hypothesis State 确定性派生。模型仍可以建议缺失证据和下一步工具,但不能成为状态真相的唯一来源。

structured-output 也改为读取模型 capability profile:当前模型选择 json_mode,旧配置缺失该字段时才保留 function_calling 兼容。修复后的最小真实 readiness 使用合成公开事实,在 24.1 秒内完成;这只证明调用 合同可用,不等于那两次失败 Run 被“改成了成功”。历史 Artifact 保持不可变,后续真实验收必须产生新的 Run。

这个案例说明,分数下降有时不是新版本变差,而是评分器终于不再掩盖更深的能力缺口。先问哪一层开始 偏离证据,再决定是否修改 LangGraph、Tool Calling 或评分器。

案例三:Single 与 Multi 都是满分,应该选哪个?

如果 Single-Agent 和 Multi-Agent 都拿到 100 分,是否应该直接选择结构更“先进”的 Multi-Agent?

当前数据不支持这个结论。我们先在 Nginx timeout 场景执行一组配对 A/B,发现两种模式的结果完全相同; 随后又构造了同时需要 Runtime 状态与 CLS 生命周期日志的 Order Pool 场景。第二组正式 A/B 依然没有 拉开准确率,却明确暴露了时延和模型调用成本的差异。

实验一:Nginx 3×3 没有拉开差距

真实 campaign route-ab-20260820 固定了 Git 工作树、30 卡知识库、模型、CLS、工具白名单和评分器:

Nginx 真实 3×3SingleMulti
有效 Run3/33/3
平均总分100100
Root Cause Top-1100%100%
Evidence Recall100%100%
每次模型调用22
安全硬门禁3/3 通过3/3 通过

这组结果证明,在这个 Nginx timeout 场景和当前配置下,Multi 没有造成准确性、安全或模型调用量退化; 但 Root Cause Top-1 与 Evidence Recall 的增益都是 0 个百分点,因此也没有测出 Multi 的额外能力。

一种合理解释是:场景的主要因果链比较明确,两个模式都碰到了指标上限,产生了 Benchmark 天花板效应。 但这只是需要用更有区分度场景验证的假设,不能据此宣称“Single 足够处理所有 Nginx 故障”。

为什么还要构造第二个实验?

Nginx 场景的因果链较集中,没有充分检验多条独立调查路径能否带来新信息。为了提高区分度,第二个实验 改用 Order Pool:Runtime 侧要确认连接池容量、waiter、数据库可达性和锁等待;CLS 侧要还原 checkout、 更新失败、未归还连接与 acquire timeout 的事件窗口。两类证据共同支持同一个连接生命周期根因。

这比 Nginx 多出一个独立证据域,但仍不能预设 Multi 一定更好。“场景更复杂”在这里是实验设计假设, 不是已经被某个复杂度指标证明的事实。判断仍必须来自同配置、重复执行的配对结果。

实验二:Order Pool 正式 3×3 A/B

修复后的正式 campaign final-order-pool-ab-20260821-223939-f72cdb9 按 Single 01~03、Multi 01~03 顺序执行。六个 Run 均存在于 PostgreSQL 与外部 Evaluation Archive,规范化 Archive checksum 与 PostgreSQL artifact_checksum 逐项一致。

Order Pool 正式 3×3SingleMulti
有效 Run3/33/3
平均总分100100
Root Cause Top-1100%100%
Evidence Recall100%100%
平均时延211,298 ms360,215 ms
nearest-rank P95217,033 ms360,333 ms
每次模型调用37
每次工具调用55
每次持久化 Evidence88
验证与 cleanup3/3 通过3/3 通过

两种模式都正确完成根因诊断、Evidence Recall、验证与 cleanup;工具调用和持久化 Evidence 数量也相同。 当前实验没有观察到 Multi 的能力收益,却观察到明确成本:Single 平均时延低约 41%,P95 更低,每次模型 调用为 3 次而不是 7 次。

选择关键不是“难不难”,而是证据能否有效拆分

“简单问题用 Single,困难问题用 Multi”方向上有一定道理,却仍然太粗。一个问题即使很难,如果所有步骤 都依赖同一个数据库状态并且必须串行完成,拆成多个 Agent 也只会增加协调成本。反过来,一个看似不复杂的 问题,如果需要同时核对 Runtime、日志和变更记录,Multi 反而可能获得真正互补的 Evidence。

更稳妥的路由标准是证据复杂度和可分解性:

条件推荐模式原因
单一证据域、主要假设明确、步骤强依赖SingleMulti 无法形成有效并行,反而增加协调成本
多个数据源,但 Single 能在预算和 deadline 内完整闭环优先 Single当前没有证据证明拆分能提升能力
Runtime、日志、变更记录等证据域可以独立调查Multi 候选可以并行获得互补 Evidence
多个竞争根因需要不同数据域分别证伪Multi 候选Specialist 独立调查后由中央链路聚合
场景很难,但步骤必须串行或共享同一状态Single难度不等于可并行性
Multi 只会重复相同工具和 Evidence不升级没有新增信息,只增加调用和时延

难度可以作为路由信号,但不是充分条件。升级 Multi 前,至少应该确认存在两个可独立调查的证据域,并且 并行结果能增加新 Evidence、关闭竞争假设,或在 deadline 内完成 Single 无法完成的调查。

当前为什么选择 Single?

在两种模式准确率、Evidence Recall、工具调用、Evidence 数量和安全闭环相同的前提下,平均/P95 时延与 模型调用量成为决定因素。因此当前生产 auto 选择 Single。这是对现有 Benchmark 的工程决策,不是 “Single 普遍优于 Multi”的结论。

下一轮怎样真正验证 Multi?

下一轮更有区分度的测试,应构造同时涉及 Runtime、CLS 和变更记录的竞争根因,并加入部分缺失、相互冲突 或时间窗口错位的 Evidence。然后在同一 Git SHA、模型、RAG、CLS、工具和 Scorer 下,顺序执行 Single ×3 与 Multi ×3。

比较指标也不能只有总分,还应包含 Root Cause Top-1、Evidence Recall、独立来源组、重复与冲突 Evidence、 模型调用量、平均/P95 时延、安全闭环和 cleanup。只有 Multi 在不降低准确性、安全和稳定性的前提下产生 可重复的能力增益,才有理由扩大生产路由。

因此,当前综合准确率、时延与模型调用成本,生产继续使用 Single。现有满分数据证明 Multi-Agent 可以 完成闭环,但还没有证明它值得成为默认路径。

案例四:证据齐全,确定性规则为什么仍会误判

最后一个案例不涉及 LLM 解析失败,而是一个很典型的事件语义错误。

Order Pool 泄漏场景已经收集到四组独立工具 Evidence:连接池满、空闲连接为 0、存在 waiter、业务探针 超时;PostgreSQL 仍可达且没有锁等待。CLS 生命周期里也能看到未归还的 checkout、订单更新失败和后续 acquire timeout。

结果仍然是 rootCauseDecision=null,终态为 VALID_FAIL/recovery_denied

错误假设:只要出现过 checkin,就不存在连接泄漏

完整事件流并不只包含故障请求。在真正的泄漏之前,有一个正常请求已经完成:

正常请求:checkout → checkin
故障请求:checkout → order_update_failed → pool_acquire_timeout

旧 trusted lifecycle matcher 使用的是全局存在性规则:只要整段日志任意位置出现 connection_checkin,就把它当作泄漏假设的反证。于是,故障前已经闭合的正常请求误伤了后面的泄漏链。

这里不能靠删除正常日志、扩充同义词或要求模型忽略 checkin 来“过测”。真正需要修复的是事件相关性: 全局出现过某事件,不等于它发生在当前候选因果窗口内。

修复:先定位候选链,再判断窗口内是否归还

新的 matcher 先定位 checkout → order_update_failed → pool_acquire_timeout 候选窗口,只检查该候选 checkout 到 timeout 之间是否存在 checkin:

for checkout in checkouts:
    failure = first_event_after(checkout, "order_update_failed")
    timeout = first_event_after(failure, "pool_acquire_timeout")
    if failure and timeout and not exists_between("checkin", checkout, timeout):
        candidates.append((checkout, failure, timeout))

这段伪代码只解释窗口语义。真实实现还必须绑定请求与连接标识,处理乱序、重复事件和窗口上界;否则仍可能 把不同请求的事件错误拼接成一条链。窗口内出现 checkin 时,规则继续 fail closed,不形成泄漏结论。

规则判断正确后,语义投影还可能继续丢分

matcher 修复后的下一次真实 Single 已形成 deterministic_grounded Decision,component、mechanism、 Evidence、恢复和安全策略都正确,Evidence Recall 也是 100%,但原始总分仍只有 90。原因是确定性投影只 罗列“看到哪些事件、池处于什么状态”,没有明确表达:异常路径没有释放连接,连接池因此耗尽,新订单等待 连接并最终超时。

这一次修复没有增加场景关键词,而是把已有 Evidence 按通用因果角色投影为 trigger、mechanism 和 impact, 并要求 mechanism、impact 都引用实际 Evidence。Ground Truth、同义词表、评分阈值和恢复权限都没有改动。

最终真实 Single order-pool-specialist-single-gate-semantic-fixed-20260821 达到 100/100 VALID_PASS:Root Cause Top-1、Evidence Recall、恢复验证、cleanup 和安全硬门禁全部通过; 诊断任务持久化了八条 Evidence、四个独立 sourceFingerprint,没有重复 Evidence。

这里仍有一条安全边界:生产 Recovery Policy 保持 manual_review_requiredexecutionPermitted=false。运行中的恢复由隔离 Live Benchmark harness 按场景合同执行,不代表生产 Agent 获得了自动执行权限。

这个案例的结论是:确定性规则也需要像模型一样接受反例和时序边界测试;“不用 LLM”不等于“不会误判”。

一套可以复用的六步排查法

遇到“看起来答对却没通过”的 Agent Benchmark,可以按下面的顺序排查。顺序很重要:越靠前的问题,越可能 让后面的分数和耗时失去解释意义。

第一步:冻结失败 Run

保存 terminal Artifact、Checkpoint、工具审计、模型调用状态和 checksum,给失败运行一个不可变 identity。 不要覆盖结果,也不要只保留最好的一次重跑。没有失败历史,就无法区分修复、波动和选择性汇报。

第二步:先检查运行是否有效

确认故障是否真的注入、依赖是否 ready、工具是否完成、参数是否绑定到当前 owner/Run,以及 cleanup 是否 执行。基础设施失败应标记为 infra invalid,而不是算成 Agent 诊断失败;同样,提前退出的低时延不能进入 性能收益比较。

第三步:逐层检查 Observation、Evidence 与 Decision

对每次工具调用追问三个问题:工具返回了什么 Observation?它是否经过 Evidence Evaluation?它支持或反驳 的是哪个当前步骤允许测试的 hypothesis?然后检查 Decision 引用的 Evidence 是否真的覆盖 trigger、 mechanism 和 impact。不要从最终答案反推中间链路“应该正确”。

第四步:单独检查 Validator

把“语义拒绝候选”与“调用失败”分开。记录安全的阶段、错误类别、Schema 解析结果、尝试次数和 deadline; 不要持久化原始模型响应或异常正文。检查 structured-output 方法是否符合模型 capability,以及剩余 deadline 是否足以支持格式纠正重试。

第五步:逐项核对 Scorer

检查每项分数的分母、过滤条件、语义匹配和 hard gate。尤其注意:知识检索是否被误算作诊断 Observation、 无序事件是否被当作有序因果链、全局事件是否误伤候选窗口。不要先降阈值,要先用正例、同义正确例、缺环例、 乱序例和错误机制例复现规则边界。

第六步:离线回归后只跑新的真实 canary

先用合成公开事实验证 Parser、状态转换、matcher 和 Scorer,再使用相同模型、RAG、工具和评分器执行一个新的 真实 canary。新结果必须使用新 Run ID,失败就保存并停止。一次 canary 只证明该配置下的可用性,扩大到 3×3 A/B、默认路由或生产恢复权限需要独立门禁。

总结:让整条链路对同一事实达成一致

“Agent 答对了,Benchmark 为什么仍失败”没有唯一答案。失败可能发生在 Planner、Tool Calling、 Observation → Evidence、Decision、Validator、Scorer,也可能只是供应商或运行时没有完成有效调用。

这次调优最重要的收获不是把某个场景从 89、50 或 90 提到 100,而是建立了一条可以逐层审计的解释链:

工具调用可追踪
  → Observation 有来源
  → Evidence 有角色和 hypothesis 归属
  → Decision 有完整因果链
  → Validator 区分语义拒绝与调用失败
  → Scorer 使用正确口径
  → Policy Gate 独立控制恢复权限

可信的 Benchmark 调优,不是让 Agent 更像标准答案,而是让工具观测、Evidence、Decision、Validator 和 Scorer 对同一事实形成一致、可审计的解释,同时保留模型失败、证据不足和高风险恢复时的安全边界。

当下一次分数异常时,先别急着改 Prompt。先找到证据链从哪一层开始断裂——那里才是真正值得修复的地方。

陈涛 · Agent Application Developer

杭州 · 2026