niceeval 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -12
- package/README.zh.md +8 -9
- package/docs-site/.mintignore +13 -0
- package/docs-site/AGENTS.md +46 -0
- package/docs-site/concepts/adapter.mdx +287 -0
- package/docs-site/concepts/assert.mdx +243 -0
- package/docs-site/concepts/drive.mdx +118 -0
- package/docs-site/concepts/evals.mdx +108 -0
- package/docs-site/concepts/experiment.mdx +37 -0
- package/docs-site/concepts/hitl.mdx +83 -0
- package/docs-site/concepts/judge.mdx +129 -0
- package/docs-site/concepts/overview.mdx +98 -0
- package/docs-site/concepts/tier.mdx +42 -0
- package/docs-site/docs-ref/00-index.md +23 -0
- package/docs-site/docs-ref/01-page-types.md +43 -0
- package/docs-site/docs-ref/03-style-rules.md +45 -0
- package/docs-site/docs-ref/04-code-examples.md +37 -0
- package/docs-site/docs-ref/05-dx-failure-paths.md +45 -0
- package/docs-site/docs-ref/06-checklists.md +53 -0
- package/docs-site/docs.json +256 -0
- package/docs-site/example/ai-agent-application.mdx +148 -0
- package/docs-site/example/claude-code-codex-plugin.mdx +162 -0
- package/docs-site/example/claude-code-codex-skill.mdx +147 -0
- package/docs-site/example/showcase.mdx +39 -0
- package/docs-site/example/tier1-ai-sdk-v7.mdx +136 -0
- package/docs-site/example/tier1-claude-sdk.mdx +119 -0
- package/docs-site/example/tier1-codex-sdk.mdx +111 -0
- package/docs-site/example/tier1-langgraph.mdx +119 -0
- package/docs-site/example/tier1-pi-sdk.mdx +119 -0
- package/docs-site/favicon.svg +5 -0
- package/docs-site/github-diff.css +135 -0
- package/docs-site/github-diff.js +11 -0
- package/docs-site/guides/authoring.mdx +129 -0
- package/docs-site/guides/ci-integration.mdx +97 -0
- package/docs-site/guides/connect-otel.mdx +209 -0
- package/docs-site/guides/connect-your-agent.mdx +221 -0
- package/docs-site/guides/dataset-fanout.mdx +98 -0
- package/docs-site/guides/experiments.mdx +71 -0
- package/docs-site/guides/fixtures.mdx +147 -0
- package/docs-site/guides/reporters.mdx +113 -0
- package/docs-site/guides/runner.mdx +79 -0
- package/docs-site/guides/sandbox-agent.mdx +138 -0
- package/docs-site/guides/sandbox-backends.mdx +64 -0
- package/docs-site/guides/scoring-guide.mdx +92 -0
- package/docs-site/guides/viewing-results.mdx +195 -0
- package/docs-site/guides/write-experiment.mdx +81 -0
- package/docs-site/guides/write-send.mdx +347 -0
- package/docs-site/images/adapter-tiers-zh.svg +60 -0
- package/docs-site/images/agent-turn-roundtrip-en.svg +62 -0
- package/docs-site/images/agent-turn-roundtrip-zh.svg +77 -0
- package/docs-site/images/hitl-handshake-zh.svg +89 -0
- package/docs-site/images/logo.svg +6 -0
- package/docs-site/index.mdx +181 -0
- package/docs-site/introduction.mdx +141 -0
- package/docs-site/quickstart.mdx +136 -0
- package/docs-site/reference/builtin-agents.mdx +161 -0
- package/docs-site/reference/capabilities.mdx +76 -0
- package/docs-site/reference/cli.mdx +120 -0
- package/docs-site/reference/define-agent.mdx +168 -0
- package/docs-site/reference/define-config.mdx +41 -0
- package/docs-site/reference/define-eval.mdx +160 -0
- package/docs-site/reference/events.mdx +131 -0
- package/docs-site/reference/expect.mdx +112 -0
- package/docs-site/tracker.js +8 -0
- package/docs-site/zh/concepts/adapter.mdx +286 -0
- package/docs-site/zh/concepts/assert.mdx +243 -0
- package/docs-site/zh/concepts/drive.mdx +118 -0
- package/docs-site/zh/concepts/evals.mdx +124 -0
- package/docs-site/zh/concepts/experiment.mdx +37 -0
- package/docs-site/zh/concepts/hitl.mdx +83 -0
- package/docs-site/zh/concepts/judge.mdx +130 -0
- package/docs-site/zh/concepts/overview.mdx +109 -0
- package/docs-site/zh/concepts/tier.mdx +44 -0
- package/docs-site/zh/example/ai-agent-application.mdx +152 -0
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +163 -0
- package/docs-site/zh/example/claude-code-codex-skill.mdx +147 -0
- package/docs-site/zh/example/showcase.mdx +39 -0
- package/docs-site/zh/example/tier1-ai-sdk-v7.mdx +140 -0
- package/docs-site/zh/example/tier1-claude-sdk.mdx +122 -0
- package/docs-site/zh/example/tier1-codex-sdk.mdx +117 -0
- package/docs-site/zh/example/tier1-langgraph.mdx +123 -0
- package/docs-site/zh/example/tier1-pi-sdk.mdx +122 -0
- package/docs-site/zh/guides/agent-feedback-loop.mdx +79 -0
- package/docs-site/zh/guides/authoring.mdx +129 -0
- package/docs-site/zh/guides/ci-integration.mdx +97 -0
- package/docs-site/zh/guides/connect-otel.mdx +208 -0
- package/docs-site/zh/guides/connect-your-agent.mdx +226 -0
- package/docs-site/zh/guides/dataset-fanout.mdx +83 -0
- package/docs-site/zh/guides/experiments.mdx +72 -0
- package/docs-site/zh/guides/fixtures.mdx +147 -0
- package/docs-site/zh/guides/official-adapters.mdx +132 -0
- package/docs-site/zh/guides/reporters.mdx +113 -0
- package/docs-site/zh/guides/runner.mdx +82 -0
- package/docs-site/zh/guides/sandbox-agent.mdx +139 -0
- package/docs-site/zh/guides/sandbox-backends.mdx +75 -0
- package/docs-site/zh/guides/scoring-guide.mdx +92 -0
- package/docs-site/zh/guides/viewing-results.mdx +195 -0
- package/docs-site/zh/guides/write-experiment.mdx +101 -0
- package/docs-site/zh/guides/write-send.mdx +353 -0
- package/docs-site/zh/index.mdx +180 -0
- package/docs-site/zh/introduction.mdx +140 -0
- package/docs-site/zh/quickstart.mdx +137 -0
- package/docs-site/zh/reference/builtin-agents.mdx +364 -0
- package/docs-site/zh/reference/capabilities.mdx +76 -0
- package/docs-site/zh/reference/cli.mdx +140 -0
- package/docs-site/zh/reference/define-agent.mdx +461 -0
- package/docs-site/zh/reference/define-config.mdx +107 -0
- package/docs-site/zh/reference/define-eval.mdx +674 -0
- package/docs-site/zh/reference/events.mdx +252 -0
- package/docs-site/zh/reference/expect.mdx +208 -0
- package/package.json +3 -2
- package/src/agents/bub.ts +1 -1
- package/src/agents/claude-code.ts +8 -5
- package/src/agents/codex.ts +9 -5
- package/src/agents/sdk-streams.test.ts +4 -4
- package/src/agents/sdk-streams.ts +14 -9
- package/src/agents/types.ts +40 -1
- package/src/agents/ui-message-stream.ts +1 -0
- package/src/cli.ts +40 -10
- package/src/context/types.ts +140 -0
- package/src/expect/index.ts +15 -8
- package/src/i18n/en.ts +20 -6
- package/src/i18n/zh-CN.ts +20 -6
- package/src/o11y/parsers/bub.test.ts +71 -0
- package/src/o11y/parsers/bub.ts +5 -0
- package/src/o11y/types.ts +27 -2
- package/src/runner/attempt.ts +2 -1
- package/src/runner/reporters/artifacts.ts +11 -3
- package/src/runner/reporters/live.ts +45 -8
- package/src/runner/run.test.ts +50 -0
- package/src/runner/run.ts +135 -41
- package/src/runner/types.ts +55 -2
- package/src/sandbox/types.ts +18 -0
- package/src/scoring/types.ts +5 -0
- package/src/shared/types.ts +3 -0
- package/src/util.test.ts +26 -1
- package/src/util.ts +16 -0
- package/src/view/app/App.tsx +2 -2
- package/src/view/app/components/AttemptModal.tsx +2 -0
- package/src/view/app/components/CopyControls.tsx +87 -28
- package/src/view/app/i18n.ts +3 -3
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +18 -18
- package/docs/README.md +0 -120
- package/docs/adapters/README.md +0 -60
- package/docs/adapters/authoring.md +0 -179
- package/docs/adapters/coding-agent-skills-plugins.md +0 -324
- package/docs/adapters/collection.md +0 -128
- package/docs/adapters/contract.md +0 -264
- package/docs/adapters/reference/agent-eval.md +0 -215
- package/docs/adapters/reference/agent-loop-apis.md +0 -69
- package/docs/adapters/reference/claude-code-otel-telemetry.md +0 -100
- package/docs/adapters/reference/eve-protocol.md +0 -127
- package/docs/adapters/reference/otel-genai.md +0 -107
- package/docs/adapters/reference/otel-instrumentation.md +0 -65
- package/docs/adapters/targets.md +0 -99
- package/docs/architecture.md +0 -140
- package/docs/assertions.md +0 -387
- package/docs/capabilities-by-construction.md +0 -48
- package/docs/cli.md +0 -173
- package/docs/concepts.md +0 -106
- package/docs/e2e-ci.md +0 -332
- package/docs/eval-authoring.md +0 -319
- package/docs/experiments.md +0 -157
- package/docs/getting-started.md +0 -224
- package/docs/multi-agent.md +0 -145
- package/docs/observability.md +0 -337
- package/docs/origin-integration.md +0 -195
- package/docs/references.md +0 -35
- package/docs/reports.md +0 -551
- package/docs/results-format.md +0 -228
- package/docs/results-lib.md +0 -191
- package/docs/runner.md +0 -104
- package/docs/sandbox.md +0 -276
- package/docs/scoring.md +0 -119
- package/docs/source-map.md +0 -126
- package/docs/tier-sync.md +0 -193
- package/docs/view.md +0 -194
- package/docs/vision.md +0 -88
package/src/agents/types.ts
CHANGED
|
@@ -143,10 +143,23 @@ export interface AgentSession {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
export interface AgentContext {
|
|
146
|
+
/**
|
|
147
|
+
* 软取消信号:合并了 attempt 超时、run 级中断(用户 Ctrl+C)与 eval 自身的中断请求
|
|
148
|
+
* (见 src/runner/attempt.ts)。adapter 可以选择性检查它(或直接传给 `fetch`)以提前
|
|
149
|
+
* 优雅退出,但这不是唯一的硬边界——即便 adapter 完全忽略它,运行器也会用
|
|
150
|
+
* `Effect.timeoutTo` 兜底强制收尾(停沙箱容器)。
|
|
151
|
+
*/
|
|
146
152
|
readonly signal: AbortSignal;
|
|
153
|
+
/** 本次 attempt 用的模型名,透传自 experiment 的 `model` 字段。sandbox 型 agent 通常在 setup 里用它写配置,remote 型通常在 send 里用它选模型。 */
|
|
147
154
|
readonly model?: string;
|
|
148
155
|
/** 模型推理努力程度;归属同 model——实验决定,省略时不覆盖 agent 原生默认。 */
|
|
149
156
|
readonly reasoningEffort?: string;
|
|
157
|
+
/**
|
|
158
|
+
* experiment 的 `flags` 字段原样透传,内容和结构完全由 experiment 作者自定义
|
|
159
|
+
* (如 `{ webResearch: true }`、`{ systemPrompt: "..." }`)。adapter 按自己的约定
|
|
160
|
+
* 读取其中的字段;框架本身不解释、不校验它的内容。与 CLI 解析出的同名 `flags`
|
|
161
|
+
* (跑法层面的 --timeout/--budget 等)是两个不相关的概念。
|
|
162
|
+
*/
|
|
150
163
|
readonly flags: Readonly<Record<string, unknown>>;
|
|
151
164
|
/** 仅沙箱型 agent 有(运行器按 --sandbox 备好)。 */
|
|
152
165
|
readonly sandbox: Sandbox;
|
|
@@ -160,6 +173,12 @@ export interface AgentContext {
|
|
|
160
173
|
* (defineConfig({ telemetry: { port } }))固定的,不从这里传。
|
|
161
174
|
*/
|
|
162
175
|
readonly telemetry?: Telemetry;
|
|
176
|
+
/**
|
|
177
|
+
* 写一行进运行器自身的进度日志(落 stderr,或调用方传入的 `onProgress` 回调)。
|
|
178
|
+
* 超时失败时,最近若干行会并入结果的 error 信息,方便定位卡在哪一步。
|
|
179
|
+
* 与 `Sandbox.appendLog` 是两回事:那个是把一行写进容器自己的原生日志
|
|
180
|
+
* (`docker logs` 能看到),这里只是运行器的观测通道。
|
|
181
|
+
*/
|
|
163
182
|
log(msg: string): void;
|
|
164
183
|
}
|
|
165
184
|
|
|
@@ -198,23 +217,43 @@ export interface Agent {
|
|
|
198
217
|
teardown?: AgentTeardown;
|
|
199
218
|
}
|
|
200
219
|
|
|
220
|
+
/** `defineSandboxAgent()` 的入参形状(见 src/define.ts)——`kind: "sandbox"` 由 define 固定填入,不由用户声明。 */
|
|
201
221
|
export interface SandboxAgentDef {
|
|
222
|
+
/** agent 的显示名/标识,原样进入 `Agent.name`——不是注册表查找 key,只用于展示、结果归属与去重指纹。 */
|
|
202
223
|
name: string;
|
|
203
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* 每个沙箱一次(不是每轮一次):装 CLI、写 config.toml / 鉴权配置(model/base/auth 等
|
|
226
|
+
* 本轮内不变的东西)。运行器在沙箱备好(上传/基线/eval.setup 之后)、第一次 send 前
|
|
227
|
+
* 调用一次。可返回一个 cleanup 闭包,与 `teardown` 一起在 finally 里跑。
|
|
228
|
+
*/
|
|
204
229
|
setup?: AgentSetup;
|
|
205
230
|
/** OTLP 导出配置:沙箱里怎么让 CLI 把 trace 发到 endpoint(env / 配置文件),从 setup 拆出。 */
|
|
206
231
|
tracing?: AgentTracing;
|
|
232
|
+
/** 原生 span → canonical 的薄 mapper;省略走通用 heuristic。只影响瀑布图。 */
|
|
207
233
|
spanMapper?: SpanMapper;
|
|
208
234
|
/** 每轮一次:跑 prompt(fresh / resume)+ 解析成 events。 */
|
|
209
235
|
send(input: TurnInput, ctx: AgentContext): Promise<Turn>;
|
|
236
|
+
/** 沙箱销毁前的清理(与 setup 返回的 cleanup 一起、都在 finally 里跑一次)。 */
|
|
210
237
|
teardown?: AgentTeardown;
|
|
211
238
|
}
|
|
212
239
|
|
|
240
|
+
/** `defineAgent()` 的入参形状(见 src/define.ts)——`kind: "remote"` 由 define 固定填入,不由用户声明。 */
|
|
213
241
|
export interface RemoteAgentDef {
|
|
242
|
+
/** agent 的显示名/标识,原样进入 `Agent.name`——不是注册表查找 key,只用于展示、结果归属与去重指纹。 */
|
|
214
243
|
name: string;
|
|
244
|
+
/**
|
|
245
|
+
* 每个 attempt 一次(remote agent 没有真实沙箱,运行器会传入一个仅含 `workdir`/`sandboxId`/
|
|
246
|
+
* `otlpHost`/`stop` 等元信息的 stub `Sandbox`,其余方法调用即抛错——不要在这里调用
|
|
247
|
+
* 文件/命令类沙箱方法)。常用于建立连接、鉴权等一次性准备。可返回一个 cleanup 闭包,
|
|
248
|
+
* 与 `teardown` 都在 finally 里跑。
|
|
249
|
+
*/
|
|
215
250
|
setup?: AgentSetup;
|
|
251
|
+
/** OTLP 导出配置:远程被测对象怎么把 trace 发到 endpoint(env-based 注入 / file-based 配置)。 */
|
|
216
252
|
tracing?: AgentTracing;
|
|
253
|
+
/** 原生 span → canonical 的薄 mapper;省略走通用 heuristic。只影响瀑布图。 */
|
|
217
254
|
spanMapper?: SpanMapper;
|
|
255
|
+
/** 每轮一次:把一轮 prompt 发给远程被测对象(HTTP/SDK 等),解析响应成 events。 */
|
|
218
256
|
send(input: TurnInput, ctx: AgentContext): Promise<Turn>;
|
|
257
|
+
/** 运行结束前的清理(与 setup 返回的 cleanup 一起、都在 finally 里跑一次)。 */
|
|
219
258
|
teardown?: AgentTeardown;
|
|
220
259
|
}
|
package/src/cli.ts
CHANGED
|
@@ -11,7 +11,9 @@ import { pathToFileURL } from "node:url";
|
|
|
11
11
|
import { parseArgs as nodeParseArgs } from "node:util";
|
|
12
12
|
import { discoverEvals, discoverExperiments, makeFilter } from "./runner/discover.ts";
|
|
13
13
|
import { runEvals, type AgentRun } from "./runner/run.ts";
|
|
14
|
+
import { runWho } from "./runner/types.ts";
|
|
14
15
|
import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
|
|
16
|
+
import { evalLevelStats } from "./shared/outcome.ts";
|
|
15
17
|
import { sandboxRecommendedConcurrency } from "./sandbox/resolve.ts";
|
|
16
18
|
import { Console as ConsoleReporter } from "./runner/reporters/console.ts";
|
|
17
19
|
import { JUnit } from "./runner/reporters/json.ts";
|
|
@@ -19,7 +21,7 @@ import { Live as LiveReporter, type LiveRow } from "./runner/reporters/live.ts";
|
|
|
19
21
|
import { Artifacts as ArtifactsReporter } from "./runner/reporters/artifacts.ts";
|
|
20
22
|
import { buildView, startViewServer, loadLatestResultsPerEval, IncompatibleResultsError } from "./view/index.ts";
|
|
21
23
|
import { t } from "./i18n/index.ts";
|
|
22
|
-
import { formatThrown } from "./util.ts";
|
|
24
|
+
import { formatThrown, upsertManagedBlock } from "./util.ts";
|
|
23
25
|
import type { Config, DiscoveredExperiment, Reporter } from "./types.ts";
|
|
24
26
|
|
|
25
27
|
/** `niceeval view <summary.json>` 指向版本不同的报告时:打印 npx 提示后退出,不抛堆栈。 */
|
|
@@ -182,6 +184,19 @@ async function loadConfig(cwd: string): Promise<Config> {
|
|
|
182
184
|
return mod.default;
|
|
183
185
|
}
|
|
184
186
|
|
|
187
|
+
// AGENTS.md 托管区块:告诉在这个项目里干活的 coding agent「niceeval 不在你的训练数据里,
|
|
188
|
+
// 先读随包文档,跑完读结构化结果」。init 时写入/刷新;标记之外的用户内容永不触碰。
|
|
189
|
+
const AGENT_RULES_BEGIN = "<!-- BEGIN:niceeval-agent-rules -->";
|
|
190
|
+
const AGENT_RULES_END = "<!-- END:niceeval-agent-rules -->";
|
|
191
|
+
const AGENT_RULES_CONTENT = [
|
|
192
|
+
"# niceeval is NOT in your training data",
|
|
193
|
+
"",
|
|
194
|
+
"Its APIs and conventions may differ from anything you have seen. Read the relevant",
|
|
195
|
+
"guide in `node_modules/niceeval/docs-site/` (English at the top level, Chinese under",
|
|
196
|
+
"`zh/`) before writing any eval, experiment, adapter, or niceeval config. After a run,",
|
|
197
|
+
"read the `summary.json` path the CLI prints for structured results.",
|
|
198
|
+
].join("\n");
|
|
199
|
+
|
|
185
200
|
async function initProject(cwd: string): Promise<void> {
|
|
186
201
|
await mkdir(join(cwd, "evals"), { recursive: true });
|
|
187
202
|
const configPath = join(cwd, "niceeval.config.ts");
|
|
@@ -199,6 +214,10 @@ async function initProject(cwd: string): Promise<void> {
|
|
|
199
214
|
"utf-8",
|
|
200
215
|
);
|
|
201
216
|
}
|
|
217
|
+
const agentsPath = join(cwd, "AGENTS.md");
|
|
218
|
+
const existing = existsSync(agentsPath) ? await readFile(agentsPath, "utf-8") : "";
|
|
219
|
+
const next = upsertManagedBlock(existing, AGENT_RULES_BEGIN, AGENT_RULES_END, AGENT_RULES_CONTENT);
|
|
220
|
+
if (next !== existing) await writeFile(agentsPath, next, "utf-8");
|
|
202
221
|
}
|
|
203
222
|
|
|
204
223
|
async function openBrowser(url: string): Promise<boolean> {
|
|
@@ -289,7 +308,6 @@ async function main(): Promise<void> {
|
|
|
289
308
|
}
|
|
290
309
|
|
|
291
310
|
const agentRuns: AgentRun[] = [];
|
|
292
|
-
let expMaxConcurrency: number | undefined;
|
|
293
311
|
|
|
294
312
|
if (command === "exp") {
|
|
295
313
|
if (flags.agent || flags.model) {
|
|
@@ -324,10 +342,11 @@ async function main(): Promise<void> {
|
|
|
324
342
|
evalFilter: evalsFilterFromExperiment(exp.evals, extraPatterns),
|
|
325
343
|
experimentId: exp.id,
|
|
326
344
|
strict: flags.strict,
|
|
345
|
+
// 实验级并发上限:随 AgentRun 进调度器按实验单独限流(runner 两级信号量),
|
|
346
|
+
// 不再取所有选中实验的最小值钳全局——那会让一个串行实验拖慢整批基线。
|
|
347
|
+
maxConcurrency: exp.maxConcurrency,
|
|
327
348
|
});
|
|
328
349
|
}
|
|
329
|
-
const vals = selected.map((e) => e.maxConcurrency).filter((v): v is number => v !== undefined);
|
|
330
|
-
if (vals.length > 0) expMaxConcurrency = Math.min(...vals);
|
|
331
350
|
} else {
|
|
332
351
|
// 裸 run / `niceeval <eval>` 不再执行。运行配置必须来自 experiments/,
|
|
333
352
|
// 这样 agent/model/flags/runs/budget 与结果聚合都有可签入的身份。
|
|
@@ -372,9 +391,9 @@ async function main(): Promise<void> {
|
|
|
372
391
|
// TTY 模式:用 live display 替换 Console reporter,把 attempt log 路由到状态表行尾
|
|
373
392
|
const liveRows: LiveRow[] = [];
|
|
374
393
|
for (const agentRun of agentRuns) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
394
|
+
// who 必须与 attempt.ts 的进度上报同源(runWho):曾用 agent/model,同 agent 同 model
|
|
395
|
+
// 的实验变体(xxx 与 xxx--agents-md)会被折叠成一行,0/2 看起来像同一 eval 跑两次。
|
|
396
|
+
const who = runWho(agentRun);
|
|
378
397
|
const matched = evals.filter((e) => agentRun.evalFilter(e.id));
|
|
379
398
|
for (const evalDef of matched) {
|
|
380
399
|
liveRows.push({ evalId: evalDef.id, who, total: agentRun.runs });
|
|
@@ -388,7 +407,8 @@ async function main(): Promise<void> {
|
|
|
388
407
|
reporters.push(ConsoleReporter());
|
|
389
408
|
}
|
|
390
409
|
}
|
|
391
|
-
|
|
410
|
+
const artifacts = ArtifactsReporter();
|
|
411
|
+
reporters.push(artifacts);
|
|
392
412
|
if (flags.junit) reporters.push(JUnit(flags.junit));
|
|
393
413
|
reporters.push(...(config.reporters ?? []));
|
|
394
414
|
|
|
@@ -445,7 +465,6 @@ async function main(): Promise<void> {
|
|
|
445
465
|
maxConcurrency:
|
|
446
466
|
flags.maxConcurrency ??
|
|
447
467
|
envNumber("NICEEVAL_MAX_CONCURRENCY") ??
|
|
448
|
-
expMaxConcurrency ??
|
|
449
468
|
config.maxConcurrency ??
|
|
450
469
|
sandboxDefaultConcurrency,
|
|
451
470
|
signal: ctrl.signal,
|
|
@@ -457,7 +476,18 @@ async function main(): Promise<void> {
|
|
|
457
476
|
// 跑顺利时登记表已空,是 no-op。
|
|
458
477
|
await stopAllSandboxes();
|
|
459
478
|
|
|
460
|
-
|
|
479
|
+
// agent 反馈闭环的入口:跑完直接给出结构化结果路径,agent 读 summary.json 与各
|
|
480
|
+
// attempt 的工件(events/trace/diff),不必解析人类向的流式输出。--quiet 下也输出。
|
|
481
|
+
if (artifacts.outputDir()) {
|
|
482
|
+
process.stdout.write(t("cli.resultsPath", { path: join(artifacts.outputDir(), "summary.json") }));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 退出码按 eval 级判决,不按 attempt:summary.failed/errored 统计的是每次 attempt,
|
|
486
|
+
// 被 runs+earlyExit 重试吸收的失败(先挂一次、后来过了)不该把进程判红——否则
|
|
487
|
+
// 「runs 吸收单次抖动」在 CI 退出码这层永远不成立。折叠口径与报表/view 共用
|
|
488
|
+
// foldEvalOutcome(任一轮通过 → 该 eval 通过),粒度 experimentId|eval id。
|
|
489
|
+
const stats = evalLevelStats(summary.results, (r) => `${r.experimentId ?? ""}|${r.id}`);
|
|
490
|
+
const failedExit = stats.failed > 0 || stats.errored > 0;
|
|
461
491
|
process.exit(failedExit ? 1 : 0);
|
|
462
492
|
}
|
|
463
493
|
|
package/src/context/types.ts
CHANGED
|
@@ -13,34 +13,59 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
/** t.send() 返回的句柄:从事件流派生便利字段 + expectOk。 */
|
|
15
15
|
export interface TurnHandle {
|
|
16
|
+
/** 本轮的原始事件流(工具调用、消息增量等);下面的派生字段都算自它。 */
|
|
16
17
|
readonly events: StreamEvent[];
|
|
18
|
+
/** 本轮内被调用的工具列表,从 events 派生。 */
|
|
17
19
|
readonly toolCalls: readonly ToolCall[];
|
|
20
|
+
/** 本轮结束状态:"completed" 正常结束、"failed" 出错、"waiting" 卡在 HITL 输入请求上。 */
|
|
18
21
|
readonly status: "completed" | "failed" | "waiting";
|
|
22
|
+
/** 本轮助手最终文本回复(events 里的消息增量拼接结果)。 */
|
|
19
23
|
readonly message: string;
|
|
24
|
+
/** adapter 附带的结构化输出(如有),供 outputEquals / outputMatches 比对。 */
|
|
20
25
|
readonly data?: unknown;
|
|
26
|
+
/** 本轮 token 用量与估算成本(仅上报了 usage 的 agent 才有)。 */
|
|
21
27
|
readonly usage?: Usage;
|
|
22
28
|
/** 上一轮若 failed 则抛(中止后续)。 */
|
|
23
29
|
expectOk(): TurnHandle;
|
|
30
|
+
/** 断言 data 与给定值深度相等。 */
|
|
24
31
|
outputEquals(value: unknown): AssertionHandle;
|
|
32
|
+
/** 断言 data 满足给定 schema(如 zod schema)。 */
|
|
25
33
|
outputMatches(schema: unknown): AssertionHandle;
|
|
26
34
|
/** 断言本轮助手回复包含 token(仅限本轮事件流,不跨轮)。 */
|
|
27
35
|
messageIncludes(token: string | RegExp): AssertionHandle;
|
|
36
|
+
/** 断言本轮 status 为 "completed"。 */
|
|
28
37
|
succeeded(): AssertionHandle;
|
|
38
|
+
/** 断言本轮卡在 HITL 输入请求上(status 为 "waiting")。 */
|
|
29
39
|
parked(): AssertionHandle;
|
|
40
|
+
/** 断言本轮调用过指定名字的工具;`match` 可进一步约束入参 / 次数 / 状态。 */
|
|
30
41
|
calledTool(name: string, match?: ToolMatch): AssertionHandle;
|
|
42
|
+
/** 断言本轮未调用指定工具(或未按 match 条件调用)。 */
|
|
31
43
|
notCalledTool(name: string, match?: ToolMatch): AssertionHandle;
|
|
44
|
+
/** 断言本轮工具调用按给定顺序出现(允许中间夹杂其它调用)。 */
|
|
32
45
|
toolOrder(names: string[]): AssertionHandle;
|
|
46
|
+
/** 断言本轮未调用任何工具。 */
|
|
33
47
|
usedNoTools(): AssertionHandle;
|
|
48
|
+
/** 断言本轮工具调用总数不超过 max。 */
|
|
34
49
|
maxToolCalls(max: number): AssertionHandle;
|
|
50
|
+
/** 断言本轮加载过指定 skill。 */
|
|
35
51
|
loadedSkill(skill: string): AssertionHandle;
|
|
52
|
+
/** 断言本轮没有失败的工具调用 / 命令。 */
|
|
36
53
|
noFailedActions(): AssertionHandle;
|
|
54
|
+
/** 断言本轮出现过指定类型的事件;`opts.count` 可约束出现次数。 */
|
|
37
55
|
event(type: StreamEvent["type"], opts?: { count?: number }): AssertionHandle;
|
|
56
|
+
/** 断言本轮未出现指定类型的事件。 */
|
|
38
57
|
notEvent(type: StreamEvent["type"]): AssertionHandle;
|
|
58
|
+
/** 断言本轮调用过指定名字的子 agent;`match` 可约束次数 / 状态 / remoteUrl。 */
|
|
39
59
|
calledSubagent(name: string, match?: SubagentMatch): AssertionHandle;
|
|
60
|
+
/** 断言本轮事件按给定类型顺序出现(允许中间夹杂其它事件)。 */
|
|
40
61
|
eventOrder(types: StreamEvent["type"][]): AssertionHandle;
|
|
62
|
+
/** 用自定义谓词对本轮整段事件流断言;失败时用 label 说明原因。 */
|
|
41
63
|
eventsSatisfy(predicate: (events: readonly StreamEvent[]) => boolean, label?: string): AssertionHandle;
|
|
64
|
+
/** 断言本轮 token 用量不超过 max。 */
|
|
42
65
|
maxTokens(max: number): AssertionHandle;
|
|
66
|
+
/** 断言本轮花费(USD)不超过 usd。 */
|
|
43
67
|
maxCost(usd: number): AssertionHandle;
|
|
68
|
+
/** 本轮可用的 judge 命名空间(t.judge.autoevals.*)。 */
|
|
44
69
|
readonly judge: JudgeNamespace;
|
|
45
70
|
}
|
|
46
71
|
|
|
@@ -56,31 +81,43 @@ export interface JudgeNamespace {
|
|
|
56
81
|
autoevals: AutoevalsNamespace;
|
|
57
82
|
}
|
|
58
83
|
|
|
84
|
+
/** 最终 diff 的只读视图(t.sandbox.diff);内容在 test() 跑完、finalize 前才落定。 */
|
|
59
85
|
export interface DiffView {
|
|
86
|
+
/** 取某个生成 / 修改文件的最终内容;文件不在 diff 里则 undefined。 */
|
|
60
87
|
get(path: string): string | undefined;
|
|
88
|
+
/** 整个 diff 是否为空(既没有生成/修改的文件,也没有删除的文件)。 */
|
|
61
89
|
isEmpty(): boolean;
|
|
90
|
+
/** 正则是否命中 diff 里任意文件的路径或内容。 */
|
|
62
91
|
matches(re: RegExp): boolean;
|
|
63
92
|
}
|
|
64
93
|
|
|
65
94
|
/** 工具匹配小语言。 */
|
|
66
95
|
export interface ToolMatch {
|
|
96
|
+
/** 只匹配入参包含这些键值的调用(浅层包含,非深度相等)。 */
|
|
67
97
|
input?: Record<string, unknown>;
|
|
98
|
+
/** 精确匹配调用次数,省略则只要求「至少一次」。 */
|
|
68
99
|
count?: number;
|
|
100
|
+
/** 只匹配处于该状态的调用(如 HITL 场景下的 rejected)。 */
|
|
69
101
|
status?: "completed" | "failed" | "rejected";
|
|
70
102
|
}
|
|
71
103
|
|
|
104
|
+
/** calledSubagent 的匹配小语言,语义同 ToolMatch。 */
|
|
72
105
|
export interface SubagentMatch {
|
|
106
|
+
/** 精确匹配调用次数,省略则只要求「至少一次」。 */
|
|
73
107
|
count?: number;
|
|
74
108
|
status?: "completed" | "failed";
|
|
109
|
+
/** 只匹配指向该远程地址的子 agent 调用。 */
|
|
75
110
|
remoteUrl?: string | RegExp;
|
|
76
111
|
}
|
|
77
112
|
|
|
113
|
+
/** requireInputRequest 的过滤条件;多个字段之间是 AND 关系。 */
|
|
78
114
|
export interface InputRequestFilter {
|
|
79
115
|
id?: string | RegExp;
|
|
80
116
|
prompt?: string | RegExp;
|
|
81
117
|
display?: string | RegExp;
|
|
82
118
|
action?: string | RegExp;
|
|
83
119
|
input?: Record<string, unknown>;
|
|
120
|
+
/** 请求的可选项 id 集合必须与此完全一致(顺序无关)。 */
|
|
84
121
|
optionIds?: readonly string[];
|
|
85
122
|
}
|
|
86
123
|
|
|
@@ -97,53 +134,104 @@ export interface RespondAnswer {
|
|
|
97
134
|
|
|
98
135
|
/** eval 作者可见的受限沙箱视图:能执行命令 / 文件 IO / 读最终 diff,但不能 stop。 */
|
|
99
136
|
export interface SandboxHandle {
|
|
137
|
+
/** 沙箱内的工作目录绝对路径。 */
|
|
100
138
|
readonly workdir: string;
|
|
139
|
+
/** 在沙箱里执行一条命令(argv 形式,不经 shell)。装系统依赖等需要 root 时传 `{ root: true }`。 */
|
|
101
140
|
runCommand(cmd: string, args?: string[], opts?: CommandOptions): Promise<CommandResult>;
|
|
141
|
+
/** 在沙箱里执行一段 shell 脚本(经 shell 解释,支持管道 / 重定向)。 */
|
|
102
142
|
runShell(script: string, opts?: CommandOptions): Promise<CommandResult>;
|
|
143
|
+
/** 读沙箱内某文件此刻的文本内容(实时读,不是 diff 快照)。 */
|
|
103
144
|
readFile(path: string): Promise<string>;
|
|
145
|
+
/** 沙箱内某路径此刻是否存在。 */
|
|
104
146
|
fileExists(path: string): Promise<boolean>;
|
|
147
|
+
/** 按选项批量读回沙箱里的源文件,供比对 / judge 用材料。 */
|
|
105
148
|
readSourceFiles(opts?: ReadSourceFilesOptions): Promise<SourceFiles>;
|
|
149
|
+
/** 把一组内容写进沙箱(路径相对 targetDir,默认 workdir)。 */
|
|
106
150
|
writeFiles(files: Record<string, string>, targetDir?: string): Promise<void>;
|
|
151
|
+
/** 把一批内存中的文件上传进沙箱。 */
|
|
107
152
|
uploadFiles(files: SandboxFile[], targetDir?: string): Promise<void>;
|
|
153
|
+
/** 把本地目录整体上传进沙箱;`opts.ignore` 排除指定路径。 */
|
|
108
154
|
uploadDirectory(localDir: string, targetDir?: string, opts?: { ignore?: string[] }): Promise<void>;
|
|
155
|
+
/** 从沙箱下载某文件内容。 */
|
|
109
156
|
downloadFile(path: string): Promise<Buffer>;
|
|
157
|
+
/** 把一段内容上传成沙箱里的单个文件。 */
|
|
110
158
|
uploadFile(path: string, content: Buffer): Promise<void>;
|
|
159
|
+
/** 沙箱后端分配的实例 id,用于排查 / 关联日志。 */
|
|
111
160
|
readonly sandboxId: string;
|
|
161
|
+
/** 相对 git 基线的最终 diff 视图(test() 跑完、finalize 前才落定)。 */
|
|
112
162
|
readonly diff: DiffView;
|
|
163
|
+
/** 取沙箱内某文件的最终内容,占位延迟到 finalize 才真正读取;只能配合 t.check 使用。 */
|
|
113
164
|
file(path: string): string;
|
|
165
|
+
/** 断言 diff 里某路径发生了变化(新增或修改)。 */
|
|
114
166
|
fileChanged(path: string): AssertionHandle;
|
|
167
|
+
/** 断言 diff 里某路径被删除。 */
|
|
115
168
|
fileDeleted(path: string): AssertionHandle;
|
|
169
|
+
/** 断言 diff 的路径与内容都不匹配给定正则(常用来否定式检查不该出现的改动)。 */
|
|
116
170
|
notInDiff(re: RegExp): AssertionHandle;
|
|
171
|
+
/** 断言沙箱里执行过的命令都没有失败退出。 */
|
|
117
172
|
noFailedShellCommands(): AssertionHandle;
|
|
118
173
|
}
|
|
119
174
|
|
|
175
|
+
/**
|
|
176
|
+
* `t.newSession()` 返回的独立会话句柄:另开一路对话,发送与断言都与默认会话及其它
|
|
177
|
+
* `newSession()` 会话相互隔离(各自的 events / usage 互不叠加)。下面的断言方法都按
|
|
178
|
+
* 「这个会话到目前为止的累计事件流」评估(跨该会话所有轮次),不是仅最后一轮——
|
|
179
|
+
* 只看最后一轮要用 `send()` 返回的 TurnHandle。
|
|
180
|
+
*/
|
|
120
181
|
export interface SessionHandle {
|
|
182
|
+
/** 在这个会话里发一条消息,返回该轮的 TurnHandle。 */
|
|
121
183
|
send(text: string): Promise<TurnHandle>;
|
|
184
|
+
/** 在这个会话里发一条带文件的消息,语义同 TestContext.sendFile。 */
|
|
122
185
|
sendFile(path: string, text?: string): Promise<TurnHandle>;
|
|
186
|
+
/** 取这个会话里等待中的 HITL 输入请求;拿不到就抛,语义同 TestContext.requireInputRequest。 */
|
|
123
187
|
requireInputRequest(filter?: InputRequestFilter): InputRequest;
|
|
188
|
+
/** 回答这个会话里等待中的输入请求,返回续接的 TurnHandle。 */
|
|
124
189
|
respond(...responses: (string | RespondAnswer)[]): Promise<TurnHandle>;
|
|
190
|
+
/** 用同一个 optionId 批量回答这个会话里全部等待中的输入请求。 */
|
|
125
191
|
respondAll(optionId: string): Promise<TurnHandle>;
|
|
192
|
+
/** 这个会话最近一轮的助手回复文本。 */
|
|
126
193
|
readonly reply: string;
|
|
194
|
+
/** adapter 侧的会话 id(有会话概念的 agent 才有)。 */
|
|
127
195
|
readonly sessionId: string | undefined;
|
|
196
|
+
/** 这个会话累计的事件流(跨该会话内所有轮次,不含其它会话)。 */
|
|
128
197
|
readonly events: readonly StreamEvent[];
|
|
198
|
+
/** 断言这个会话累计状态为 "completed"(跨全部轮次)。 */
|
|
129
199
|
succeeded(): AssertionHandle;
|
|
200
|
+
/** 断言这个会话当前卡在 HITL 输入请求上。 */
|
|
130
201
|
parked(): AssertionHandle;
|
|
202
|
+
/** 断言这个会话累计的助手回复包含 token(跨全部轮次)。 */
|
|
131
203
|
messageIncludes(token: string | RegExp): AssertionHandle;
|
|
204
|
+
/** 断言这个会话累计调用过指定名字的工具;match 可约束入参 / 次数 / 状态。 */
|
|
132
205
|
calledTool(name: string, match?: ToolMatch): AssertionHandle;
|
|
206
|
+
/** 断言这个会话累计未调用指定工具(或未按 match 条件调用)。 */
|
|
133
207
|
notCalledTool(name: string, match?: ToolMatch): AssertionHandle;
|
|
208
|
+
/** 断言这个会话累计工具调用按给定顺序出现。 */
|
|
134
209
|
toolOrder(names: string[]): AssertionHandle;
|
|
210
|
+
/** 断言这个会话至今未调用任何工具。 */
|
|
135
211
|
usedNoTools(): AssertionHandle;
|
|
212
|
+
/** 断言这个会话累计工具调用总数不超过 max。 */
|
|
136
213
|
maxToolCalls(max: number): AssertionHandle;
|
|
214
|
+
/** 断言这个会话累计加载过指定 skill。 */
|
|
137
215
|
loadedSkill(skill: string): AssertionHandle;
|
|
216
|
+
/** 断言这个会话累计没有失败的工具调用 / 命令。 */
|
|
138
217
|
noFailedActions(): AssertionHandle;
|
|
218
|
+
/** 断言这个会话累计出现过指定类型的事件;opts.count 可约束次数。 */
|
|
139
219
|
event(type: StreamEvent["type"], opts?: { count?: number }): AssertionHandle;
|
|
220
|
+
/** 断言这个会话累计未出现指定类型的事件。 */
|
|
140
221
|
notEvent(type: StreamEvent["type"]): AssertionHandle;
|
|
222
|
+
/** 断言这个会话累计调用过指定名字的子 agent;match 可约束次数 / 状态 / remoteUrl。 */
|
|
141
223
|
calledSubagent(name: string, match?: SubagentMatch): AssertionHandle;
|
|
224
|
+
/** 断言这个会话累计事件按给定类型顺序出现。 */
|
|
142
225
|
eventOrder(types: StreamEvent["type"][]): AssertionHandle;
|
|
226
|
+
/** 用自定义谓词对这个会话累计的整段事件流断言。 */
|
|
143
227
|
eventsSatisfy(predicate: (events: readonly StreamEvent[]) => boolean, label?: string): AssertionHandle;
|
|
228
|
+
/** 断言这个会话累计 token 用量不超过 max。 */
|
|
144
229
|
maxTokens(max: number): AssertionHandle;
|
|
230
|
+
/** 断言这个会话累计花费(USD)不超过 usd。 */
|
|
145
231
|
maxCost(usd: number): AssertionHandle;
|
|
232
|
+
/** 这个会话累计的 token 用量与估算成本。 */
|
|
146
233
|
readonly usage: Usage;
|
|
234
|
+
/** 这个会话可用的 judge 命名空间。 */
|
|
147
235
|
readonly judge: JudgeNamespace;
|
|
148
236
|
}
|
|
149
237
|
|
|
@@ -153,27 +241,59 @@ export interface SessionHandle {
|
|
|
153
241
|
*/
|
|
154
242
|
export interface TestContext {
|
|
155
243
|
// 会话
|
|
244
|
+
/** 向默认会话发一条消息,返回该轮的 TurnHandle。事件同时累加进默认会话的累计事件流,供下面的作用域断言使用。 */
|
|
156
245
|
send(text: string): Promise<TurnHandle>;
|
|
157
246
|
/** 发一条带文件(图片等多模态输入)的消息。`path` 相对项目根;读出后 base64 随 TurnInput.files 交给 adapter。 */
|
|
158
247
|
sendFile(path: string, text?: string): Promise<TurnHandle>;
|
|
248
|
+
/** 取默认会话里等待中的 HITL 输入请求;不传 filter 要求恰好一条,拿不到就抛。 */
|
|
159
249
|
requireInputRequest(filter?: InputRequestFilter): InputRequest;
|
|
250
|
+
/**
|
|
251
|
+
* 回答默认会话里等待中的输入请求,返回续接的 TurnHandle。字符串形式按顺序对应各请求;
|
|
252
|
+
* 多个请求并停、需要指名回答哪一条时用 RespondAnswer 对象形式(见其类型注释)。
|
|
253
|
+
*/
|
|
160
254
|
respond(...responses: (string | RespondAnswer)[]): Promise<TurnHandle>;
|
|
255
|
+
/** 用同一个 optionId 批量回答默认会话里全部等待中的输入请求。 */
|
|
161
256
|
respondAll(optionId: string): Promise<TurnHandle>;
|
|
257
|
+
/** 默认会话最近一轮的助手回复文本。 */
|
|
162
258
|
readonly reply: string;
|
|
259
|
+
/** adapter 侧的默认会话 id(有会话概念的 agent 才有)。 */
|
|
163
260
|
readonly sessionId: string | undefined;
|
|
261
|
+
/** 默认会话累计的事件流(跨该会话所有轮次)。 */
|
|
164
262
|
readonly events: readonly StreamEvent[];
|
|
263
|
+
/**
|
|
264
|
+
* 另开一路独立会话,返回它的 SessionHandle——不是 void。多会话隔离的关键入口:
|
|
265
|
+
* 新会话的发送与断言都与默认会话及其它 newSession() 会话相互隔离,常用于「多用户
|
|
266
|
+
* 并行对话」「一条主线 + 一条旁支」这类场景。
|
|
267
|
+
*/
|
|
165
268
|
newSession(): SessionHandle;
|
|
166
269
|
|
|
167
270
|
// 运行上下文
|
|
271
|
+
/** 本次 attempt 的中止信号;超时 / 早停 / 用户 Ctrl-C 时触发,传给 adapter 的长耗时调用做取消。 */
|
|
168
272
|
readonly signal: AbortSignal;
|
|
273
|
+
/** 本次 attempt 使用的模型名(由 experiment/CLI flag 决定);省略即 agent 原生默认,不代表「无模型」。 */
|
|
169
274
|
readonly model?: string;
|
|
275
|
+
/** 本次 attempt 的推理努力程度(如 "low"/"medium"/"high",取值由 adapter/模型决定)。 */
|
|
170
276
|
readonly reasoningEffort?: string;
|
|
277
|
+
/** 本次 attempt 生效的 flags(experiment.flags 与 CLI flag 合并后的只读视图)。 */
|
|
171
278
|
readonly flags: Readonly<Record<string, unknown>>;
|
|
279
|
+
/** 打一行调试日志;有 live 进度回调时走该回调,否则落到 stderr,不出现在最终结果里。 */
|
|
172
280
|
log(msg: string): void;
|
|
281
|
+
/** 立即中止本 eval 并标记为 skipped(outcome / EvalResult.skipReason),reason 不能为空。 */
|
|
173
282
|
skip(reason: string): never;
|
|
174
283
|
|
|
175
284
|
// 值级断言
|
|
285
|
+
/**
|
|
286
|
+
* 对任意值跑一个 ValueAssertion,返回可链 `.gate()` / `.atLeast()` 的 AssertionHandle。
|
|
287
|
+
* 打分延迟到 eval 结束后统一 finalize,调用本身同步、不抛错——不通过只是记一条失败断言,
|
|
288
|
+
* 不会中止后续代码。要「不满足就立即中止 eval」用 require。
|
|
289
|
+
*/
|
|
176
290
|
check(value: unknown, assertion: ValueAssertion): AssertionHandle;
|
|
291
|
+
/**
|
|
292
|
+
* 对任意值跑一个 ValueAssertion,立即(await 时)求值;不满足就抛错中止整个 eval 剩余步骤
|
|
293
|
+
* (仍会把这条断言计入报告,不影响已记录的其它断言)。跟 check 的区别:check 只记录、
|
|
294
|
+
* 从不抛错,打分留到最后统一算;require 当场判定、失败即中止,适合「前置条件不满足,
|
|
295
|
+
* 后面写了也没意义」的场景。
|
|
296
|
+
*/
|
|
177
297
|
require(value: unknown, assertion: ValueAssertion): Promise<unknown>;
|
|
178
298
|
/**
|
|
179
299
|
* 把一组断言归到一个有标题的分组下(对照 vitest 的 test('title', ...))。纯组织/报告用,
|
|
@@ -182,30 +302,50 @@ export interface TestContext {
|
|
|
182
302
|
group<T>(title: string, fn: () => Promise<T> | T): Promise<T>;
|
|
183
303
|
|
|
184
304
|
// 作用域断言(工具 / 会话)
|
|
305
|
+
/** 断言默认会话累计状态为 "completed"(跨该会话所有轮次,不止最后一轮)。 */
|
|
185
306
|
succeeded(): AssertionHandle;
|
|
307
|
+
/** 断言默认会话当前卡在 HITL 输入请求上。 */
|
|
186
308
|
parked(): AssertionHandle;
|
|
309
|
+
/** 断言默认会话累计的助手回复包含 token(跨该会话所有轮次,不止最后一轮)。 */
|
|
187
310
|
messageIncludes(token: string | RegExp): AssertionHandle;
|
|
311
|
+
/** 断言默认会话累计调用过指定名字的工具;match 可约束入参 / 次数 / 状态。 */
|
|
188
312
|
calledTool(name: string, match?: ToolMatch): AssertionHandle;
|
|
313
|
+
/** 断言默认会话累计未调用指定工具(或未按 match 条件调用)。 */
|
|
189
314
|
notCalledTool(name: string, match?: ToolMatch): AssertionHandle;
|
|
315
|
+
/** 断言默认会话累计工具调用按给定顺序出现(允许中间夹杂其它调用)。 */
|
|
190
316
|
toolOrder(names: string[]): AssertionHandle;
|
|
317
|
+
/** 断言默认会话至今未调用任何工具。 */
|
|
191
318
|
usedNoTools(): AssertionHandle;
|
|
319
|
+
/** 断言默认会话累计工具调用总数不超过 max。 */
|
|
192
320
|
maxToolCalls(max: number): AssertionHandle;
|
|
321
|
+
/** 断言默认会话累计加载过指定 skill。 */
|
|
193
322
|
loadedSkill(skill: string): AssertionHandle;
|
|
323
|
+
/** 断言默认会话累计没有失败的工具调用 / 命令。 */
|
|
194
324
|
noFailedActions(): AssertionHandle;
|
|
325
|
+
/** 断言默认会话累计出现过指定类型的事件;opts.count 可约束出现次数。 */
|
|
195
326
|
event(type: StreamEvent["type"], opts?: { count?: number }): AssertionHandle;
|
|
327
|
+
/** 断言默认会话累计未出现指定类型的事件。 */
|
|
196
328
|
notEvent(type: StreamEvent["type"]): AssertionHandle;
|
|
329
|
+
/** 断言默认会话累计调用过指定名字的子 agent;match 可约束次数 / 状态 / remoteUrl。 */
|
|
197
330
|
calledSubagent(name: string, match?: SubagentMatch): AssertionHandle;
|
|
331
|
+
/** 断言默认会话累计事件按给定类型顺序出现(允许中间夹杂其它事件)。 */
|
|
198
332
|
eventOrder(types: StreamEvent["type"][]): AssertionHandle;
|
|
333
|
+
/** 用自定义谓词对默认会话累计的整段事件流断言;失败时用 label 说明原因。 */
|
|
199
334
|
eventsSatisfy(predicate: (events: readonly StreamEvent[]) => boolean, label?: string): AssertionHandle;
|
|
200
335
|
|
|
201
336
|
// 工作区 / 沙箱
|
|
337
|
+
/** 受限沙箱视图:能执行命令 / 读写文件 / 看最终 diff,不能 stop 沙箱本身(见 SandboxHandle)。 */
|
|
202
338
|
readonly sandbox: SandboxHandle;
|
|
203
339
|
|
|
204
340
|
// 效率 / 成本
|
|
341
|
+
/** 默认会话累计的 token 用量与估算成本。 */
|
|
205
342
|
readonly usage: Usage;
|
|
343
|
+
/** 断言默认会话累计 token 用量不超过 max。 */
|
|
206
344
|
maxTokens(max: number): AssertionHandle;
|
|
345
|
+
/** 断言默认会话累计花费(USD)不超过 usd。 */
|
|
207
346
|
maxCost(usd: number): AssertionHandle;
|
|
208
347
|
|
|
209
348
|
// judge
|
|
349
|
+
/** 可用的 judge 命名空间(t.judge.autoevals.*)。 */
|
|
210
350
|
readonly judge: JudgeNamespace;
|
|
211
351
|
}
|
package/src/expect/index.ts
CHANGED
|
@@ -112,8 +112,9 @@ export function equals(expected: unknown): ValueAssertion {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
* 用 schema 校验 value
|
|
116
|
-
*
|
|
115
|
+
* 用 schema 校验 value——不是正则匹配,是 Standard Schema / zod 风格的结构校验。
|
|
116
|
+
* 优先 Standard Schema(schema['~standard'].validate),否则退化到 zod 风格的
|
|
117
|
+
* .safeParse / .parse。校验通过 1,否则 0;任何异常 → 0。默认硬门槛。
|
|
117
118
|
*/
|
|
118
119
|
export function matches(schema: unknown): ValueAssertion {
|
|
119
120
|
return createAssertion("matches(schema)", "gate", async (value) =>
|
|
@@ -121,7 +122,10 @@ export function matches(schema: unknown): ValueAssertion {
|
|
|
121
122
|
);
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* 纯字符串编辑距离,不是语义相似度——归一化 Levenshtein 距离 [0,1](1 - 编辑距离 / 较长串长度),
|
|
127
|
+
* 不理解含义,同义改写 / 语序调整会被判低分。默认软分,阈值 0.6。
|
|
128
|
+
*/
|
|
125
129
|
export function similarity(expected: string): ValueAssertion {
|
|
126
130
|
return createAssertion(
|
|
127
131
|
`similarity(${safeLabel(expected)})`,
|
|
@@ -143,19 +147,19 @@ export function satisfies(predicate: (v: unknown) => boolean, label?: string): V
|
|
|
143
147
|
return createAssertion(name, "gate", (value) => (predicate(value) ? 1 : 0));
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
/** value 非 null / 非 undefined 则 1,否则 0。省掉 `x !== undefined` + isTrue
|
|
150
|
+
/** value 非 null / 非 undefined 则 1,否则 0。省掉 `x !== undefined` + isTrue 的样板。默认硬门槛。 */
|
|
147
151
|
export function isDefined(label?: string): ValueAssertion {
|
|
148
152
|
const name = label ? `isDefined(${label})` : "isDefined()";
|
|
149
153
|
return createAssertion(name, "gate", (value) => (value != null ? 1 : 0));
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
/** value === true 则 1,否则 0。带 label 的布尔断言(fileExists 等检查用)
|
|
156
|
+
/** value === true 则 1,否则 0。带 label 的布尔断言(fileExists 等检查用)。默认硬门槛。 */
|
|
153
157
|
export function isTrue(label?: string): ValueAssertion {
|
|
154
158
|
const name = label ? `isTrue(${label})` : "isTrue()";
|
|
155
159
|
return createAssertion(name, "gate", (value) => (value === true ? 1 : 0));
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
/** CommandResult.exitCode === 0
|
|
162
|
+
/** CommandResult.exitCode === 0 则 1,否则 0。默认硬门槛。 */
|
|
159
163
|
export function commandSucceeded(): ValueAssertion {
|
|
160
164
|
return createAssertion("commandSucceeded()", "gate", (value) => {
|
|
161
165
|
if (value === null || typeof value !== "object") return 0;
|
|
@@ -163,13 +167,16 @@ export function commandSucceeded(): ValueAssertion {
|
|
|
163
167
|
});
|
|
164
168
|
}
|
|
165
169
|
|
|
166
|
-
/** value === false 则 1,否则 0。带 label
|
|
170
|
+
/** value === false 则 1,否则 0。带 label 的布尔断言。默认硬门槛。 */
|
|
167
171
|
export function isFalse(label?: string): ValueAssertion {
|
|
168
172
|
const name = label ? `isFalse(${label})` : "isFalse()";
|
|
169
173
|
return createAssertion(name, "gate", (value) => (value === false ? 1 : 0));
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* 自定义断言工厂:直接给名字 / 严重级 / 阈值 / score,一次调用即返回可用的 ValueAssertion——
|
|
178
|
+
* 不像 gate()/atLeast() 那样需要二段链式调用来定级。severity 省略默认 gate。
|
|
179
|
+
*/
|
|
173
180
|
export function makeAssertion(spec: {
|
|
174
181
|
name: string;
|
|
175
182
|
severity?: Severity;
|