图解 Pi AgentPi Agent, Illustratedv1.1.0
包Packages· evals
演示Slides
Infra已完成Donepackages/evals · v1.1.0

evals@earendil-works/pi-evals · private

私有包:行为评测与成对的文档提升对比(Docker 隔离)。Private package: behavioural evals and paired documentation-lift comparisons (Docker-isolated).

1.1.0
版本version
5
源文件src files
1,446
源码行src LOC
6
测试文件test files
43
测试用例test cases
0
导出路径export paths
依赖Depends on无内部依赖no internal deps被依赖Used by—devpi-aicoding-agent

这个包是什么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.

01Infra

运行器、评测定义与镜像:三个目录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.

pi-evals 的结构:运行器、评测定义与镜像构建pi-evals: the runner, eval definitions and the image build
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
pi-evals: a private package — a 1,446-line runner, 7 eval files and a multi-stage Docker imagesrc/运行器the runnerevals/评测定义与夹具eval definitions and fixturesdocker/镜像构建image buildpackage.jsonprivate: true · 没有 exportsprivate: true · no exports192cli.tseval:docs 的主程序:解析参数 → 镜像 → 发现 → 计划 → 逐个运行 → 报告eval:docs main: parse args → images → discover → plan → run each → report59plan.ts(case, variant, run) 任务(case, variant, run) tasks483report.ts配对与提升pairing and lift175docker.ts镜像与一次隔离运行images, one isolated run537harness.tsvitest-evals 适配器:在隔离目录里跑一个真实的 AgentSessionthe vitest-evals adapter: runs a real AgentSession in an isolated directorydocumentation-audit.eval.tssmoke.eval.tscustom-providerextensionsmodelsopenai-providertuiacme-server.tsconfigured-runtime.ts44Dockerfile5 个阶段 · 2 个目标5 stages · 2 targets152entrypoint.ts容器内的检查与 vitestin-container checks, vitest70install-runtime.mjs打包并安装工作区pack + install workspacepi-aipi-coding-agentvitest-evals@vitest-evals/coreautoevalsvitestPI_SESSION_SNAPSHOT_ARTIFACTcreatePi*Harnesshost(2)host (2)docs(5)docs (5)夹具fixturesdocs 评测文件名省略了 .docs.eval.tsdocs file names shown without .docs.eval.ts镜像里只有运行时:评测源码对模型用户不可读。The image holds only the runtime: eval sources are unreadable to the model user.Dockerfile.dockerignore 排除 .git、node_modules、dist、.env*。Dockerfile.dockerignore drops .git, node_modules, dist, .env*.testvitest:test/ 里的单元测试vitest: the unit tests in test/eval:hostvitest --project host:在本机跑 host 评测vitest --project host: host evals on this machineeval:docsnode src/cli.ts:容器里的文档提升对比node src/cli.ts: the documentation-lift comparison in containersevaleval:host,然后 eval:docseval:host, then eval:docsclean删除 .eval/ 产物目录delete the .eval/ artifactsdevDependencies工作区内的 pi 包都来自当前仓库(npm overrides)。Internal pi packages come from this repository (npm overrides).评测的对象是 coding-agent 本身:host 评测在本机检验行为;docs 评测在容器里比较“有文档 / 没文档”两种安装,测量文档带来的提升。The subject is coding-agent itself: host evals check behaviour on this machine; docs evals compare a with-docs and a without-docs install in containers to measure documentation lift.标签 = 行数tag = lines运行器runnerharness评测evalsDocker使用uses

要点Key points

  • *.docs.eval.ts 是文档提升评测,由 eval:docs 在容器里成对运行;其余 *.eval.ts 是普通的 host 评测,由 eval:host 在本机运行。*.docs.eval.ts files are documentation-lift evals, run in pairs in containers by eval:docs; other *.eval.ts files are ordinary host evals run on the machine by eval:host.
  • 评测文件只依赖 harness.ts 的两个工厂:createPiCodingAgentHarness()(host)与 createPiDocumentationEvalHarness()(docs)。Eval files depend only on two factories in harness.ts: createPiCodingAgentHarness() (host) and createPiDocumentationEvalHarness() (docs).
  • harness.ts 只从 report.ts 拿一个常量 PI_SESSION_SNAPSHOT_ARTIFACT:会话快照随结果一起写进 Vitest JSON。harness.ts takes just one constant from report.ts, PI_SESSION_SNAPSHOT_ARTIFACT: the session snapshot travels with the result inside the Vitest JSON.

