Gemini CLI 行为评估(Behavioral Evals)运行与晋级实战指南:从本地 Vitest 调试到 USUALLY_PASSES 晋级 ALWAYS_PASSES
行为评估(Behavioral Evals)是 Gemini CLI 用来验证「模型是否会做出正确决策」而非「功能是否可用」的测试体系。本文基于仓库内置的 behavioral-evals 技能文档 running.md,系统讲解如何构建评估产物、配置 API 密钥、运行不同策略的评估用例,以及如何通过工具轨迹日志和调试日志定位失败根因,最终掌握从 USUALLY_PASSES 孵化到 ALWAYS_PASSES 晋级的完整晋级流程。
背景:行为评估验证的是什么
与普通集成测试验证「文件写入器是否真的写盘」不同,行为评估验证的是模型是否主动选择了正确的动作(例如:被要求保存代码时,模型是否决定调用写文件工具)。它们也是系统提示词(system prompt)、工具定义等模型引导(model steering)机制变更的关键反馈回路。由于 LLM 行为具有非确定性,Gemini CLI 将评估划分为两个一致性质等级别,这一区分直接决定了后文所有运行与晋级命令的设计:
ALWAYS_PASSES:预期 100% 通过,通常为无歧义的基线行为。运行在每次 CI 中,失败会阻塞 PR 合并;USUALLY_PASSES:预期大多数时候通过,但因依赖非确定性的提示行为而允许偶发抖动。仅在夜间(nightly)孵化运行中执行,用于追踪产品质量趋势。
关于策略、断言编写的完整概念,evals/README.md 是单一事实来源,本文聚焦其中的运行与晋级环节。
前置条件:必须先构建编译产物
行为评估直接运行在编译后的二进制产物上,因此每次修改代码后都必须先完成构建与打包,否则评估测的是过期代码:
npm run build && npm run bundle
对照 package.json 中的脚本定义可以确认,build 对应 node scripts/build.js,而 bundle 是一条较长的组合命令(生成资源、构建 devtools 包、浏览器 MCP 打包、esbuild 打包并复制 bundle 资源)。这解释了为什么文档强调「构建是前置强制步骤」——评估子进程启动的就是这条流水线最终输出的 CLI。
配置环境变量与 API 密钥
评估需要标准的 Gemini API Key。如果 .env 文件中存在多个 key 或注释行,直接 source .env 可能拿到错误的值,文档给出了精确提取的写法:
export GEMINI_API_KEY=$(grep '^GEMINI_API_KEY=' .env | cut -d '=' -f2) && RUN_EVALS=1 npx vitest run --config evals/vitest.config.ts <file_name>
这条命令做了三件事:
grep '^GEMINI_API_KEY='只匹配以该键开头的行,排除注释和其他 key;cut -d '=' -f2截取等号后的值并导出为环境变量;- 附带
RUN_EVALS=1运行指定评估文件。
RUN_EVALS=1 为何是硬性要求
从源码 evals/test-helper.ts 中的 runEval 函数可以看到其底层机制:
} else if (
!process.env['RUN_EVALS'] &&
(policy === 'USUALLY_PASSES' || policy === 'USUALLY_FAILS')
) {
it.skip(name, options, fn);
}
即:未设置 RUN_EVALS 时,所有 USUALLY_PASSES 与 USUALLY_FAILS 策略的用例会被 it.skip 静默跳过。这就是为什么单独运行某个孵化中(incubated)的测试文件时必须显式加上 RUN_EVALS=1,否则会看到「全部 skip」而误以为测试不存在。
运行命令速查
| 命令 | 覆盖范围 | 说明 |
|---|---|---|
npm run test:always_passing_evals |
ALWAYS_PASSES |
快速反馈,运行在 CI 中 |
npm run test:all_evals |
全部 | 夜间孵化测试,脚本内部会设置 RUN_EVALS=1 |
两条命令在 package.json 中的实际定义分别是:
test:always_passing_evals:vitest run --config evals/vitest.config.tstest:all_evals:cross-env RUN_EVALS=1 vitest run --config evals/vitest.config.ts
运行指定评估文件
针对单个功能文件(孵化期测试必须带 RUN_EVALS=1):
RUN_EVALS=1 npx vitest run --config evals/vitest.config.ts my_feature.eval.ts
这里指定 evals/vitest.config.ts 作为配置文件并非多余。该配置(见 evals/vitest.config.ts)为评估场景做了专门定制:
include: ['**/*.eval.ts']:只匹配.eval.ts命名的评估文件,与常规单元测试隔离;testTimeout: 300000:单用例 5 分钟超时——一次真实的 agent 完整执行远比普通单测耗时长;reporters: ['default', 'json'],JSON 报告输出到evals/logs/report.json,供后续聚合分析脚本消费;setupFiles指向 packages/cli/test-setup.ts,复用 CLI 包的测试全局初始化。
调试与日志分析
测试失败时,文档给出两个层次的排查手段。
1. 工具轨迹日志(Tool Trajectory Logs)
每次评估执行后,evals/test-helper.ts 中的 internalEvalTest 会把完整的工具调用序列写入 evals/logs/<test_name>.log(文件名经 prepareLogDir 清洗为小写下划线格式):
await fs.promises.writeFile(
logFile,
JSON.stringify(rig.readToolLogs(), null, 2),
);
失败抛错时,测试框架还会自动把工具调用链摘要(经 formatToolLogChain 格式化)追加到错误信息中,使失败原因与「模型实际做了什么」直接对应。此外,子进程的 stderr 会单独落盘为 <test_name>.stderr.log,活动日志写入 JSONL 活动文件并在成功时自动清理。
2. 详细推理日志(Verbose Reasoning)
需要原始缓冲区追踪(raw buffer traces)时,设置环境变量捕获完整调试日志:
export GEMINI_DEBUG_LOG_FILE="debug.log"
3. API 可靠性与自动重试
从源码还可以看到一层对运行稳定性至关重要的机制:withEvalRetries 会对 500/503 等 API 侧错误自动重试至多 3 次,若持续失败则以「skip 而非 fail」的方式优雅退出,避免基础设施抖动阻塞 PR;每次重试/跳过事件都会同步追加到 evals/logs/api-reliability.jsonl(使用同步文件 I/O 确保进程被 CI 强杀时日志也已落盘)。排查「偶发失败」时应先区分是模型行为问题还是 API 抖动问题,后者的证据就在这份 JSONL 中。
模型定向校验经验
文档在 Model Targeting 小节给出了一条重要经验:标准评估会在多个模型变体上做基准对比。如果一个测试在 Flash 上通过而在 Pro 上失败(或反过来),问题通常出在**工具描述(tool description)**而非提示词定义本身。原因是两类模型的敏感度不同:Flash 对「指令膨胀(instruction bloat)」敏感,Pro 对「意图模糊(ambiguous intent)」敏感。这与 evals/README.md 中「夜间报告 pass rate 显著下滑往往提示近期系统提示词或工具定义变更」的判断方法互为印证。
去抖(Deflaking)与孵化期
为维持 CI 稳定性,所有新评估都要经过严格的孵化期,对应工作流标题 Evals: Nightly,它不阻塞 PR 合并。
1. 孵化:以 USUALLY_PASSES 策略创建
新测试必须这样声明(这是硬性约束):
evalTest('USUALLY_PASSES', { ... })
这些测试只在夜间工作流中运行并持续被监控。Evals: Nightly 每次运行会把同一测试连续执行 3 次、覆盖每个支持模型,按单次执行通过比例记 0% / 33% / 66% / 100%。提交前,测试在包含 Gemini 3.1 Pro、Gemini 3.0 Pro、Gemini 3 Flash 在内的关键模型上应至少达到 66% 分。
本地也可以借助 scripts/deflake.js 对任意测试命令重复执行多轮(--runs 默认 5 次)来预检稳定性,例如:
npm run deflake -- --command="npx vitest run --config evals/vitest.config.ts <file_name>" --runs=3
2. 失败调查:交给 agent 自动排查
夜间评估回归时,通过仓库内置的 behavioral-evals 技能命令交给 agent 调查(可附带一次具体运行记录):
gemini /fix-behavioral-eval [optional-run-uri]
该命令背后的流程(见 fixing.md):拉取夜间工作流结果、审查 evals/logs 中的轨迹日志、优先对 prompt.ts 与工具指令做最小改动(而非改测试本身),然后跨多模型本地复跑验证。配套技能总览见 SKILL.md。
3. 晋级:USUALLY_PASSES → ALWAYS_PASSES
当一个测试在多个夜间周期内达到 100% 一致性后执行晋级命令:
gemini /promote-behavioral-eval
文档特别强调:不要手动晋级。该命令会在更新文件策略前先验证轨迹日志,确保 100% 成功率是被实证而非假设的。
对照 promoting.md 与 evals/README.md 的晋级判据,完整标准比文档中一句「多个夜间周期」更严格:
- 审计夜间日志:从
main分支最近 7 次夜间运行 中汇总结果(汇总摘要会自动整合最近 7 次运行历史),且所有验证仅限本地,不推送、不触发远端运行; - 稳定性判定:候选测试必须在最近 7 次夜间运行中、对所有启用模型每次 3 连跑全部通过(即每个模型每轮都是 3/3);
- 最小化修改:晋级变更只允许修改策略参数为
evalTest('ALWAYS_PASSES', { ... }),严禁顺手重构测试或 fixtures; - 本地复核:晋级后用非交互 Vitest 本地跑一遍被晋级的测试,确认其被标准可运行范围(
npm run test:always_passing_evals)正确拾取; - 报告:说明晋级了哪些测试、附上成功率证据(如 7/7 轮全模型通过);若无达标候选,则列出最接近的候选及其当前通过率。
进阶:PR 回归检查与本地模拟
晋级机制之外,仓库还内置了 PR 级别的高信号回归检查脚本(详见 evals/README.md 的 Regression Check Scripts 一节),可以在推送前本地模拟 CI 行为:
# 对特定模型运行完整回归循环
MODEL_LIST=gemini-3-flash-preview node scripts/run_eval_regression.js
# 仅跑「可信测试」子集(60/80 过滤器:每夜不低于 60% 且 6 日聚合不低于 80%)
OUTPUT=$(node scripts/get_trustworthy_evals.js "gemini-3-flash-preview")
node scripts/run_regression_check.js "gemini-3-flash-preview" "$OUTPUT"
其判定逻辑值得理解:PR 中一个测试只要最多 4 次尝试里成功 2 次即算通过(Best-of-4),且若测试在 main 分支同样失败则标记为 Pre-existing 放行——保证只被「你自己的变更引入的回归」阻塞。评估默认模型由 GEMINI_MODEL 环境变量控制(默认取 PREVIEW_GEMINI_FLASH_MODEL,见 evals/test-helper.ts),本地复现 CI 问题时可通过 --model 或该变量切换模型做交叉验证。
小结
行为评估的运行与晋级链路可以概括为一条清晰的主线:先构建(build + bundle)→ 配好 API Key 与 RUN_EVALS → 按策略选择命令运行 → 失败时用 evals/logs 轨迹日志与 GEMINI_DEBUG_LOG_FILE 定位 → 新测试一律 USUALLY_PASSES 孵化 → 7 轮夜间 100% 稳定后由 /promote-behavioral-eval 实证晋级。这套机制的精髓在于:用「先孵化、后晋级、全程留证」的流程把 LLM 的非确定性关进 CI 的笼子,让提示词与工具定义的每一次变更都有可量化、可追溯的行为反馈。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00