跳转到主要内容
你给 Claude Code 写了一个 Skill,想知道装上它之后,Agent 修 bug 是不是真的更准。这种评估没法只看一段回复:Agent 要读代码、改文件、跑命令,结果体现在仓库里。 Sandbox Agent 就是为这种场景准备的。NiceEval 先创建一个隔离的 Sandbox,把你的项目放进去,再在里面启动 Coding Agent 的 CLI 并交给它任务。Agent 可以随意改文件、跑命令,不会碰到你的机器。结束后,NiceEval 收集对话记录、文件改动和测试结果,供评估用例判分。

内置 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:
第二条命令把同一条评估用例跑 10 次。跑完后用 pnpm exec niceeval show 在终端看结果,或用 pnpm exec niceeval view 在浏览器里看。
Sandbox 只能写在 Experiment 或评估用例里,没有对应的 CLI flag,也没有项目级默认 Provider。两处都没指定 Sandbox 镜像或模板时,NiceEval 在创建任何 Sandbox 之前就报错。
在云端跑 Coding Agent 时,可以直接用 NiceEval 发布的 E2B 公共模板,省掉每个 Attempt 安装 CLI 的时间:
Claude Code 用 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 一样,从工作目录根算起:不带 / 的匹配任意深度的同名项,带 / 的从工作目录根匹配,末尾 / 表示目录。项目自己的 .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。
不要在 Adapter 里直接调用 console.log/error 或写 process.stdout/stderr。它们会打乱终端的运行面板,也会让 CI 日志顺序错乱。 运行中出现 Sandbox 或 Adapter 错误时,终端会给出这个 Attempt 的 locator:
第一条显示完整错误、diagnostic 和已经完成的阶段。第二条显示每个阶段的耗时,能看出错误或超时发生在哪一步。脚本或 CI 要读取同样的信息时,用 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 每次的做法差别很大,跑一次的结果说明不了多少。
各个 Agent 不同的行为都写在它自己的 Adapter 里,不要在其它地方按 Agent 名字写分支判断。