工作原理How it works

  1. 全部评测: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).
  2. 只跑 host:npm run eval:host -w packages/evals;-- 之后的参数只传给 eval:docs。Host only: npm run eval:host -w packages/evals; flags after -- go to eval:docs only.
  3. 运行器自身的单元测试: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.tsL131cli.ts编排一次对比orchestrates a comparison
packages/evals/src/docker.tsL38buildImages两个镜像the two images
packages/evals/src/harness.tsL471createPiCodingAgentHarnessvitest-evals 适配器the vitest-evals adapter
packages/evals/package.jsonL4private不发布not published
packages/evals/vitest.evals.config.tsL15projectsdocs / host 两个项目docs / host projects
02Infra

公共 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 cleanshx rm -rf .eval删除 .eval/ 下的产物delete the artifacts under .eval/
npm run evalnpm 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:hostvitest run --config vitest.evals.config.ts --project host在本机跑 host 评测run host evals on this machine
npm run eval:docsnode --experimental-strip-types src/cli.ts在两个容器变体里成对运行 docs 评测并报告提升run docs evals in pairs in both container variants and report lift
npm run testvitest run --config vitest.test.config.ts运行器自身的单元测试(vitest.test.config.ts)the runner's own unit tests (vitest.test.config.ts)

package.json · scriptsL6

参数与环境变量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.

03Infra

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.

7 个评测:2 个 host 套件与 5 个文档提升套件Seven evals: two host suites and five documentation-lift suites
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
What is evaluated: 2 host suites (including a 40-page documentation audit) and 5 documentation-lift suiteshost 评测 · eval:hostHost evals · eval:host本机运行on this machine文档提升评测 · eval:docsDocumentation-lift evals · eval:docs每个用例 × 两种镜像each case × both imagesreadgrepfindlssubmit_documentation_auditStructuredOutputJudgeStructuredOutputJudge + ToolCallJudgeStructuredOutputJudgeStructuredOutputJudgecreateJudgedocumentation-audit.eval.ts“Audit documentation against implementation”> $path matches the implementationsmoke.eval.ts“Answer a basic prompt”> returns the expected answersmoke:没有工具;答案必须正好是 "Paris",smoke: no tools; the answer must be exactly "Paris",且 usage 的 provider / model 与环境变量一致。and usage must match PI_PROVIDER / PI_MODEL.documentation-audit:it.for 遍历 coding-agent/docsdocumentation-audit: it.for over the 40 pages下的 40 页;只读工具 + 一个提交工具,in coding-agent/docs; read-only tools plus a submitverdict 必须是 "match"(mismatch / inconclusivetool; the verdict must be "match" (mismatch /都让用例失败)。inconclusive fail the case).Add custom streaming provider> implements a provider from an API fixturecustom-provider.docs.eval.tsCreate and use a tool extension> creates, reloads, and invokes the extensionextensions.docs.eval.tsAdd model to existing provider> adds the model without replacing existing modelsmodels.docs.eval.tsAdd OpenAI-compatible provider> configures a provider that works through Piopenai-provider.docs.eval.tsCustomize the interactive context footer> replaces numeric context usage with a progress bartui.docs.eval.ts每个都是 [prompt, reload]:先让 Pi 改配置或写扩展,重载后再检查结果。Each is [prompt, reload]: Pi changes its config or writes an extension, then reloads and is checked.judgeThreshold: null:低分是数据,不是失败;分数参与配对比较。judgeThreshold: null: a low score is data, not a failure; scores feed the paired comparison.acme-server.ts 提供假的 OpenAI 兼容 / NDJSON 流式 API 供 provider 用例调用。acme-server.ts serves fake OpenAI-compatible / NDJSON streaming APIs for the provider cases.vitest.evals.config.ts · projects: docs = evals/**/*.docs.eval.ts · host = evals/**/*.eval.ts minus docs · testTimeout 300_000 · pool forks · maxWorkers 1 · fileParallelism falsecreatePiDocumentationEvalHarness() 在容器沙箱外直接抛错,所以 docs 评测只能经 eval:docs 运行。createPiDocumentationEvalHarness() throws outside the container sandbox, so docs evals only run through eval:docs.host 评测host evalsdocs 评测(标签 = 评分器)docs evals (tag = judge)

