这个包是什么What this package ispi-evals 是一个私有包,用来回答一个具体问题:pi 自带的文档到底有没有帮模型把事做对?它用 vitest-evals 写行为评测,主角是“文档提升”评测:同一个用例在两个只差文档的 Docker 镜像里各跑一遍(without_docs 是对照组,with_docs 是实验组),每次都是一个新容器、一个真实的 AgentSession,模型的工具在 UID 65532 下运行、读不到评分器和夹具;最后成对比较通过率,任何一对不完整就不公布结果。另有两个直接在本机跑的 host 评测(冒烟、文档审计)。pi-evals is a private package that answers one concrete question: does pi's bundled documentation actually help a model get things right? It writes behavioural evals with vitest-evals, and its centrepiece is the documentation-lift eval: each case runs in two Docker images that differ only in the docs (without_docs is the control, with_docs the treatment), every time in a fresh container with a real AgentSession, the model's tools running as UID 65532 unable to read judges or fixtures; the arms are then compared in pairs, and nothing is published if any pair is incomplete. Two host evals (smoke, documentation audit) run directly on the machine.
1,446 行 TypeScript 运行器(src/ 的 5 个文件)、7 个评测文件与 2 个夹具(evals/)、一个 5 阶段的 Dockerfile 与容器入口(docker/)。private: true,没有 exports,不发布到 npm;通过 npm run eval -w packages/evals 使用。被测对象是 pi-coding-agent(容器里用 dist/,本机通过别名指向源码),用到 pi-ai 的 InMemoryCredentialStore。包没有 CHANGELOG。A 1,446-line TypeScript runner (5 files in src/), 7 eval files and 2 fixtures (evals/), and a 5-stage Dockerfile plus container entrypoint (docker/). private: true, no exports, never published to npm; you use it through npm run eval -w packages/evals. The subject under test is pi-coding-agent (from dist/ in containers, aliased to source on the host), and it uses pi-ai's InMemoryCredentialStore. The package has no CHANGELOG.
运行器、评测定义与镜像:三个目录Runner, eval definitions and image: three directories#
一句话结论Takeawaysrc/ 是运行器:cli.ts 编排一次对比,docker.ts 构建两个镜像并运行一侧,plan.ts 展开任务,report.ts 读 Vitest JSON、配对、算提升,harness.ts 是 vitest-evals 适配器。evals/ 放评测定义和夹具;docker/ 放镜像构建文件。package.json 只有五个脚本:test、eval:host、eval:docs、eval 与 clean。src/ is the runner: cli.ts orchestrates one comparison, docker.ts builds the two images and runs one arm, plan.ts expands tasks, report.ts reads Vitest JSON, pairs arms and computes lift, and harness.ts is the vitest-evals adapter. evals/ holds eval definitions and fixtures; docker/ holds the image build files. package.json has just five scripts: test, eval:host, eval:docs, eval and clean.
要点Key points
*.docs.eval.ts是文档提升评测,由eval:docs在容器里成对运行;其余*.eval.ts是普通的 host 评测,由eval:host在本机运行。*.docs.eval.tsfiles are documentation-lift evals, run in pairs in containers byeval:docs; other*.eval.tsfiles are ordinary host evals run on the machine byeval:host.- 评测文件只依赖
harness.ts的两个工厂:createPiCodingAgentHarness()(host)与createPiDocumentationEvalHarness()(docs)。Eval files depend only on two factories inharness.ts:createPiCodingAgentHarness()(host) andcreatePiDocumentationEvalHarness()(docs). harness.ts只从report.ts拿一个常量PI_SESSION_SNAPSHOT_ARTIFACT:会话快照随结果一起写进 Vitest JSON。harness.tstakes just one constant fromreport.ts,PI_SESSION_SNAPSHOT_ARTIFACT: the session snapshot travels with the result inside the Vitest JSON.
工作原理How it works
- 全部评测:
PI_PROVIDER=… PI_MODEL=… npm run eval -w packages/evals(先 host,再 docs)。Everything:PI_PROVIDER=… PI_MODEL=… npm run eval -w packages/evals(host first, then docs). - 只跑 host:
npm run eval:host -w packages/evals;--之后的参数只传给eval:docs。Host only:npm run eval:host -w packages/evals; flags after--go toeval:docsonly. - 运行器自身的单元测试:
npm test -w packages/evals(test/ 下 6 个文件)。The runner's own unit tests:npm test -w packages/evals(6 files under test/).
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/src/cli.tsL131 | cli.ts | 编排一次对比orchestrates a comparison |
| packages/evals/src/docker.tsL38 | buildImages | 两个镜像the two images |
| packages/evals/src/harness.tsL471 | createPiCodingAgentHarness | vitest-evals 适配器the vitest-evals adapter |
| packages/evals/package.jsonL4 | private | 不发布not published |
| packages/evals/vitest.evals.config.tsL15 | projects | docs / host 两个项目docs / host projects |
公共 APIPublic API#
一句话结论Takeawaypi-evals 是私有包(private: true),没有 exports,不能被别的包导入;它的“API”是 5 个 npm 脚本。src/ 的 5 个模块之间共有 26 个值导出和 17 个类型导出,只给评测文件与运行器内部使用;评测文件只用 createPiCodingAgentHarness、createPiDocumentationEvalHarness 与 DOCUMENTATION_EVAL_TOOLS。pi-evals is a private package (private: true) with no exports, so nothing else can import it; its "API" is five npm scripts. Between them the 5 modules in src/ have 26 value exports and 17 type exports, used only by eval files and the runner itself; eval files use just createPiCodingAgentHarness, createPiDocumentationEvalHarness and DOCUMENTATION_EVAL_TOOLS.
npm 脚本npm scripts
| 脚本Script | 命令Command | 用途Use |
|---|---|---|
npm run clean | shx rm -rf .eval | 删除 .eval/ 下的产物delete the artifacts under .eval/ |
npm run eval | npm run eval:host && npm run eval:docs -- | 先 eval:host,再 eval:docs;-- 之后的参数只给 eval:docseval:host, then eval:docs; flags after -- go to eval:docs only |
npm run eval:host | vitest run --config vitest.evals.config.ts --project host | 在本机跑 host 评测run host evals on this machine |
npm run eval:docs | node --experimental-strip-types src/cli.ts | 在两个容器变体里成对运行 docs 评测并报告提升run docs evals in pairs in both container variants and report lift |
npm run test | vitest run --config vitest.test.config.ts | 运行器自身的单元测试(vitest.test.config.ts)the runner's own unit tests (vitest.test.config.ts) |
参数与环境变量Flags and environment
| 参数 / 环境变量Flag / environment | 作用Effect |
|---|---|
PI_PROVIDER / PI_MODEL | 被测模型(也可用 --provider / --model)the model under test (or --provider / --model) |
PI_EVAL_RUNS_PER_VARIANT | 每种变体的重复次数(或 --runs-per-variant)repetitions per variant (or --runs-per-variant) |
-t / --testNamePattern | 按名字选用例select cases by name |
PI_EVAL_VARIANT | 镜像里设置:without_docs / with_docsset in the image: without_docs / with_docs |
PI_EVAL_CONTAINER · PI_EVAL_SANDBOX_UID/GID | 容器入口设置;docs 评测缺了就拒绝运行set by the container; docs evals refuse to run without them |
src/ 各模块的导出(逐行链接)Exports of each src/ module (line-linked)
由构建脚本扫描 src/ 下每个模块的 export 声明生成;每个符号链接到 v1.1.0 中的定义行。Generated at build time by scanning each src/ module's export declarations; each symbol links to its defining line at v1.1.0.
7 个评测:2 个 host,5 个文档提升Seven evals: two host, five documentation-lift#
一句话结论Takeawayhost 评测是普通的 vitest-evals 套件:smoke 不给工具,只问一个固定答案;documentation-audit 用 it.for 遍历 coding-agent 的 40 页文档,让模型只读核对实现并提交一个结构化结论。5 个 docs 评测都是“改配置/写扩展 → reload → 检查结果”的真实任务,由 StructuredOutputJudge、ToolCallJudge 或自定义 createJudge 评分,judgeThreshold: null 让低分成为数据而不是失败。Host evals are ordinary vitest-evals suites: smoke gives no tools and asks for one fixed answer; documentation-audit uses it.for over coding-agent's 40 doc pages, having the model check the implementation read-only and submit a structured verdict. The five docs evals are real tasks — change config or write an extension → reload → inspect the result — scored by StructuredOutputJudge, ToolCallJudge or a custom createJudge; judgeThreshold: null makes a low score data rather than a failure.
要点Key points
- 每个 docs 用例的输入都是
[prompt, reload]两步:模型改完配置后,harness 调session.reload(),评分器再从运行时里读出真实状态。Every docs case takes two steps,[prompt, reload]: after the model edits config the harness callssession.reload(), and the judge reads the real state back from the runtime. acme-server.ts是本地的 OpenAI 兼容 / NDJSON 流式夹具服务器,供 provider 类用例调用;configured-runtime.ts把运行时状态整理成评分器要比较的结构。acme-server.tsis a local OpenAI-compatible / NDJSON streaming fixture server for the provider cases;configured-runtime.tsshapes runtime state into what judges compare.vitest.evals.config.ts分docs与host两个项目:串行、单 worker、5 分钟超时;容器外它把 coding-agent 别名到源码。vitest.evals.config.tsdefinesdocsandhostprojects: sequential, one worker, 5-minute timeouts; outside containers it aliases coding-agent to source.
工作原理How it works
- 新增 docs 评测:写
evals/<name>.docs.eval.ts,用createPiDocumentationEvalHarness({ output })把结果整理成可比较的 JSON。A new docs eval: writeevals/<name>.docs.eval.tsand usecreatePiDocumentationEvalHarness({ output })to shape the result into comparable JSON. - 评分:确定的结构用
StructuredOutputJudge({ expected, match: 'strict' });还要求某次工具调用就加ToolCallJudge。Scoring: useStructuredOutputJudge({ expected, match: 'strict' })for a fixed structure, and addToolCallJudgewhen a specific tool call is required. - docs 评测不能在本机直接跑:工厂在容器沙箱外会抛错。Docs evals cannot run directly on the host: the factory throws outside the container sandbox.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/evals/smoke.eval.tsL5 | smoke | 不给工具no tools |
| packages/evals/evals/documentation-audit.eval.tsL43 | documentation-audit | 40 页文档40 doc pages |
| packages/evals/evals/models.docs.eval.tsL30 | models | StructuredOutputJudgeStructuredOutputJudge |
| packages/evals/evals/extensions.docs.eval.tsL40 | extensions | ToolCallJudgeToolCallJudge |
| packages/evals/evals/tui.docs.eval.tsL192 | tui | 自定义评分器a custom judge |
| packages/evals/evals/acme-server.tsL3 | acme-server | 夹具服务器fixture server |
eval:docs:先写下实验计划,再逐个运行eval:docs: write the plan down first, then run each arm#
一句话结论Takeawaycli.ts 依次解析参数、确认 auth.json 里有该 provider 的凭据、构建两个镜像(id 必须不同)、在两个镜像里分别发现用例(必须完全相同)、生成任务计划,并把 protocol.json(含 sha256 协议摘要)和 expected-runs.json 写盘;然后对每个任务启动一个新容器,读回 vitest.json,最后汇总成 report.json / report.txt。cli.ts parses arguments, checks auth.json holds the provider's credential, builds the two images (whose ids must differ), discovers cases in both images (which must be identical), builds the task plan, and writes protocol.json (with a sha256 protocol digest) and expected-runs.json; it then starts a fresh container per task, reads the vitest.json back, and finally summarises into report.json / report.txt.
要点Key points
- 实验设计在运行前就固定:报告按
expected-runs.json检查每一对,而不是只看实际跑出了什么。The design is fixed before anything runs: the report checks each pair againstexpected-runs.json, not just against what happened to run. observations.jsonl每个任务之后重写:中途崩溃也保留已完成的观测。一侧失败不会中断计划。observations.jsonlis rewritten after every task, so a crash keeps what finished. One failed arm does not stop the plan.- 存在阻塞对时进程退出码为 1;产物目录
.eval/<时间戳>_<uuid>/权限 0700,且被 git 忽略。With blocked pairs the process exits 1; the artifact directory.eval/<timestamp>_<uuid>/is 0700 and git-ignored.
工作原理How it works
- 限定用例:
npm run eval:docs -w packages/evals -- -t "<名字>" <文件>。Narrow it:npm run eval:docs -w packages/evals -- -t "<name>" <file>. - 重复次数:
--runs-per-variant 3或PI_EVAL_RUNS_PER_VARIANT=3。Repetitions:--runs-per-variant 3orPI_EVAL_RUNS_PER_VARIANT=3. - 复查一次运行:看
.eval/…/report.txt,再到<variant>/sessions/打开原生的 Pi 会话。Review a run: read.eval/…/report.txt, then open the native Pi sessions under<variant>/sessions/.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/src/cli.tsL129 | requireEvalAuthFile | 凭据credentials |
| packages/evals/src/cli.tsL133 | same image | 镜像必须不同images must differ |
| packages/evals/src/cli.tsL148 | compareDiscovery | 发现结果必须一致discoveries must match |
| packages/evals/src/cli.tsL163 | protocolDigest | 协议摘要protocol digest |
| packages/evals/src/cli.tsL192 | exitCode | 阻塞即失败blocked means failure |
任务计划:用例 × 运行序号 × 两种变体The task plan: case × run number × both variants#
一句话结论TakeawaycreateTaskPlan() 把每个 (用例, 模型, 运行序号) 展开成一对任务:without_docs 与 with_docs。奇数次运行先跑对照组,偶数次反过来,以减少顺序偏差。用例身份是 "<评测集> > <用例>" 两段全名,不允许重复;模型身份是 provider/model;每个任务的目录名是身份元组的 sha256。createTaskPlan() expands each (case, model, run number) into a pair of tasks, without_docs and with_docs. Odd runs start with the control and even runs flip, to reduce order bias. A case's identity is its two-part full name "<eval set> > <case>", which must be unique; the model identity is provider/model; each task's directory name is the sha256 of its identity tuple.
要点Key points
- 用例来自
vitest list --json:parseDiscoveredCases()拒绝不是恰好两段的名字和重复身份。Cases come fromvitest list --json:parseDiscoveredCases()rejects names that are not exactly two parts and duplicate identities. runsPerVariant必须是正整数,默认 1;模型字符串必须含/且不在两端。runsPerVariantmust be a positive integer, default 1; the model string must contain/, not at either end.- 每个 (用例, 模型, 运行序号) 就是报告里的一“对”:对照组与实验组共享同一个用例定义和评分器。Each (case, model, run number) is one "pair" in the report: control and treatment share one case definition and one judge.
工作原理How it works
- 想要更稳的结论:提高
--runs-per-variant,让 flaky 标记有机会出现。For steadier conclusions: raise--runs-per-variantso the flaky flag has a chance to show up. - 在单元测试里验证计划:
test/plan.test.ts覆盖展开顺序与身份校验。Check the plan in unit tests:test/plan.test.tscovers expansion order and identity checks.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/src/plan.tsL1 | DOCUMENTATION_VARIANTS | 两种变体the two variants |
| packages/evals/src/plan.tsL39 | createTaskPlan | 展开任务expands tasks |
| packages/evals/src/plan.tsL54 | order | 交替顺序alternating order |
| packages/evals/src/plan.tsL33 | identity | 身份不能重复no duplicate identities |
| packages/evals/src/docker.tsL157 | taskDirectoryName | 任务目录task directory |
一个 Dockerfile,两个只差文档的目标One Dockerfile, two targets that differ only in docs#
一句话结论Takeawaybuilder 阶段挂载整个仓库,npm ci --ignore-scripts、离线构建,再用 install-runtime.mjs 把 coding-agent 打包安装到 /opt/evaluator;without-docs-install 删掉 coding-agent 的 README、CHANGELOG、docs/ 与 examples/。evaluator 从干净的 node:24-slim 开始,只拷运行器需要的文件并把评测源码设成 root 专属;两个最终目标只在 node_modules 的来源和 PI_EVAL_VARIANT 上不同。The builder stage mounts the whole repository, runs npm ci --ignore-scripts and an offline build, then uses install-runtime.mjs to pack and install coding-agent into /opt/evaluator; without-docs-install deletes coding-agent's README, CHANGELOG, docs/ and examples/. evaluator starts from a clean node:24-slim, copies only runner files and makes eval sources root-only; the two final targets differ only in where node_modules comes from and in PI_EVAL_VARIANT.
要点Key points
- 两个变体用同一份安装结果:对照组只是在其上删除文档,所以差异只有文档本身。Both variants share one installation: the control merely deletes docs from it, so the only difference is the documentation.
- 镜像名是
pi-evals-<仓库路径 sha256 前 12 位>-<variant>:local;构建后用docker image inspect取 id,两个 id 相同则直接报错。Images are namedpi-evals-<first 12 hex of sha256(repo path)>-<variant>:local; after building,docker image inspectreads the id, and equal ids are an error. - 每个任务一次
docker run --rm --read-only:只有/tmp与.vite-temp两个 tmpfs 可写,产物目录与只读的auth.json通过 bind mount 进入。Each task is onedocker run --rm --read-only: only the/tmpand.vite-temptmpfs mounts are writable, and the artifact directory and a read-onlyauth.jsoncome in as bind mounts.
工作原理How it works
- 构建上下文是仓库根;
Dockerfile.dockerignore排除.git、.eval、node_modules、dist、coverage与.env*。The build context is the repo root;Dockerfile.dockerignoreexcludes.git,.eval,node_modules,dist,coverageand.env*. - 改了 coding-agent 之后重新跑
eval:docs即可:每次都会重新构建两个镜像。After changing coding-agent just reruneval:docs: it rebuilds both images every time.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/docker/DockerfileL1 | builder | 构建阶段build stage |
| packages/evals/docker/DockerfileL14 | without-docs-install | 删文档deletes docs |
| packages/evals/docker/DockerfileL21 | evaluator | 运行时镜像runtime image |
| packages/evals/docker/DockerfileL42 | with_docs | 实验组目标treatment target |
| packages/evals/docker/install-runtime.mjsL26 | install-runtime.mjs | 打包安装pack and install |
| packages/evals/src/docker.tsL101 | docker run | 只读容器read-only container |
三层防护:模型读不到评分器Three guards: the model cannot read its judges#
一句话结论Takeaway容器以 root 启动:entrypoint.ts 先核对 /repo 的内容完全符合清单、内部依赖不带文档、变体与文档一致、评测源码只属于 root 并实际以 UID 65532 探测读不到、coding-agent 从 dist/ 解析,然后把凭据交给沙箱用户并启动 vitest。每个用例里 harness 再建临时目录、隔离环境变量、把凭据放进内存、删除 auth.json,最后 setgroups([]) → setgid → setuid(65532),此后模型的工具才开始运行。The container starts as root: entrypoint.ts checks that /repo matches an exact allowlist, internal deps carry no docs, the variant matches its docs, eval sources are root-only and an actual read probe as UID 65532 fails, and coding-agent resolves from dist/; it then hands credentials to the sandbox user and starts vitest. Inside each case the harness creates a temp root, isolates environment variables, keeps the credential in memory, deletes auth.json, and finally setgroups([]) → setgid → setuid(65532) — only then do the model's tools run.
要点Key points
- docs 评测只给
read、write、edit、grep、find、ls六个工具:没有 shell,也没有联网搜索。Docs evals get only six tools —read,write,edit,grep,find,ls: no shell and no web search. - Vitest 转换后的评测模块缓存被设成 0600,降权后 harness 再确认一个也读不到。Vitest's transformed eval modules are set to 0600, and after dropping privileges the harness confirms none of them is readable.
- README 明确承认边界:调用 provider 需要联网,因此单靠 Docker 不能证明模型写的代码从不联网。The README states the limit plainly: provider calls need the network, so Docker alone cannot prove agent-written code never uses it.
工作原理How it works
- 评测要在沙箱外跑会失败:
createPiDocumentationEvalHarness()要求PI_EVAL_CONTAINER=1与沙箱 UID/GID。Running outside the sandbox fails:createPiDocumentationEvalHarness()requiresPI_EVAL_CONTAINER=1and a sandbox UID/GID. - 加新夹具时放在
evals/下:它会自动成为 root 专属、对模型不可读。Put new fixtures underevals/: they automatically become root-only and unreadable to the model.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/docker/entrypoint.tsL27 | assertWorkspace | 镜像清单image allowlist |
| packages/evals/docker/entrypoint.tsL80 | assertSandboxCannotRead | 读取探测read probe |
| packages/evals/docker/entrypoint.tsL104 | dist | 必须用 distmust use dist |
| packages/evals/src/harness.tsL82 | applyIsolatedEnvironment | 环境隔离environment isolation |
| packages/evals/src/harness.tsL154 | enterToolSandbox | 降权privilege drop |
| packages/evals/src/harness.tsL485 | DOCUMENTATION_EVAL_TOOLS | 六个工具six tools |
一次运行:真实的 AgentSession,跑完即拆One run: a real AgentSession, torn down afterwards#
一句话结论TakeawayrunPiCodingAgent() 用 ModelRuntime 和内存凭据创建服务与会话(thinkingLevel: "off"),降权后按输入逐步执行 prompt 或 reload,收集转录事件、系统提示词的 sha256、token、工具调用与(有定价时的)成本,校验系统提示词,再交给 output() 或直接返回回答。finally 里总是保存 session.jsonl、释放会话、删掉临时目录并还原环境。runPiCodingAgent() creates services and a session (thinkingLevel: "off") with a ModelRuntime and in-memory credentials, drops privileges, runs each input step as a prompt or a reload, collects transcript events, the system prompt's sha256, tokens, tool calls and (when priced) cost, verifies the system prompt, and hands off to output() or returns the response. finally always saves session.jsonl, disposes the session, removes the temp root and restores the environment.
要点Key points
- 两种变体的唯一区别:
without_docs注册一个隐藏的before_agent_start扩展,用excludePiDocumentation剪掉系统提示词里的<docs>…</docs>。The only difference between variants:without_docsregisters a hiddenbefore_agent_startextension that cuts<docs>…</docs>out of the system prompt withexcludePiDocumentation. verifySystemPrompt()防止实验悄悄失效:<rules>必须还在,<docs>的有无必须与变体一致。verifySystemPrompt()keeps the experiment from silently breaking:<rules>must survive and the presence of<docs>must match the variant.- 会话里出现任何意外扩展都会报错:只允许
<inline:…>的隐藏扩展。Any unexpected extension in the session is an error: only hidden<inline:…>extensions are allowed. - 失败时如果已有诊断数据,错误会带上部分结果(
attachHarnessRunToError),报告据此把这一侧记为 errored。On failure with diagnostics already collected, the error carries a partial run (attachHarnessRunToError), and the report records that arm as errored.
工作原理How it works
- host 评测直接用
createPiCodingAgentHarness({ tools, noTools, workspaceFiles, output })。Host evals usecreatePiCodingAgentHarness({ tools, noTools, workspaceFiles, output })directly. - 评分器需要的东西放进
output({ response, session, systemPrompt, agentDir })的返回值。Put whatever the judge needs into the return value ofoutput({ response, session, systemPrompt, agentDir }).
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/src/harness.tsL272 | runPiCodingAgent | 一次运行one run |
| packages/evals/src/harness.tsL238 | promptAgent | 一个 prompt 步骤one prompt step |
| packages/evals/src/harness.tsL257 | verifySystemPrompt | 提示词校验prompt check |
| packages/evals/src/harness.tsL494 | excludePiDocumentation | 剪掉 <docs>cuts <docs> |
| packages/evals/src/harness.tsL517 | createPiDocumentationEvalHarness | docs 评测工厂docs eval factory |
报告:结果 → 配对 → 比较The report: outcomes → pairs → comparison#
一句话结论TakeawayreadTaskObservation() 把每个任务归为 scored、unscored、skipped、pending 或 errored;resolvePair() 要求每一侧在计划里恰好一次、恰好观测到一次且已评分,否则这一对被阻塞并写明原因。每个评测集的通过率按 score ≥ 1 计算,提升 = 实验组 − 对照组;只要有一对被阻塞就不公布通过率。另有 5 个标记和 tokens / 工具 / 耗时 / 成本的配对均值差。readTaskObservation() classifies each task as scored, unscored, skipped, pending or errored; resolvePair() requires each arm to be planned exactly once, observed exactly once and scored, otherwise the pair is blocked with its reasons. Per eval set, pass rate counts score ≥ 1 and lift = treatment − control; if any pair is blocked, no pass rate is published. There are also five flags and paired mean deltas for tokens / tools / latency / cost.
要点Key points
- 0 分也是 scored:它是数据。只有没有分数才是 unscored。A score of 0 is still scored — it is data. Only a missing score is unscored.
- 标记:
no-lift、negative-delta、control-saturated、treatment-saturated,以及同一用例同一侧跨运行结果不同的flaky。Flags:no-lift,negative-delta,control-saturated,treatment-saturated, andflakywhen the same case and arm passes in only some runs. - 运行总量(每个变体的 runs、tokens、工具、耗时、成本)包含出错的运行,并标出有多少次有测量值。Operational totals (runs, tokens, tools, latency, cost per variant) include errored runs and say how many were measured.
工作原理How it works
- 先看
report.txt的 Pass rate 行;出现 withheld 时到 Blocked pairs 一节找原因。Read the Pass rate line inreport.txtfirst; when it says withheld, the Blocked pairs section gives the reasons. - 机器读取用
report.json(schemaVersion: 3),它带协议摘要,可以和protocol.json对上。For machines usereport.json(schemaVersion: 3); it carries the protocol digest that matchesprotocol.json.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/evals/src/report.tsL136 | readTaskObservation | 每个任务一个结果one outcome per task |
| packages/evals/src/report.tsL249 | resolvePair | 配对pairing |
| packages/evals/src/report.tsL336 | comparisonFlags | 5 个标记five flags |
| packages/evals/src/report.tsL359 | summarizeEvalObservations | 比较comparison |
| packages/evals/src/report.tsL436 | formatEvalComparisonReport | report.txtreport.txt |
扩展点Extension points#
evals/<name>.docs.eval.ts新增一个文档提升评测:自动在两个变体里成对运行并进入报告。Add a documentation-lift eval: it runs in pairs in both variants and lands in the report automatically.evals/<name>.eval.ts新增一个 host 评测:普通的 vitest-evals 套件,由 eval:host 在本机运行。Add a host eval: an ordinary vitest-evals suite run on the machine by eval:host.createPiDocumentationEvalHarness({ output })把运行后的状态整理成评分器可比较的 JSON。Shape the post-run state into JSON a judge can compare.judges: StructuredOutputJudge / ToolCallJudge / createJudge选择或编写评分器;judgeThreshold: null 让分数成为数据。Pick or write judges; judgeThreshold: null makes the score data.--runs-per-variant · -t · PI_PROVIDER / PI_MODEL控制重复次数、选择用例和被测模型。Control repetitions, select cases and choose the model under test.测试Tests#
一句话结论Takeaway六个测试文件只测运行器本身,不调用模型:report.test.ts(12 个用例)覆盖每一种结果分类与会话快照;harness.test.ts(10)覆盖隔离环境、提示词校验与剪掉 <docs>;configured-runtime.test.ts(8)覆盖评分器读取的运行时状态;acme-server.test.ts(5)覆盖夹具服务器;comparison.test.ts(4)覆盖配对、阻塞与报告格式;plan.test.ts(4)覆盖任务展开与身份校验。评测本身(*.eval.ts)不计入。Six test files test only the runner and never call a model: report.test.ts (12 cases) covers every outcome classification and the session snapshot; harness.test.ts (10) covers the isolated environment, prompt verification and cutting <docs>; configured-runtime.test.ts (8) covers the runtime state judges read; acme-server.test.ts (5) covers the fixture server; comparison.test.ts (4) covers pairing, blocking and report formatting; plan.test.ts (4) covers task expansion and identity checks. The evals themselves (*.eval.ts) are not counted.
用例数 = 源码中 it( / test( 的出现次数(静态统计,不运行测试)。Cases = occurrences of it( / test( in the source (static count; tests were not run).
交叉链接Cross-links#
被测对象:容器里用 dist/,评测创建真实的 AgentSessionthe subject under test: dist/ in containers, evals create real AgentSessions
pi-ai已完成DoneInMemoryCredentialStore 与 ModelRuntime 的模型与认证InMemoryCredentialStore and ModelRuntime models and auth
telemetry已完成Done另一种观测方式:结构化的 span,而不是评测指标a different kind of observation: structured spans rather than eval metrics