B18 · Day 176OSS 收口 + 英文 + 全局 SOTA 复核
抽独立公开 repo 工程化
B18 是整个 AICAP-180 的收口批次。回顾整条曲线:
阶段: B18 · OSS 收口 + 英文 + 全局 SOTA 复核(Day 171-180) 标签: #monorepo-extraction #vitest #github-actions #deterministic-tests
今日导引(由浅入深)
B18 是整个 AICAP-180 的收口批次。回顾整条曲线:
- B1→B17 把「评测严谨性」(κ harness、partial-credit、bootstrap CI、A/B、outcome 仪表盘)一砖一瓦造进了主仓 monorepo;
- 但这些资产至今只活在一个私有大仓里——对外不可链接、不可独立验证,这正是 B18「OSS 收口」要解决的;
- 今天要做的就是把其中最有外部价值的闭包——
agent-evals——从 monorepo 切出来成为独立公开 repo,配上自己的 CI; - 目标是让「423 测试绿」里属于评测的那一部分变成一个 GitHub 上独立可点开、可 fork、可
npm test复跑的工件。
承上启下:昨天(Day 175)写的是这个包的英文 README.en.md 草稿与 κ-harness 数据流图,今天是把文字承诺兑现成可运行的 repo;明天(Day 177)则转向真实 OSS PR 实现。今日最小可判定产出:一个新 repo,CI 跑 npm test 绿,passing 测试数 ≥ 18。
1. 机理精读
monorepo → 独立 repo 的本质是依赖闭包的切割。
- 一个文件能否被「拎出去」,取决于它的传递依赖(transitive imports)是否构成一个不引回主仓其余部分的最小闭包。
- 如果
cohensKappa.ts只 import 了纯标准库 / 同目录的stats.ts,那它就是可切的。 - 如果它 import 了
../aml/typology或某个带 React/Vite 的模块,就会把整棵依赖树拖出来,闭包不再「最小」。 - 识别最小闭包是这次工程化的第一步,也是最关键的一步——下面第 2 节就是逐文件画这张依赖图。
为什么 agent-evals 是天然的切割候选?
- B1-B17 在设计这批评测代码时就刻意走了「纯 TS + 确定性 + RNG 可注入」的路线(见
stats.ts头注:Pure + deterministic (RNG injectable) — no API key)。 - 这意味着它们没有网络、没有时钟、没有真实模型 key 依赖——这正是可独立分发的前提。
- 反例:一个需要
DEEPSEEK_API_KEY才能跑测试的包,在别人(或自己的公开 CI)的环境里是跑不绿的,等于不可分发。
独立包的最小骨架。 seed 明确列出 agent-evals 独立包需自带:
cohensKappa.ts(judge-human 一致性)+tasks.ts(任务集);- labels schema(标注数据的 JSON schema)——定义
agent-evals/labels.json里手标数据的形状(哪个 taskId、人类给的 label、判官给的 label),是 κ 计算的输入契约; - 各自的
tsconfig/vitest配置 + CI。 - 一句话:切出去的不只是源码,还有一整套「让它自洽运行」的脚手架。
关键权衡:纯确定性 vs 真实信号。
- 切出去的包能在 CI 里跑绿,恰恰因为它不包含真模型回合——
tasks.ts里每个任务的codeCheck是确定性正则/JSON 断言,不需要 LLM。 - 代价是:CI 绿只证明「评测脚手架本身正确」,不证明「某个模型在这套任务上表现如何」。
- 后者要
pnpm eval:agent(需 key)才产出,那部分绝不能进 CI,否则 CI 不可重放。 - 这与本仓 dsdb-lab「浏览器内确定性教学装置」是同一条工程哲学:把可确定性测试的部分和需要外部资源的部分严格分层。
与相邻概念的边界。
- 今天做的是「打包/分发工程」,不是「写新评测逻辑」——逻辑早在 B1-B17 写完且已测。
- 也不是「发布 npm 包」(那是另一步,涉及版本号 / registry / 语义化版本)。
- 今天的判定边界就一条:新 repo 的
npm test在 GitHub Actions 上绿,且 passing ≥ 18。
2. 代码走读:可抽出的纯 TS 闭包
seed 点名 stats.ts(summarizeLatency p50/p95/p99)、abCompare.ts、dashboard.ts 均纯 TS 可测,构成可抽出闭包。逐一走读其真实符号与依赖边界(已 Read 三个文件):
src/agent/eval/stats.ts— 零外部 import。导出mean/sd/percentile/pairedBootstrap(返回PairedResult{deltaMean, ci95, n, iters})/requiredNForDelta(正态近似的功效分析n = ((z_{α/2}+z_β)·sd/|δ|)²,zAlpha=1.959963985、zBeta=0.841621234)/summarizeLatency(返回{n,p50,p95,p99,mean})。无依赖 → 闭包根节点,第一个该拷的文件。src/agent/eval/abCompare.ts— 只import { pairedBootstrap } from './stats'。导出abCompare(a, b),内部passMap把perTask[].judge.label==='pass'折成 0/1,按taskId对齐(ids = [...ma.keys()].filter(id => mb.has(id)).sort()),再pairedBootstrap出deltaMean/ci95,并统计wins/losses/ties。依赖只回到 stats.ts,仍在闭包内。src/agent/eval/cohensKappa.ts— 零外部 import(自带percentile)。导出cohensKappa(a, b)(返回{kappa, po, pe},退化边界pe===1时po===1?1:0)与cohensKappaWithCI(百分位 bootstrap,B=2000,RNG 可注入)。头注直接写明用途:calibrate an LLM-judge against human gold labels,且small N => wide CI — the reason P1 wants N>=50。闭包内。src/agent/eval/dashboard.ts— 零外部 import。导出buildDashboard(reports)聚合成DashboardSnapshot{runs, totalTasks, nsm, leaves},NSM=task autonomous completion,叶子含 partial-credit / code-check pass / unknown rate / cost(USD/run) / judge-human kappa。注释强调「fs reader lives in scripts/build-dashboard.ts so this stays bundler-safe」——纯函数与 IO 已分层,切包时 fs 部分留主仓即可。src/agent/eval/tasks.ts—import type { EvalTask } from './agentEval'(仅类型导入)。导出EVAL_TASKS([...AML_DETECT, ...AML_RESTRAINT, ...AML_COMPLIANCE, ...AGENT_CORE],约 30 个任务)与tasksByCategory()。每个codeCheck是确定性断言。注意闭包边界:要么把agentEval.ts的EvalTask类型一并拷入,要么在独立包里就地定义该接口——这是切割时唯一需要处理的类型耦合。
走读结论与依赖图:
stats.ts—— 闭包根(零依赖)。abCompare.ts→stats.ts(pairedBootstrap)。cohensKappa.ts—— 自包含(自带percentile)。dashboard.ts—— 自包含(纯聚合,fs reader 留在scripts/build-dashboard.ts)。tasks.ts→agentEval.ts(仅import type)—— 唯一需手动处理的耦合。- 结论:五文件构成一个干净的纯函数闭包,切割成本只有「就地定义
EvalTask类型」一处。
3. 今日实战
- 新建独立 repo(如
agent-evals),git init,配package.json("test": "vitest run")。 - 画依赖图:从
stats.ts(闭包根,零依赖)出发,确认abCompare → stats、cohensKappa(自包含)、dashboard(自包含)、tasks → agentEval(仅类型)的边,标出唯一需处理的耦合点。 - 拷入纯 TS 闭包:
src/agent/eval/下的stats.ts/abCompare.ts/cohensKappa.ts/dashboard.ts/tasks.ts,连同它们各自的*.test.ts。 - 处理
tasks.ts的EvalTask类型:把agentEval.ts里的EvalTask接口就地拷成一个最小types.ts(只留打包需要的字段),避免拖入整个 agentEval 运行时。 - 加 labels schema:定义
labels.json的 JSON schema(taskId / humanLabel / judgeLabel),供cohensKappa消费。 - 配
tsconfig.json+vitest.config.ts(strict、ESM、node 环境)。 - 配
.github/workflows/ci.yml:npm ci && npm test,不注入任何模型 key,确保 CI 纯确定性可重放。 - push 到 GitHub,确认 Actions 绿,统计 passing 测试数,与主仓 423 测试中归属 eval 的子集对齐。
4. 今日实测 / 产出
- 新 repo CI 绿 + 测试通过数:外部动作 + 待 CI(里程碑要求 ≥18 passing)。seed 状态为「外部动作 + 待 CI → 将产出新 repo CI 绿 + 测试通过数」,未完成,不升级为已完成。
- 本仓侧已有(已落地、可立即引用的硬资产):
stats.ts的summarizeLatency(p50/p95/p99)、abCompare.ts、dashboard.ts均纯 TS 可测,构成可抽出闭包;主仓 423 测试绿。 - Block 里程碑参照:agent-evals 抽独立公开 repo = 待 CI(目标 ≥18 tests 绿,纯 TS 闭包已就绪:
cohensKappa.ts/tasks.ts/stats.ts/abCompare.ts)。
5. 常见误区 / 陷阱
- 把需 key 的测试塞进 CI:一旦 CI 跑真模型回合,结果随机、随网络波动,CI 不再可重放、PR 红绿失去意义。坚持「纯确定性单测进 CI,真模型 eval 留本地/手动」。
- 闭包没切干净:拷
tasks.ts时漏处理EvalTask类型导入,编译期就会把agentEval.ts(及其传递依赖)拖进来,独立包失去「最小」属性。先画依赖图再动手。 - README 数字与 CI 脱节:README 里引用的 423 / V4-Pro 89.7% 等数字属于主仓,独立 repo 的 CI 只覆盖评测脚手架本身。
- 不要在独立 repo 里声称「423 tests 绿」,那是主仓数;独立 repo 只能声称它自己的 passing 数(≥18 目标)。
- 把抽包当成「已交付」:seed 明确这是外部动作 + 待 CI,没合 CI 绿之前不算里程碑达成。
- 拷源码忘拷测试:只拷
stats.ts不拷stats.test.ts,独立 repo 的 CI 就成了空跑——passing 数上不去,也证明不了脚手架正确。
6. 学习资源(每条带 YYYY-MM)
- Vitest 官方文档(migration / config / CI 集成),2026-01 当周版本需复验。
- GitHub Actions for Node.js(
actions/setup-node+npm ci)官方指南,2026。 - TypeScript Project References / monorepo 拆分实践(
tsconfigreferences),TS 官方 handbook,2026。 - 本仓 monorepo 结构 + vitest 测试体系(seed 依据)。
- 本仓
src/agent/eval/下stats.ts/abCompare.ts/cohensKappa.ts/dashboard.ts/tasks.ts(待抽出的纯 TS 闭包,本日已走读)。 - Anthropic「Demystifying evals」(2026-01)——评测脚手架可复现性的方法论背书(被 B17/Day 175 引用,本日承接其「可独立验证」精神)。
SOTA检查 (2026-06 更新)
- vitest + GitHub Actions 为 2026 TS 项目标准 CI 栈,仍 SOTA。vitest 当周版本号需在 npm 复验。
- 当前主流栈:vitest(测试 runner)+
tsc --noEmit(类型门)+ GitHub Actions(CI)+ Conventional Commits / changesets(版本管理),仍是 2026 TS 库的默认组合。 - 过时黑名单:
- 避免引入需真模型 key 的测试进 CI(会使 CI 不可重放)——保持纯确定性单测,与本仓 dsdb-lab「浏览器内确定性教学装置」同理;
- 避免用已进入维护模式的旧测试框架配置(如纯 mocha+chai 手搓 ESM)做新库脚手架。
- 下次复查点:
- 抽包后 CI 首次跑绿时,复核 passing 数是否 ≥18;
- npm 上 vitest / @vitest/* 当周版本号;
- GitHub Actions runner 镜像 node 版本。
衔接
- 昨天:Day 175 — 英文 writeup 体例(problem→repro→fix→eval,写出 agent-evals README.en.md + κ-harness 数据流图)
- 今天:把 agent-evals 纯 TS 闭包切成独立公开 repo,配确定性 CI,目标 ≥18 passing
- 明天:Day 177 — 真实 OSS PR 实现(针对 D174 选定 issue 设计 fix + 单测,push fork branch)