要点Key points

  • 每个 docs 用例的输入都是 [prompt, reload] 两步:模型改完配置后,harness 调 session.reload(),评分器再从运行时里读出真实状态。Every docs case takes two steps, [prompt, reload]: after the model edits config the harness calls session.reload(), and the judge reads the real state back from the runtime.
  • acme-server.ts 是本地的 OpenAI 兼容 / NDJSON 流式夹具服务器,供 provider 类用例调用;configured-runtime.ts 把运行时状态整理成评分器要比较的结构。acme-server.ts is a local OpenAI-compatible / NDJSON streaming fixture server for the provider cases; configured-runtime.ts shapes runtime state into what judges compare.
  • vitest.evals.config.ts 分 docs 与 host 两个项目:串行、单 worker、5 分钟超时;容器外它把 coding-agent 别名到源码。vitest.evals.config.ts defines docs and host projects: sequential, one worker, 5-minute timeouts; outside containers it aliases coding-agent to source.

工作原理How it works

  1. 新增 docs 评测:写 evals/<name>.docs.eval.ts,用 createPiDocumentationEvalHarness({ output }) 把结果整理成可比较的 JSON。A new docs eval: write evals/<name>.docs.eval.ts and use createPiDocumentationEvalHarness({ output }) to shape the result into comparable JSON.
  2. 评分:确定的结构用 StructuredOutputJudge({ expected, match: 'strict' });还要求某次工具调用就加 ToolCallJudge。Scoring: use StructuredOutputJudge({ expected, match: 'strict' }) for a fixed structure, and add ToolCallJudge when a specific tool call is required.
  3. 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.tsL5smoke不给工具no tools
packages/evals/evals/documentation-audit.eval.tsL43documentation-audit40 页文档40 doc pages
packages/evals/evals/models.docs.eval.tsL30modelsStructuredOutputJudgeStructuredOutputJudge
packages/evals/evals/extensions.docs.eval.tsL40extensionsToolCallJudgeToolCallJudge
packages/evals/evals/tui.docs.eval.tsL192tui自定义评分器a custom judge
packages/evals/evals/acme-server.tsL3acme-server夹具服务器fixture server
04Infra

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.

一次 eval:docs:8 个步骤与 .eval/ 下的产物One eval:docs run: eight steps and the artifacts under .eval/
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
One eval:docs run: write down the whole experiment first, then run each arm in a fresh container第 7 步,对每个任务Step 7, for each task.eval/<时间戳>_<uuid>/(0700).eval/<timestamp>_<uuid>/ (0700)parseEvalCli--provider --model · -t · 文件--provider --model · -t · filesrequireEvalAuthFileauth.json 里必须有该 providerauth.json must hold the providerbuildImageswithout_docs / with_docs,id 必须不同without_docs / with_docs, ids must differdiscoverCases × 2两种镜像里发现的用例必须完全相同both images must discover identical casescreateTaskPlan(case, variant, model, run) 全部任务every (case, variant, model, run) taskprotocol.json+ sha256 协议摘要 · expected-runs.json+ sha256 protocol digest · expected-runs.jsonfor task: runTaskdocker run 一个全新容器 → vitest.jsondocker run a fresh container → vitest.jsonsummarize + formatreport.json / report.txt;有阻塞对 → 退出码 1report.json / report.txt; blocked pairs → exit 1protocol.jsonexpected-runs.jsondiscovery/<variant>/tasks/<sha256>/vitest.jsonobservations.jsonl<variant>/sessions/<sha256>/session.jsonlreport.jsonreport.txtrunTask(docker, task)--project docs 文件 --testNamePattern ^<集> <用例>$--project docs file --testNamePattern ^<set> <case>$有 vitest.json?vitest.json?有 → readTaskObservation;没有 → erroredObservationyes → readTaskObservation; no → erroredObservationobservations.jsonl每个任务之后整体重写:中途崩溃也留下已完成的观测rewritten after every task: a crash keeps what finished失败不中断Failures don't stop it失败或缺失的一侧被记下,计划内的其余任务照常继续a failed or missing arm is recorded; the rest of the plan continues产物可能包含提示词、回答、生成的代码与工具输出;目录被 .gitignore 忽略。重复次数按运行序号交替顺序,减少顺序偏差。Artifacts may contain prompts, responses, generated code and tool output; the directory is git-ignored. Repetitions alternate order by run number to reduce order bias.123456781步骤step顺序order容器里执行runs in a container

