内置 Sandbox Agent
这三个 Coding Agent 可以直接用,只需要准备好对应的 API key:claude-code
运行 Anthropic Claude Code CLI,需要
ANTHROPIC_API_KEY。codex
运行 OpenAI Codex CLI,需要
CODEX_API_KEY。bub
运行 bub coding agent,需要
BUB_API_KEY 与 BUB_API_BASE。运行内置 Agent
先让一个内置 Agent 在本机 Docker 里跑起来。在 Experiment(或评估用例)上设置sandbox,告诉 NiceEval 在哪里创建 Sandbox:
pnpm exec niceeval show 在终端看结果,或用 pnpm exec niceeval view 在浏览器里看。
Sandbox 只能写在 Experiment 或评估用例里,没有对应的 CLI flag,也没有项目级默认 Provider。两处都没指定 Sandbox 镜像或模板时,NiceEval 在创建任何 Sandbox 之前就报错。
NICEEVAL_CLAUDE_CODE_E2B_TEMPLATE,Bub 用 NICEEVAL_BUB_E2B_TEMPLATE。常量的值是带固定版本的完整模板引用,版本随模板里的 Agent 版本更新,你不用自己跟踪。要在模板上加系统包、二进制或模型缓存,见 预制 Sandbox 环境 · 从官方基线继续构建。
内置 Agent 从 niceeval/adapter 导出的是工厂函数。要配置鉴权、代理、MCP 或 GitHub Skill,写进工厂参数。模型仍然写在 Experiment 的 model 字段,Sandbox Provider 仍然写在 sandbox 字段:
Agent 环境变量
运行前确认这些变量已经设置。缺了它们,Agent 在 Sandbox 里无法调用模型。
这些是各 Agent 官方的变量名。CI 里只保存一组共享凭据时,在 Experiment 里用
apiKey、baseUrl 或 apiBase 显式传入。这不会改变 Adapter 默认读取的变量名。
一个 Attempt 依次做什么
排查时先知道每一步发生在哪里。一个 Attempt 按下面的顺序执行:test(t) 里。Agent 运行时只能看到你已经写进 Sandbox 的文件。
t.sandbox.fileChanged() 这类文件断言只看 Agent 在 t.send() 期间改动的文件。NiceEval 在每次 t.send() 前后各记一次工作区状态,中间的变化算在 Agent 头上。你上传的起始文件、t.send() 之后写入的验证文件都不算,所以 fileChanged("src/app.ts") 只在 Agent 真的改过这个文件时通过。
让文件改动能被记下来
文件断言意外失败,或niceeval show @<locator> --diff 里看不到改动时,先对照下面几条。NiceEval 在 Sandbox 里另建一个私有 Git 仓库,每次 t.send() 前后各记一次工作区状态。这需要:
- Sandbox 里有
git和 POSIX shell。 内置 Provider 的默认镜像都有,自定义镜像要自己确认。被测项目本身是不是 Git 仓库不影响:私有仓库放在工作目录之外,不碰你的.git,Agent 也看不到它。 - 文件写在工作目录里。 只记录
sandbox.workdir下的文件。写文件时省略targetDir/cwd;需要绝对路径时读sandbox.workdir,不要写死/workspace。写到工作目录外的文件 Agent 看不到,也记不下来。 - 工作目录里没有第二个 Git 仓库。 被测项目直接放在工作目录根下。工作目录里有 submodule 或另一个 clone 进来的仓库时,Attempt 会报执行错误并列出路径,因为那个仓库里的文件改动会被悄悄漏掉。确实不参与判分的仓库,用
diff.ignore整个排除。 - 改动发生在
t.send()期间。 最后一次t.send()之后写入的文件算作你的验证文件,不计入 Agent 的改动。
.git、node_modules、__pycache__、Python 虚拟环境、常见构建输出和包管理器缓存默认不记录,否则一次 npm install 就可能产生几万个文件。要调整,写评估用例的 diff 字段:
/ 的匹配任意深度的同名项,带 / 的从工作目录根匹配,末尾 / 表示目录。项目自己的 .gitignore 不参与判断,被项目忽略的文件照常记录。排除清单在第一次记录时就定下来,Agent 之后改 .gitignore 不影响它。
每次 t.send() 前后最多扫描 100,000 个路径、256 MiB 去重后的文本。改动最多保留 10,000 条;更多时只保留前面固定的一部分,并标为 partial(原因 collection-cap-reached)。只有超过扫描上限才报执行错误,不会假装“一个文件都没改”让文件断言误判通过。工作区特别大时,用 diff.include 或 diff.ignore 缩小范围。
Experiment、评估组、评估用例和 Sandbox Agent 都可以添加只运行命令的 .before() 准备步骤。Agent 自己写 .env 时,把这一步放在 Agent 的 sandbox 字段,并设一个较大的 changeFrequency,让固定的工具和 Fixture 排在前面。写法和缓存规则见 Sandbox · 按顺序准备 Sandbox。
自定义 Sandbox Agent
要评估内置之外的 Coding Agent,用defineSandboxAgent 自己写一个。下面的 Agent 在 Sandbox 里运行 my-agent CLI,读回它写出的对话记录,再翻译成事件:
createNpmCliInstaller 在宿主机下载 npm 包、传进 Sandbox,再用 npm 安装。所以 Sandbox 里需要 Node.js 和 npm;只有自带运行时的 platformPackage 不需要。完整步骤见为自定义 Sandbox Agent 安装 CLI。
setup、每次 send 和 teardown 都有各自的反馈方法,用它们告诉你运行到了哪里、出了什么问题:
ctx.progress({ message, current?, total? })显示当前在做什么,适合安装 CLI、运行一轮、读取对话记录这类步骤。不要每个 token 或每行 JSONL 都调用一次。ctx.diagnostic({ code, level, message, data?, dedupeKey? })留下需要事后查看的问题,例如输出格式不完整、对话记录缺失、清理失败。它会显示在终端,并和这个 Attempt 的结果一起保存。- 无法继续时直接抛错。NiceEval 会保存出错阶段、错误码、错误信息、cause 和 stack,并把这个 Attempt 判为
errored。
console.log/error 或写 process.stdout/stderr。它们会打乱终端的运行面板,也会让 CI 日志顺序错乱。
运行中出现 Sandbox 或 Adapter 错误时,终端会给出这个 Attempt 的 locator:
pnpm --silent exec niceeval query discover 找到 Attempt trace 请求,再用 query run 执行。
步骤很多时,阶段树超过 80 个细节节点后只保留失败的、慢的和首尾几项,并提示省略了多少。要逐项核对,用 query 读取完整的 trace。Sandbox 创建失败可能发生在 trace 开始采集之前,所以错误信息不依赖 trace,show @<locator> 总能看到。
把模型和 flags 传给 CLI
Experiment 里写的model 和 flags 会出现在 ctx.model 和 ctx.flags 上。Adapter 把它们转成 CLI 参数或请求体,Agent 才会真的换模型或切换配置。
在 Experiment 中使用自定义 Agent
写好的 Agent 和内置 Agent 一样放进 Experiment:attempts: 3 让每条评估用例跑 3 次。Coding Agent 每次的做法差别很大,跑一次的结果说明不了多少。