指标仪表盘雏形
Day 43 我们画了指标树(NSM=task 自主完成率 + 叶子指标),Day 46/48 我们(待 key 后)跑出了真实的 A/B 报告。
阶段: B5 · tool/context engineering + 指标树(Day 41-50) 标签: #dashboard #live-data #metric-tree #evals-ops
今日导引(由浅入深)
Day 43 我们画了指标树(NSM=task 自主完成率 + 叶子指标),Day 46/48 我们(待 key 后)跑出了真实的 A/B 报告。
今天(Day 49)在 B1→B18 曲线上是把这两条线缝合的一步:写一个吃活数据的聚合脚本,从 agent-evals/reports/ 的真实跑批报告里读数,映射回指标树的 NSM + 叶子,输出 JSON + 简图。
关键纪律是「live,不硬编码」——仪表盘要么显示真实数据,要么诚实降级显示「还没有报告」,绝不编造好看的 demo 数字。
明天(Day 50)的 Block 收口会把这个仪表盘连同 Δ、context-budget 表一起定稿。
今天的最小可判定产出:聚合脚本跑通,对缺数据诚实降级出占位 JSON/chart(脚本待建,活数据待 key)。
1. 机理精读
仪表盘的第一性原理:它是数据源的视图,不是装饰。
一个仪表盘只有在它如实反映底层 eval 报告时才有价值。
最常见的反模式是「先做个好看的图,里面填几个 demo 数字」——这种仪表盘在评审时一戳就破,因为数字无法溯源到任何真实跑批。
本项目的硬约束是:仪表盘必须从 agent-evals/reports/ 下的真实报告 JSON 读数,每个展示值都能追到某次具体跑批。
Anthropic《Demystifying evals》(2026-01) 把 eval 报告本身当作数据源(eval 报告即 data source),仪表盘只是它的聚合呈现层。
诚实降级(graceful degradation)优于编造。
当 reports/ 为空(还没跑过真实 eval,比如缺 key 时),正确行为不是填占位假数据,而是显式标注「无数据」。
本仓 buildDashboard([]) 在 reports 为空时返回 nsm.value = null 并带 note: 'no eval reports yet — run pnpm eval:agent (needs an API key) to populate live data'。
这把「缺数据」这个事实本身变成仪表盘的一部分,而不是用假数据掩盖它——这正是这套笔记的诚信底线在工程上的体现。
指标树到仪表盘的映射关系。 Day 43 的指标树是抽象骨架,仪表盘是它的实例化:
- 根节点 NSM「task 自主完成率」对应
completionRate的均值; - 叶子指标对应
partialCreditMean(部分得分均值)、codePassRate(代码检查通过率)、unknownRate(判 unknown 比例)、totalCostUsd(单次运行成本)、judgeHumanKappa(判分一致性,连回 Day 47)。 每个叶子都映射到 eval 报告里一个真实存在的字段,不引入任何报告里没有的派生臆造量。
纯函数核 + fs 边界分离。
仪表盘逻辑被刻意拆成两层:纯聚合(src/agent/eval/dashboard.ts 的 buildDashboard,只吃 reports 数组、不碰文件系统)和 IO 读取(scripts/build-dashboard.ts,负责 readdir/读 JSON)。
这样纯逻辑可单测、可在浏览器/bundler 环境复用,而文件读取留在脚本侧。
这是本仓一贯的「pure core / impure shell」分层,保证仪表盘逻辑本身离线可测。
边界:仪表盘不产生新结论,只聚合既有报告。
仪表盘的 NSM/叶子值全是对 reports 的算术聚合(均值/求和),它不做统计推断、不算 CI——那是 abCompare/cohensKappa 的职责。
仪表盘的价值在「一眼看全」,不在「证明什么」。
把推断和展示分清,避免仪表盘里出现没有 CI 的裸数字被误当结论。
与「指标树」的承接关系。
Day 43 的指标树回答「该测什么」(NSM + 叶子的语义结构),今天的仪表盘回答「测出来的数怎么活着呈现」。
两者是定义层与呈现层的关系:指标树一旦改了(比如新增一个叶子「平均步数」),仪表盘的 leaves 数组就要同步加一行,且必须能在报告 JSON 里找到对应字段——否则就是「定义了却测不到」的空指标。
这也是为什么 EvalReportLike 的字段集要和指标树叶子一一对齐:每个展示叶子背后都有一个被 runTaskEval 真实写出的报告字段。这种「指标树 ↔ 报告 schema ↔ 仪表盘叶子」的三方对齐,是 evals-ops 不流于装饰的关键约束。
2. 代码走读:dashboard.ts + build-dashboard.ts
buildDashboard(reports)(src/agent/eval/dashboard.ts,纯函数、无 key):入参EvalReportLike[],每份含n/completionRate/partialCreditMean/unknownRate/codePassRate/totalCostUsd/judgeHumanKappa。- 空报告降级:
if (reports.length === 0)直接返回runs:0, totalTasks:0, nsm.value:null,并写note: 'no eval reports yet — run pnpm eval:agent (needs an API key) to populate live data'——这就是 seed 说「诚实降级而非编造」的代码落点。 - 类型安全的取数:内部
num(sel)把reports.map(sel)过滤成纯 number 数组(filter((x): x is number => typeof x === 'number')),跳过缺失字段而不报错。 - NSM:
nsm: { name: 'task autonomous completion', value: avg(num(r => r.completionRate)), unit: 'rate' }——根节点取所有报告 completionRate 的均值。 - totalTasks:
num(r => r.n).reduce((s,x)=>s+x, 0)把每份报告的任务数求和。 - 叶子:
leaves数组依次给出 partial-credit mean、code-check pass(rate)、unknown rate、cost (USD/run)、judge-human kappa(从r.judgeHumanKappa?.kappa收集后取均值)——精确对应指标树叶子。 avg(xs):空数组返回null(不是 0),让「无数据」和「真值为 0」可区分。- IO 侧
scripts/build-dashboard.ts:readdirSync(dir)过滤.json、逐个JSON.parse(解析失败的报告return null后过滤掉,容错),喂给buildDashboard,写agent-evals/dashboard.json并打印;if (snap.runs === 0)时额外提示「no reports yet — run pnpm eval:agent first」。
两种输出对照:降级 vs 活数据
空 reports(缺 key)时 buildDashboard([]) 的输出形态:
{
"runs": 0,
"totalTasks": 0,
"nsm": { "name": "task autonomous completion", "value": null, "unit": "rate" },
"leaves": [],
"note": "no eval reports yet — run `pnpm eval:agent` (needs an API key) to populate live data"
}
注意 nsm.value 是 null 不是 0,leaves 是空数组——仪表盘据此渲染「暂无数据」而非一条 0% 的假曲线。
喂入 N 份真实报告后(待 key),同一函数会把 nsm.value 填成各报告 completionRate 的均值、leaves 填齐 5 个叶子。关键是:这两种输出走的是同一段纯函数代码,只因输入不同而分叉——没有任何「演示模式」开关去伪造数据。这正是「live-data,不硬编码」在结构上的保证:你想造假都没有专门的造假入口。
3. 今日实战
- 实现/确认聚合脚本
scripts/build-dashboard.ts读agent-evals/reports/*.json→ 调buildDashboard→ 写agent-evals/dashboard.json(本仓已具雏形,本日按指标树补齐叶子映射)。 - 离线先验证降级路径:
reports/为空时跑pnpm dashboard,确认输出nsm.value=null+ 「no reports yet」提示。 - 用离线 fixture 报告(含 completionRate/partialCreditMean 等字段)跑一遍,确认 NSM + 5 个叶子都正确聚合、chart/JSON 成形。
- 可叠加现有真实 token 基线(tokenizer 的 1760 tokens)作输入层占位指标,标注其为离线测量、非跑批活数据。
- 待 Day46/48 真实跑批产出 reports 后,重跑脚本把占位替换为活数据。
4. 今日实测 / 产出
- 脚本 「待建」(本仓有雏形,本日按指标树对齐叶子映射并补降级路径)。
- 输出依赖 Day46/48 真实跑批,故 「待跑(需 OPENROUTER_API_KEY 先产报告)」。
- 可先用现有 tokenizer 真实数 (1760 tokens)+ 离线 fixture 报告 跑通脚本出占位 chart/JSON,待 key 后替换为活数据。
- 本日不把占位 demo 数字写成实测,不把待建/待跑升级成已完成。
5. 常见误区 / 陷阱
- 硬编码 demo 数字冒充实测:seed 点名的头号反模式;缺数据要降级显示「无报告」。
- 空数组聚合返回 0 而非 null:会让「无数据」和「真值 0」无法区分;
avg([])必须返回 null。 - 在仪表盘里塞没有 CI 的裸 Δ 当结论:展示层不做推断,CI 归
abCompare。 - 解析失败的报告让脚本崩:IO 侧要 try/catch 跳过坏 JSON(本仓已
return null容错)。
6. 学习资源(每条带 YYYY-MM)
- Anthropic, Demystifying evals — 2026-01(eval 报告即数据源、live-data 实践)。
- Langfuse 文档(evals/observability 仪表盘实践)— 2026。
- Braintrust 文档(eval 报告聚合与可视化)— 2026。
- 本仓代码:
src/agent/eval/dashboard.ts、scripts/build-dashboard.ts— 2026-06。
SOTA检查 (2026-06 更新)
- 当前主流:吃活数据(live-data)的 evals-ops 仪表盘(Langfuse / Braintrust 类)是 2026-06 当前实践,仪表盘从真实跑批报告读数而非硬编码。
- 是否仍 SOTA:是。pure core + 报告即数据源 + 诚实降级的组合无过时风险。
- 过时黑名单:硬编码 demo 数字冒充实测;空数据用假数填充;在展示层混入无 CI 的推断结论。
- 下次复查点:Day46/48 真实报告就绪后,重跑脚本把占位换活数据;关注是否需接 Langfuse/OTel 做持续聚合。
衔接
- 昨天:Day 48 — 跨模型稳健性,产出 Qwen3 复核报告。
- 今天:写吃活数据的聚合脚本,把指标树 NSM + 叶子映射到真实跑批报告,诚实降级。
- 明天:Day 50 — Block 收口与归因,汇总 Δ + budget 表 + 仪表盘定稿 tool/context 三件套。