要点Key points

  • 实验设计在运行前就固定:报告按 expected-runs.json 检查每一对,而不是只看实际跑出了什么。The design is fixed before anything runs: the report checks each pair against expected-runs.json, not just against what happened to run.
  • observations.jsonl 每个任务之后重写:中途崩溃也保留已完成的观测。一侧失败不会中断计划。observations.jsonl is 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

  1. 限定用例:npm run eval:docs -w packages/evals -- -t "<名字>" <文件>。Narrow it: npm run eval:docs -w packages/evals -- -t "<name>" <file>.
  2. 重复次数:--runs-per-variant 3 或 PI_EVAL_RUNS_PER_VARIANT=3。Repetitions: --runs-per-variant 3 or PI_EVAL_RUNS_PER_VARIANT=3.
  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.tsL129requireEvalAuthFile凭据credentials
packages/evals/src/cli.tsL133same image镜像必须不同images must differ
packages/evals/src/cli.tsL148compareDiscovery发现结果必须一致discoveries must match
packages/evals/src/cli.tsL163protocolDigest协议摘要protocol digest
packages/evals/src/cli.tsL192exitCode阻塞即失败blocked means failure
05Extensions

任务计划:用例 × 运行序号 × 两种变体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.

createTaskPlan:用例 × 运行序号 × 两种变体,交替顺序createTaskPlan: case × run × both variants, in alternating order
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
createTaskPlan: case × run number × both variants; odd runs start with without_docs, even runs flip身份与校验Identity and checksplan.ts例子:2 个用例,--runs-per-variant 3 → 12 个任务(按执行顺序)Example: 2 cases, --runs-per-variant 3 → 12 tasks (in execution order)#1 · without_docs#2 · with_docs#3 · with_docs#4 · without_docs#5 · without_docs#6 · with_docs#7 · without_docs#8 · with_docs#9 · with_docs#10 · without_docs#11 · without_docs#12 · with_docsparseDiscoveredCasesvitest list 的 JSON → 用例vitest list JSON → cases"<eval set> > <case>"全名必须恰好两段,否则报错the full name must have exactly two parts身份不能重复No duplicate identitiesJSON.stringify([evalSet, caseId])模型身份Model identity"provider/model":必须含 "/",且不在两端"provider/model": must contain "/", not at either endrunsPerVariant正整数;默认 1 · --runs-per-variant · PI_EVAL_RUNS_PER_VARIANTpositive integer; default 1 · --runs-per-variant · PI_EVAL_RUNS_PER_VARIANT任务目录Task directorysha256([set, case, variant, model, run])Add model to existing provider> adds the model …run 1run 2run 3Create and use a tool extension> creates, reloads …run 1run 2run 3每个 (用例, 模型, 运行序号) 组成一对:without_docs 是对照组,with_docs 是实验组。所有任务在运行前就写进 expected-runs.json。Each (case, model, run number) forms a pair: without_docs is the control and with_docs the treatment. Every task is written to expected-runs.json before anything runs.加粗 = 这一轮先跑的一侧bold = the arm that runs first in its roundwithout_docs(对照)without_docs (control)with_docs(实验)with_docs (treatment)

要点Key points

  • 用例来自 vitest list --json:parseDiscoveredCases() 拒绝不是恰好两段的名字和重复身份。Cases come from vitest list --json: parseDiscoveredCases() rejects names that are not exactly two parts and duplicate identities.
  • runsPerVariant 必须是正整数,默认 1;模型字符串必须含 / 且不在两端。runsPerVariant must 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

  1. 想要更稳的结论:提高 --runs-per-variant,让 flaky 标记有机会出现。For steadier conclusions: raise --runs-per-variant so the flaky flag has a chance to show up.
  2. 在单元测试里验证计划:test/plan.test.ts 覆盖展开顺序与身份校验。Check the plan in unit tests: test/plan.test.ts covers expansion order and identity checks.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/evals/src/plan.tsL1DOCUMENTATION_VARIANTS两种变体the two variants
packages/evals/src/plan.tsL39createTaskPlan展开任务expands tasks
packages/evals/src/plan.tsL54order交替顺序alternating order
packages/evals/src/plan.tsL33identity身份不能重复no duplicate identities
packages/evals/src/docker.tsL157taskDirectoryName任务目录task directory
06Extensions

一个 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.

