@ expo/ 代理服务器
以合用地点表示的代理评价 .eval.ts 案例。 每个案件都准备
项目,运行过一次代理,然后通过Vitest报告独立命名的检查.
此软件包正在为其第一次 npm 发布做准备; 以下示例使用
登记名称。 它需要节点 >=22.17 和 Vitest 4.1.0 < 5.
一个自定义的跑者的案子
// refresh.eval.ts
import { createAgentEval, expect } from '@expo/agent-eval-vitest';
import { myToolLoop } from './runner';
import { startReloadFixture } from './fixtures';
const agentEval = createAgentEval({
runner: myToolLoop,
timeoutMs: 900_000,
artifactsDir: '.eval-results',
});
agentEval(
import.meta.url,
{
prompt: 'The app still shows the old screen after my edit. Refresh it.',
projectSetup: {
async prepareAsync({ root, signal, onCleanup }) {
const fixture = await startReloadFixture(root, signal);
onCleanup(() => fixture.stop());
return fixture;
},
},
},
(check) => {
check('runtime receives the reload', (_workspace, { fixture }) => {
expect(fixture.reloadRequests()).toHaveLength(1);
});
check('agent explains the result', (_workspace, { execution }) => {
expect(execution.finalAnswer).toContain('refreshed');
});
}
);
固定装置和跑道装置属于消费者。 返回类型从
prepareAsync 输入回调。 立即清理注册
获取每个资源,包括设置完成前创建的资源.
使用一个专用的 Vitest 配置, 这样昂贵的代理运行与单位测试是分开的 :
// vitest.evals.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['**/*.eval.ts'],
// The package imports Vitest's test/hook API; keep the same module context.
server: { deps: { inline: ['@expo/agent-eval-vitest'] } },
maxWorkers: 1,
testTimeout: 30_000,
hookTimeout: 30_000,
},
});
npx vitest run --config vitest.evals.config.ts
agentEval.skip(...), (中文(简体) ), agentEval.only(...),普通名称过滤,普通
反证断言,以及 skip(note) 在检查中支持。 跳过或
完全过滤的大小写不会启动代理 。 不需要自定义记者 .
timeoutMs 控制设置和代理运行。 每次检查(包括之前的工作)
skip())使用Vitest的单独 testTimeout; 消费钩 hookTimeout。 。 。 .
该套装为安装和清理钩子提供自身的超时服务.
奔跑者和生命周期合同
一个 AgentRunner 接收 { prompt, root, artifactsDir, signal } 返回 :
{
finalAnswer: string | null,
toolCalls: Array<{ id: string; name: string; input: unknown;
result?: unknown; error?: string }> | null,
endReason: 'completed' | 'failed' | 'cancelled' | 'timeout' | 'budget-exhausted',
artifacts: string[], // paths relative to artifactsDir
metadata?: Record<string, unknown>,
}
使用 null 而不是声称没有使用任何工具.
保持证据/元数据JSON的可序列性。 将原始记录抄入 artifactsDir, (中文(简体) ),
不是一次性工作空间。 提供者/工具特定数据可保留在 input, (中文(简体) ),
result,和元数据;检查必须了解其跑者的工具词汇.
拒绝基础设施故障( 缺少可执行性, 无效的协议等) . 当一名特工尝试失败时,退回执行证据。 一个工具 单是非零退出并不意味着基础设施失灵。 检查仍在进行 尝试失败,但未完成的处决使得Vitest套房失败 即使保存支票通过.
设置和跑道必须尊重 signal,停止其资源,并解决中止.
包里有它自己的期限 等待一个取消的限定间隔
按倒序运行注册清理。 原设置/运行者 signal 实值
已中止内部 onCleanup; 不重复用于关闭请求。 部分设置后, 清理也会运行
失败和跑道失败。 处置者共享总计 cleanupTimeoutMs 预算(默认5);
如果一个处置者失败,则仍可援引其后的处置者。 已注册的清理命令
结束 runAsync 接收以清理期限为界的新信号.
runAsync(command, args, { timeoutMs }) 使用毫秒( 默认) 600_000) (中文(简体) ). JavaScript 无法强制终止
任意注入函数,忽略取消。 检查应该遵守
完成了证据,避免开始未登记的同步工作.
默认 agentEval 用途 claudeRunner(),从
@expo/agent-eval-vitest/claude运行已安装的 Claude 代码 CLI
--dangerously-skip-permissions 在临时工作空间。 CLI 认证
必须已经配置。 配置模型为 claudeRunner({ model })· ;
否则,遗留的模型环境会覆盖或 CLI 配置.
适配器记录了分离出 Stdout JSONL 和 stderr 的文物, 正常化工具
电话/结果,等待流程终止和文物冲洗。 这个
发布目标 POSIX 主机用于子进程树取消; Windows child
工艺树不属于该保障的范围.
Ollama 命令执行器
ollamaRunner 运行一个对奥拉玛的有界限的 JSON 命令循环
/api/chat API 密码。模型释放 {"run": ["command", "arg"]} 或者说
{"done": true, "summary": "..."}。使用 JSON 输出,而不是本地 Ollama 工具
电话 呼叫者提供命令执行器,并在
系统提示; 运行者从不暗示暴露一个 shell 或读取工作空间文件.
import { ollamaRunner } from '@expo/agent-eval-vitest/ollama';
const runner = ollamaRunner({
model: 'qwen3:4b', // provision this model before the evaluation
systemPrompt: 'Use the agent CLI to inspect the project. Start with status --json.',
maxTurns: 8,
async runCommand(args, { root, signal }) {
// Your executor validates argv, runs the fixed CLI in root, forwards signal,
// and returns { exitCode, stdout, stderr }. It must settle after cancellation.
return executeAgentCli(args, { cwd: root, signal });
},
});
const agentEval = createAgentEval({ runner, timeoutMs: 20 * 60_000 });
executeAgentCli 上面是消费者拥有的:验证参数,选择可执行文件,
前置取消,并抓取 stdout/stder。 在
系统提示; 跑者添加了 JSON 动作指令. 把分级的检查.
供应 actionSchema 用 Ollama JSON Schema 匹配来限制生成
您的命令界面。 它必须保留跑者 run编号 :done 动作形状.
运行时动作解析和命令处理器验证仍然适用.
主机默认为 OLLAMA_HOST 或者说 http://127.0.0.1:11434· ; host 超过它.
模式总是明确的。 跑者不会启动奥拉玛或拉出模型.
使用 think: false 以禁用对所支持的模型的思维;省略时
服务器/模型默认适用。 默认为 8 个聊天请求, 温度 0, 种子 42, 以及每个 15分钟的截止时间
HTTP请求. 配置评价总数 timeoutMs 单独; 默认
装箱期限可能短于当地推论。 节点 HTTP 避免获取更短
标头超时, 运行者取消等待的 HTTP 中止请求 .
每个命令接收工作空间上下文并中止信号. 非零命令
将出口反馈到模型;将执行器出错作为基础设施拒绝
失败。 断层/断层反馈仅限于 maxOutputChars (每个字段800个)
),而全部结果仍在 execution.toolCalls 财务报告和审定财务报表 ollama.jsonl。 。 。 .
记录记录请求、答复和错误 artifactsDir。 。 。 .
它包含提示和命令输出,因此将其作为评价证据.
错误的模型动作收到纠正反馈并计数到 maxTurns。 。 。 .
耗尽转弯或生成长度返回 budget-exhausted; HTTP 截止日期
返回时 timeout; 取消返回 cancelled. HTTP/protocol 失败拒绝.
包件测试中没有直播模式的呼叫: 它们使用本地的假HTTP
服务器,包括节点下的包装消费者测试.
工作空间助手
工作空间助手( W)read, (中文(简体) ), exists, (中文(简体) ), sourceFiles, (中文(简体) ), source, (中文(简体) ), packageJson, (中文(简体) ), glob(中文(简体) )
和工作空间第一个命名的检查回调 保持其熟悉的形状.
loadAstSupport() 使用已发送的扫描器依赖性; 损坏的剖析器安装
放弃而不是默默跳过 AST 检查 .
展览固定装置
与 createExpoProject数字 :
import { createExpoProject } from '@expo/agent-eval-vitest/expo';
const projectSetup = createExpoProject({
packageName: 'expo-sqlite',
packageRoot: new URL('../../', import.meta.url),
skillDir: new URL('../', import.meta.url),
fixturesDir: new URL('./fixtures/', import.meta.url),
baseDirectory: new URL('./base-app/', import.meta.url),
fixture: 'notes-db',
files: { 'README.md': 'Eval workspace\n' },
async prepareAsync({ runAsync }) {
await runAsync('npm', ['install']);
},
});
上面的路径是说明性的;为您的案件位置解决它们。 已编写
碱基和固定装置必须包含普通文件/目录,而不是同义链接。 构建
本地软件包所需的依赖性必须已经存在
结账 准备后,适配器会设置显示的依赖和链接
node_modules/<package> 前往当地实际的离职。 测试验证真实模块
而不是仅仅阅读依赖声明.
脚手架是明显替代 baseDirectory: 提供精确数据
createExpoAppVersion 和明确的 baseTemplate (也输入模板)
(可复制)。 3.7.0和较新的脚手架通行证
--no-agents-md 防止生成代理指令的条件和
自动曝光克洛德-插座激活. 更古老的版本不接收国旗.
已准备好的固定层和用户钩仍然可能引入自己的代理配置.
在这个初始执行中没有共享脚手架缓存.
对于 with-skill,适配器将包技能复制到克洛德技能中
编程和排除 .evals用于 without-skill,它取消了软件包的技能
(b) 消费者仍然对任何其他技能/配置负责
在他们的基地固定。 核心内容 projectSetup API 无需使用
此适配器; createExpoProject 需要一个 skillDir。 。 。 .
遗产 EXPO_SKILL_EVAL_TIMEOUT (秒数), EXPO_SKILL_EVAL_CONDITION, (中文(简体) ),
EXPO_SKILL_EVAL_DRY, (中文(简体) ), EXPO_SKILL_EVAL_KEEP,以及 EXPO_SKILL_EVAL_MODEL 保留
已支持。 明确的配置胜过环境值. 其他时间
选项使用毫秒.
结果和核查
每一个被执行的案件都写着 .eval-results/<case>-<attempt>/result.json 与
schemaVersion: 1,任务状态,检查计数/结果,执行证据,迅速,
条件、时间和错误 状态区分 passed, (中文(简体) ), failed, (中文(简体) ), error, (中文(简体) ),
ungraded (没有通过检查),和 dry-run干燥种子从不标注为
探员成功 代理/提供者元数据由执行者提供.
继续跟踪一名专门的汇总记者和固定内容指纹
工作;这种结果格式与现有的EAS生产商清单是分开的.
检查失败并不妨碍以后的检查 。 检查复试/重复已禁用;
a 维特表重新运行开始新的案件尝试. keepWorkspace 保留文件用于
检查,但仍停止注册资源。 记录和结果文件
工作空间删除后仍可使用; 管理将其保留在消费者CI中.
bun run build
bun run --cwd packages/agent-eval-vitest test
bun run --cwd packages/agent-eval-vitest test:pack
测试使用假跑者、假CLI可执行文件、固定资源以及实际Vitest 子进程。 装满了烟雾的烟雾测试 用Npm和Bun装入了油布 在Repo以外的项目,在节点下运行Vitest,并检查公共出口 和 TypeScript 推论。 它需要注册访问,但不会发出模式呼叫.
该套装使用Bun的JavaScript捆绑器. @expo/source-scan 是一个私人工作空间
dev 依赖性:其代码包含在生成的无害环境管理中,而 Babel 和 Vitest 则包含在其中
保持对外。 Babel是一个运行时的依赖,而Vitest是一个同行的依赖.
没有源扫描文件被复制到工具包的源树上.
构建生成带有 TypeScript 的声明并使用
rollup-plugin-dts包括私人扫描仪的类型 其类型脚本 6
兼容依赖性提供编译器 API 不在 TypeScript 7 中.
这些只是建造工具;消费者既不需要Bun,也不需要私人工作空间.
bun pm pack 运行此构建通过 prepack (Bun必须安装在建筑物上)
)并用普通版本取代目录/工作空间引用.
用于包装或发布发布;直接 npm pack 未解决
Bun目录. 焦油球包括了JavaScript块,源地图,以及声明.
烟雾检查用Npm和Bun安装 @expo 登记册被屏蔽,
然后进行真正的节点/Vitest检查,并严格地进行NodeNext/Bundler类型的检查,而不进行
skipLibCheck。类型检查包括: ESNext.Disposable,根据Vitest公司的要求
间谍声明.
现场终端覆盖
选入 bun run test:e2e 命令通过Vitest练习真正的奥拉马模型,
独立检查 清理 保存证据 见
端到端测试指南 用于本地设置和单独的咨询 GitHub
动作工作。 默认测试继续运行,没有活模型.