Dockerfile 的 5 个阶段与每个任务的 docker runThe five Dockerfile stages and the per-task docker run
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
The Dockerfile: one build, two targets that differ only in docs; docker run is read-only, one task eachdocker/Dockerfile5 个阶段 · 2 个构建目标5 stages · 2 build targetsbuildImages()docker.tsbuilderFROM node:24-slim · 挂载仓库 · npm ci --ignore-scriptsFROM node:24-slim · repo bind mount · npm ci --ignore-scriptsbuild:offline → install-runtime.mjs /opt/evaluatorwithout-docs-installFROM builder删掉 coding-agent 的 README.md CHANGELOG.md docs/ examples/rm coding-agent README.md CHANGELOG.md docs/ examples/evaluatorFROM node:24-slim · COPY builder 的 root → /repoFROM node:24-slim · COPY builder root → /repochmod go-rwx src evals docker · ENTRYPOINT entrypoint.tswith_docs+ builder 的 node_modules · PI_EVAL_VARIANT=with_docs+ builder node_modules · PI_EVAL_VARIANT=with_docswithout_docs+ 去掉文档的 node_modules · PI_EVAL_VARIANT=without_docs+ docs-free node_modules · PI_EVAL_VARIANT=without_docsFROMCOPY rootFROMFROMnode_modules镜像名Image namepi-evals-<hash>-<variant>:localhashsha256(repoRoot)[:12]构建Builddocker build --target <variant>然后Thendocker image inspect → Idcli.ts两个 Id 相同 → 报错equal Ids → error上下文排除Context ignores.git .eval node_modulesdist coverage .env .env.*install-runtime.mjs 只把运行器需要的文件拷进 root/install-runtime.mjs copies only runner files into root/docker.ts · 每个任务一次docker.ts · once per taskdocker run --rm --read-only --tmpfs /tmp:rw,exec,mode=1777 --tmpfs /repo/node_modules/.vite-temp:rw,exec,mode=1777--mount type=bind,source=<.eval/…/tasks/<sha256>>,target=/artifacts --env PI_EVAL_ARTIFACT_DIR=/artifacts --env PI_EVAL_RUNS_PER_VARIANT=<n>--env PI_EVAL_SANDBOX_UID=65532 --env PI_EVAL_SANDBOX_GID=65532 --env PI_PROVIDER --env PI_MODEL [--env PI_EVAL_ARTIFACT_UID/GID = host user]--mount type=bind,source=<auth.json>,target=/run/pi-eval-secrets/auth.json,readonly <image> <entrypoint args>FROM / COPY 阶段FROM / COPY stageCOPY node_modules带文档with docs无文档 / 评测器no docs / evaluator

要点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 named pi-evals-<first 12 hex of sha256(repo path)>-<variant>:local; after building, docker image inspect reads the id, and equal ids are an error.
  • 每个任务一次 docker run --rm --read-only:只有 /tmp 与 .vite-temp 两个 tmpfs 可写,产物目录与只读的 auth.json 通过 bind mount 进入。Each task is one docker run --rm --read-only: only the /tmp and .vite-temp tmpfs mounts are writable, and the artifact directory and a read-only auth.json come in as bind mounts.

工作原理How it works

  1. 构建上下文是仓库根;Dockerfile.dockerignore 排除 .git、.eval、node_modules、dist、coverage 与 .env*。The build context is the repo root; Dockerfile.dockerignore excludes .git, .eval, node_modules, dist, coverage and .env*.
  2. 改了 coding-agent 之后重新跑 eval:docs 即可:每次都会重新构建两个镜像。After changing coding-agent just rerun eval:docs: it rebuilds both images every time.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/evals/docker/DockerfileL1builder构建阶段build stage
packages/evals/docker/DockerfileL14without-docs-install删文档deletes docs
packages/evals/docker/DockerfileL21evaluator运行时镜像runtime image
packages/evals/docker/DockerfileL42with_docs实验组目标treatment target
packages/evals/docker/install-runtime.mjsL26install-runtime.mjs打包安装pack and install
packages/evals/src/docker.tsL101docker run只读容器read-only container
07pi-coding-agent

三层防护:模型读不到评分器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.

三层防护:只读容器、root 自检、降权到 UID 65532Three guards: a read-only container, root self-checks, a drop to UID 65532
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Three guards: a read-only container → the entrypoint self-checks as root → the harness drops to UID 65532 before the model gets toolsdocker run宿主 → 容器host → containerentrypoint.ts以 root 运行runs as rootrunPiCodingAgentharness.ts · 每个用例harness.ts · each case模型的工具以 UID 65532 运行The model's tools run as UID 65532read · write · edit · grep · find · ls;没有 shell、没有联网搜索read · write · edit · grep · find · ls; no shell, no web searchworkspace/ (rw)isolated home/pi-coding-agent distREADME · docs/ · examples/ (with_docs)src/evals/ (judges, fixtures)docker/vitest configsauth.jsoncredential env varother packages' docs只读根文件系统Read-only root filesystem--read-only;可写的只有两个 tmpfs--read-only; only two tmpfs mounts are writable每个任务一个新容器A fresh container per task--rm:上一个任务的痕迹不会留下--rm: nothing carries over between tasks/artifacts绑定到 .eval/…/tasks/<sha256>bound to .eval/…/tasks/<sha256>/run/pi-eval-secrets/auth.json只读挂载;目录在镜像里是 0700read-only mount; the directory is 0700 in the image源码 root 专属Root-only sources镜像构建时 chmod go-rwx src evals dockerchmod go-rwx src evals docker at build timePI_EVAL_SANDBOX_UID/GID= 65532,交给后面两层= 65532, handed to the next two layers/repo 内容完全符合清单/repo matches an exact allowlistnode_modules · package.json · packages · vitest.base.ts内部依赖不带文档Internal deps carry no docs无 docs/ examples/ src/ test/ README CHANGELOGno docs/ examples/ src/ test/ README CHANGELOG变体与文档一致Variant matches its docswithout_docs 必须没有;with_docs 必须有 docs/models.mdwithout_docs: none; with_docs: docs/models.md exists评测源码:root 专属 + 探测Eval sources: root-only + probe以 65532 身份尝试读取,必须失败a read attempted as 65532 must failcoding-agent 来自 distcoding-agent resolves from dist…/pi-coding-agent/dist/index.js凭据交给沙箱用户Credentials handed overauth.json → /tmp/pi-eval-host-agent,chown 65532auth.json → /tmp/pi-eval-host-agent, chown 65532只跑指定的评测文件Only an explicit eval filePI_EVAL_CONTAINER=1 · vitest run / listmkdtemp pi-eval-*workspace/ · home/ · sessions/applyIsolatedEnvironmentHOME / PI_CODING_AGENT_DIR 指向临时目录;清掉 PI_EVAL_*HOME / PI_CODING_AGENT_DIR → temp dir; PI_EVAL_* removedInMemoryCredentialStore凭据只在内存里;随后删除 auth.jsoncredential in memory only; auth.json is then deleted隐藏凭据环境变量Credential env var hidden运行期间 delete,结束后恢复deleted for the run, restored afterwards不许有意外扩展No unexpected extensions只允许 <inline:…> 的隐藏扩展only hidden <inline:…> extensionsenterToolSandbox转换后的模块 0600;setgroups([]) → setgid → setuidtransformed modules 0600; setgroups([]) → setgid → setuid确认读不到Confirm it cannot read读到任何受保护模块 → 报错any readable protected module → error看得到can see看不到cannot seeREADME 坦白说明:调用 provider 仍需要容器联网,所以单靠 Docker 不能证明模型写的代码从不联网。The README is explicit: provider traffic still needs container network access, so Docker alone cannot prove agent-written code never uses the network.下一层next layer容器container入口(root)entrypoint (root)测试框架harness

要点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

  1. 评测要在沙箱外跑会失败:createPiDocumentationEvalHarness() 要求 PI_EVAL_CONTAINER=1 与沙箱 UID/GID。Running outside the sandbox fails: createPiDocumentationEvalHarness() requires PI_EVAL_CONTAINER=1 and a sandbox UID/GID.
  2. 加新夹具时放在 evals/ 下:它会自动成为 root 专属、对模型不可读。Put new fixtures under evals/: they automatically become root-only and unreadable to the model.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/evals/docker/entrypoint.tsL27assertWorkspace镜像清单image allowlist
packages/evals/docker/entrypoint.tsL80assertSandboxCannotRead读取探测read probe
packages/evals/docker/entrypoint.tsL104dist必须用 distmust use dist
packages/evals/src/harness.tsL82applyIsolatedEnvironment环境隔离environment isolation
packages/evals/src/harness.tsL154enterToolSandbox降权privilege drop
packages/evals/src/harness.tsL485DOCUMENTATION_EVAL_TOOLS六个工具six tools
08pi-coding-agent

一次运行:真实的 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.

runPiCodingAgent:一个用例的一次运行与两种变体的差别runPiCodingAgent: one run of one case, and how the variants differ
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
One run of one case: a real AgentSession, torn down afterwards; the two variants differ only in the <docs> of the system promptrunPiCodingAgent(input, signal, setArtifact, options)promptAgent每个 prompt 步骤each prompt stepcreatePiDocumentationEvalHarness失败时On failureresolveModelSelection · mkdtempprovider/model;root/ 下 workspace、home、sessions(见 ev_sandbox)provider/model; workspace, home, sessions under root/ (see ev_sandbox)ModelRuntime.create({ credentials })getModel → getAuth;没有认证就直接报错getModel → getAuth; no auth → errorcreateAgentSessionServicescwd = workspace · extensionFactories(隐藏的提示词变换)cwd = workspace · extensionFactories (hidden prompt transform)createAgentSessionFromServicesthinkingLevel "off" · tools / noTools / customToolsenterToolSandbox → for step of inputprompt → promptAgent;reload → session.reload()prompt → promptAgent; reload → session.reload()诊断数据Diagnostics转录事件 · systemPromptSha256 · tokens / toolCalls · 有定价才算 costtranscript events · systemPromptSha256 · tokens / toolCalls · cost only if pricedverifySystemPrompt → output()有 output 选项就交给它(评分器用),否则就是回答文本an output option shapes the result for judges; otherwise the response textfinallysession.jsonl → PI_SESSION_SNAPSHOT_ARTIFACT · dispose · rm root · 还原环境session.jsonl → PI_SESSION_SNAPSHOT_ARTIFACT · dispose · rm root · restore envpreamble<tools><rules><docs> Pi documentation (read only ……<cwd>preamble<tools><rules>…<cwd>stopReason ∈ { stop, toolUse }否则抛出 errorMessage 或 "unexpected stop reason"otherwise throw errorMessage or "unexpected stop reason"stop && !text → error"Agent run produced no assistant text."getLastAssistantText() ?? ""最后一个 prompt 的回答就是 responsethe last prompt's answer becomes the response须 PI_EVAL_CONTAINER=1 且有沙箱 UID/GID,否则直接报错needs PI_EVAL_CONTAINER=1 and a sandbox UID/GID, or it throwswithout_docswith_docsnamewithout_docswith_docstoolsread…ls (6)read…ls (6)transformexcludePiDocumentation—expect <docs>falsetrue两种变体共用同一个用例定义和同一个评分器。Both variants share one case definition and one judge.已有诊断 → attachHarnessRunToError:带 errors 与耗时的部分结果diagnostics exist → attachHarnessRunToError: a partial run with errors + timings运行与清理都失败 → AggregateErrorrun and cleanup both fail → AggregateError报告据此把这一侧记为 erroredthe report then records that arm as errored系统提示词的分段(coding-agent 的 system-prompt.ts)System prompt sections (coding-agent system-prompt.ts)with_docswithout_docsexcludePiDocumentation 剪掉 <docs>…</docs>;verifySystemPrompt 要求 <rules> 还在、<docs> 有无与变体一致excludePiDocumentation cuts <docs>…</docs>; verifySystemPrompt requires <rules> and a <docs> matching the variant123456781步骤step顺序order只在 with_docswith_docs only

要点Key points

  • 两种变体的唯一区别:without_docs 注册一个隐藏的 before_agent_start 扩展,用 excludePiDocumentation 剪掉系统提示词里的 <docs>…</docs>。The only difference between variants: without_docs registers a hidden before_agent_start extension that cuts <docs>…</docs> out of the system prompt with excludePiDocumentation.
  • 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

  1. host 评测直接用 createPiCodingAgentHarness({ tools, noTools, workspaceFiles, output })。Host evals use createPiCodingAgentHarness({ tools, noTools, workspaceFiles, output }) directly.
  2. 评分器需要的东西放进 output({ response, session, systemPrompt, agentDir }) 的返回值。Put whatever the judge needs into the return value of output({ response, session, systemPrompt, agentDir }).

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/evals/src/harness.tsL272runPiCodingAgent一次运行one run
packages/evals/src/harness.tsL238promptAgent一个 prompt 步骤one prompt step
packages/evals/src/harness.tsL257verifySystemPrompt提示词校验prompt check
packages/evals/src/harness.tsL494excludePiDocumentation剪掉 <docs>cuts <docs>
packages/evals/src/harness.tsL517createPiDocumentationEvalHarnessdocs 评测工厂docs eval factory
09Infra

报告:结果 → 配对 → 比较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.

报告:结果分类、配对、比较与一个算出来的 report.txtThe report: outcomes, pairing, comparison and a computed report.txt
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
The report: classify each arm, then compare in pairs; if any pair is incomplete, no pass rate is publishedreadTaskObservation每个任务一个结果one outcome per taskresolvePair(集, 用例, 模型, 运行序号)(set, case, model, run)每个评测集的比较Comparison per eval setsummarize…formatEvalComparisonReport → report.txt示例数据:2 个评测集 × 3 次运行,第二个集里有一侧出错illustrative data: 2 eval sets × 3 runs, one arm errored in the second seterrorederroredskipped / pendingerrorederroredunscoredscoredvitest.json 读不了vitest.json unreadable断言不是恰好 1 条 / 名字不符not exactly 1 assertion / name mismatchskipped · todo · disabled · pending没有 harness 运行 / 模型不符no harness run / wrong modelfailed 或运行里有错误failed, or run has errors没有分数no score有分数(0 也算)a score (0 counts)每一侧在计划里恰好 1 次each arm planned exactly oncedesign expected 1 run, found N观测数 = 计划数observations = plannedexpected 1 observation, found N结果是 scoredoutcome is scored<variant>: <outcome>全部满足 → Pair { control, treatment }all hold → Pair { control, treatment }否则 → BlockedPair,带上面的原因(红色)otherwise → BlockedPair with the reasons above (red)without_docs = 对照 · with_docs = 实验without_docs = control · with_docs = treatment通过Passscore ≥ 1通过率Pass ratepasses / eligible pairs提升Lifttreatment − control (toPrecision 15)不公布Withheld任何一对被阻塞时if any pair is blockedno-lift两组通过率相同equal pass ratesnegative-delta实验组更低treatment is lowercontrol-saturated对照组 100%control at 100%treatment-saturated实验组 100%treatment at 100%flaky同一用例同一侧跨运行结果不同same case + arm passes in some runs only另有 tokens / tools / latency / cost 的配对均值差plus paired mean deltas for tokens / tools / latency / costDocumentation Eval Comparisons  Add model to existing provider         Pairs  3/3 eligible     Pass rate  +33.3 pp (with 100.0%, without 66.7%)         Flags  treatment-saturated, flaky        Tokens  -3750.0 (with 15373.3, without 19123.3, 3 pairs)         Tools  -4.3 (with 5.3, without 9.7, 3 pairs)       Latency  -13533.3ms (with 31100.0ms, without 44633.3ms, 3 pairs)     Est. cost  -$0.0076 (with $0.0353, without $0.0429, 3 pairs)  Create and use a tool extension         Pairs  2/3 eligible     Pass rate  withheld because pairs are blocked         Flags  flaky        Tokens  -6400.0 (with 23800.0, without 30200.0, 2 pairs)         Tools  -6.0 (with 8.5, without 14.5, 2 pairs)       Latency  -14850.0ms (with 54600.0ms, without 69450.0ms, 2 pairs)     Est. cost  -$0.0143 (with $0.0537, without $0.0681, 2 pairs)  Operational totals    without_docs: 6 runs, 146570 tokens, 71 tools, 339.10s, $0.3301 cost    with_docs: 6 runs, 120120 tokens, 44 tools, 263.50s, $0.2725 cost  Blocked pairs    Create and use a tool extension/creates, reloads, and invokes the extension/      provider/model/run-2: with_docs: errored通过率、提升与标记按 report.ts 的规则从示例数据算出(构建时计算)。有阻塞对时 eval:docs 退出码为 1;report.json 是 schemaVersion 3。Pass rates, lift and flags are computed from the example data with the report.ts rules at build time. With blocked pairs eval:docs exits 1; report.json is schemaVersion 3.数据流data flowscored阻塞 / 出错blocked / errored

要点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, and flaky when 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

  1. 先看 report.txt 的 Pass rate 行;出现 withheld 时到 Blocked pairs 一节找原因。Read the Pass rate line in report.txt first; when it says withheld, the Blocked pairs section gives the reasons.
  2. 机器读取用 report.json(schemaVersion: 3),它带协议摘要,可以和 protocol.json 对上。For machines use report.json (schemaVersion: 3); it carries the protocol digest that matches protocol.json.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/evals/src/report.tsL136readTaskObservation每个任务一个结果one outcome per task
packages/evals/src/report.tsL249resolvePair配对pairing
packages/evals/src/report.tsL336comparisonFlags5 个标记five flags
packages/evals/src/report.tsL359summarizeEvalObservations比较comparison
packages/evals/src/report.tsL436formatEvalComparisonReportreport.txtreport.txt
10Infra

扩展点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.
11Infra

测试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).

12Infra

交叉链接Cross-links#