niceeval 0.5.4 → 0.6.1
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/INDEX.md +55 -0
- package/dist/agents/types.d.ts +321 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
- package/dist/report/built-ins/experiment-comparison.js +13 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +180 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +600 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +26 -0
- package/dist/report/format.js +109 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +94 -0
- package/dist/report/locale.js +183 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +19 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +11 -0
- package/dist/report/react/ExperimentList.js +67 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +96 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +398 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +541 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +408 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +7 -7
- package/docs-site/zh/concepts/assert.mdx +19 -18
- package/docs-site/zh/concepts/evals.mdx +12 -11
- package/docs-site/zh/concepts/experiment.mdx +2 -2
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +3 -3
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -1
- package/docs-site/zh/guides/fixtures.mdx +4 -2
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +4 -4
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +61 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
- package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
- package/docs-site/zh/guides/viewing-results.mdx +200 -79
- package/docs-site/zh/guides/write-experiment.mdx +9 -1
- package/docs-site/zh/guides/write-send.mdx +4 -3
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +15 -13
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +9 -3
- package/docs-site/zh/reference/expect.mdx +13 -8
- package/package.json +10 -6
- package/src/agents/bub.ts +129 -42
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +9 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +59 -3
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +23 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +8 -8
- package/src/define.ts +11 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +33 -15
- package/src/i18n/zh-CN.ts +37 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +640 -0
- package/src/report/built-ins/experiment-comparison.tsx +19 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +225 -50
- package/src/report/compute.ts +335 -54
- package/src/report/dual-render.test.tsx +1129 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +35 -2
- package/src/report/index.ts +47 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +229 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +113 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +220 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +126 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +168 -0
- package/src/report/react/fixtures.ts +169 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +21 -6
- package/src/report/react/render.test.tsx +180 -31
- package/src/report/react/styles.css +432 -213
- package/src/report/report.test.ts +398 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +344 -96
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +184 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +48 -13
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +24 -24
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +119 -95
- package/src/show/render.ts +462 -159
- package/src/show/show.test.ts +471 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { AgentSetup, AgentTeardown } from "../agents/types.ts";
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
exitCode: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SandboxFile {
|
|
8
|
+
path: string;
|
|
9
|
+
content: string | Buffer;
|
|
10
|
+
}
|
|
11
|
+
/** 一个源码文件:相对工作区根的路径 + 文本内容。readSourceFiles 的返回元素。 */
|
|
12
|
+
export interface SourceFile {
|
|
13
|
+
path: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* readSourceFiles 的返回值:仍是一个 SourceFile 数组(.filter/.some/.map 照用),
|
|
18
|
+
* 额外挂上整体匹配 / 按文件匹配的便利方法,省掉 eval 目录里手写的 source-helpers。
|
|
19
|
+
*/
|
|
20
|
+
export interface SourceFiles extends ReadonlyArray<SourceFile> {
|
|
21
|
+
/** 全部文件内容拼接(每段前带 `// path` 注释),用于整体 regex。 */
|
|
22
|
+
text(): string;
|
|
23
|
+
/** 同 text() 但先剥注释,只看真实代码。 */
|
|
24
|
+
code(): string;
|
|
25
|
+
/** 第一个内容命中 pattern 的文件。 */
|
|
26
|
+
fileMatching(pattern: RegExp): SourceFile | undefined;
|
|
27
|
+
/** 第一个内容命中全部 patterns 的文件(同文件共现,per-file 而非拼接源码)。 */
|
|
28
|
+
fileMatchingAll(patterns: RegExp[]): SourceFile | undefined;
|
|
29
|
+
/** 是否存在路径命中 pattern 的文件。 */
|
|
30
|
+
hasPath(pattern: RegExp): boolean;
|
|
31
|
+
}
|
|
32
|
+
/** readSourceFiles 的可选项;不传则用一套合理默认。 */
|
|
33
|
+
export interface ReadSourceFilesOptions {
|
|
34
|
+
/** 文件扩展名(不带点)。默认 ts/tsx/js/jsx。 */
|
|
35
|
+
extensions?: string[];
|
|
36
|
+
/** 按目录名(任意深度)剪枝。默认 .git/.next/node_modules/dist/build/coverage。 */
|
|
37
|
+
ignoreDirs?: string[];
|
|
38
|
+
/** 按文件 basename 忽略。默认 EVAL.ts/PROMPT.md。 */
|
|
39
|
+
ignoreFiles?: string[];
|
|
40
|
+
}
|
|
41
|
+
/** 内置 provider 名;不出现在 `sandbox` 字段的类型里(spec 用各自的 `provider` 判别字段区分)。 */
|
|
42
|
+
export type SandboxProvider = "docker" | "vercel" | "e2b";
|
|
43
|
+
/** 镜像/模板里的 Node 运行时版本。 */
|
|
44
|
+
export type SandboxRuntime = "node20" | "node24";
|
|
45
|
+
/**
|
|
46
|
+
* SandboxSpec 四个变体共有的沙箱生命周期钩子挂载点:`.setup(fn)` / `.teardown(fn)` 链式方法,
|
|
47
|
+
* 由 `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` / `defineSandbox()` 产出的对象上
|
|
48
|
+
* 直接可调。
|
|
49
|
+
*
|
|
50
|
+
* **语义**:环境预置层,俗称「动态镜像层」——本想直接烘进镜像/快照/模板,但内容要按实验
|
|
51
|
+
* (`ctx.flags` / `ctx.experimentId`)动态变化的东西:装某个实验专属的二进制、预热缓存、
|
|
52
|
+
* 写运行期才知道内容的 hook 文件、按 `ctx.experimentId` 载入/回存跨 attempt 的状态。
|
|
53
|
+
*
|
|
54
|
+
* **与另外两层 setup 的分工**(三者都在同一个沙箱生命周期里,各管一层):
|
|
55
|
+
* · `sandbox.setup`(这里)—— 环境层,不知道具体跑哪个 eval / 接哪个 agent;
|
|
56
|
+
* · `eval.setup` —— 任务层,准备这次 eval 的素材(如 `npm install` 起始项目依赖);
|
|
57
|
+
* · `agent.setup` —— 协议层,接入被测 agent(装 CLI、写鉴权 / 模型配置)。
|
|
58
|
+
*
|
|
59
|
+
* **执行顺序**:沙箱就绪 → `sandbox.setup` 钩子 → workspace 上传 / git 基线 / `eval.setup` →
|
|
60
|
+
* `agent.setup` → `agent.tracing.configure` → 逐轮 `send`。`sandbox.setup` 特意排在 git
|
|
61
|
+
* 基线之前——它的改动会被提交进基线,不会被误算进 agent 产出的 diff。收尾按 LIFO:
|
|
62
|
+
* agent 级 cleanup / `agent.teardown` 先跑,`sandbox.teardown` 钩子最后跑(沙箱销毁前)。
|
|
63
|
+
*
|
|
64
|
+
* **多钩子**:`.setup(a).setup(b)` 按追加顺序依次执行(a 先 b 后);`.teardown(x).teardown(y)`
|
|
65
|
+
* 按追加的**逆序**执行(y 先 x 后)。每次调用都返回一个新 spec(不改变原对象),可继续链式。
|
|
66
|
+
*
|
|
67
|
+
* **失败语义**:`setup` 钩子抛错按执行错误计——与 `eval.setup` / `agent.setup` 抛错走同一条
|
|
68
|
+
* 路径,不新增错误分类;但不阻断该 attempt 已进入的收尾(已跑过的 setup 返回的 cleanup、
|
|
69
|
+
* `teardown` 钩子仍会在 finally 里跑)。`teardown` 钩子报错只作诊断(吞掉 / 记 log),不改变
|
|
70
|
+
* 已产出的结果——与 `agent.teardown` 现状一致。
|
|
71
|
+
*/
|
|
72
|
+
export interface SandboxHooks<Self> {
|
|
73
|
+
/** 已挂载的 setup 钩子,按追加顺序保存(内部读取,一般用不到)。 */
|
|
74
|
+
readonly setupHooks?: readonly AgentSetup[];
|
|
75
|
+
/** 已挂载的 teardown 钩子,按追加顺序保存,执行时逆序(内部读取,一般用不到)。 */
|
|
76
|
+
readonly teardownHooks?: readonly AgentTeardown[];
|
|
77
|
+
/** 追加一个沙箱级 setup 钩子,返回新 spec;详细契约见 {@link SandboxHooks}。 */
|
|
78
|
+
setup(fn: AgentSetup): Self;
|
|
79
|
+
/** 追加一个沙箱级 teardown 钩子,返回新 spec;详细契约见 {@link SandboxHooks}。 */
|
|
80
|
+
teardown(fn: AgentTeardown): Self;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Sandbox 的「数据结构」定义 —— 与 agent 一样可带参数(见 docs/feature/sandbox/library.md)。
|
|
84
|
+
* 必须用工厂函数构造(`dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` / `defineSandbox()`),
|
|
85
|
+
* 放进 config / experiment 的 `sandbox` 字段 —— 字段类型只接受这个数据结构,不接受裸字符串。
|
|
86
|
+
* 各 provider 的参数互不相同 —— 这是个按 `provider` 区分的可辨识联合(discriminated union)。
|
|
87
|
+
* 四个变体都带 {@link SandboxHooks} 的 `.setup()` / `.teardown()` 链式方法。
|
|
88
|
+
*/
|
|
89
|
+
export interface DockerSandboxSpec extends SandboxHooks<DockerSandboxSpec> {
|
|
90
|
+
readonly provider: "docker";
|
|
91
|
+
/** 覆盖默认镜像;默认按 runtime 选 `node:*-slim`。预制模板:传烘焙好 agent CLI 的镜像名。 */
|
|
92
|
+
readonly image?: string;
|
|
93
|
+
readonly runtime?: SandboxRuntime;
|
|
94
|
+
}
|
|
95
|
+
export interface VercelSandboxSpec extends SandboxHooks<VercelSandboxSpec> {
|
|
96
|
+
readonly provider: "vercel";
|
|
97
|
+
/** 从已有快照起 microVM。预制模板:烘焙好 agent CLI 的 snapshotId。 */
|
|
98
|
+
readonly snapshotId?: string;
|
|
99
|
+
readonly runtime?: SandboxRuntime;
|
|
100
|
+
}
|
|
101
|
+
export interface E2BSandboxSpec extends SandboxHooks<E2BSandboxSpec> {
|
|
102
|
+
readonly provider: "e2b";
|
|
103
|
+
/** e2b 模板名/ID。预制模板:烘焙好 agent CLI 的模板(如 `"niceeval-agents"`)。省略用 e2b 默认 `"base"`。 */
|
|
104
|
+
readonly template?: string;
|
|
105
|
+
/** 仅作记录;e2b 的 node 版本由模板决定,不在创建时选。 */
|
|
106
|
+
readonly runtime?: SandboxRuntime;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 用户自定义 provider:`create` 直接产出一个 `Sandbox` 实例,不经 resolve.ts 的内置 provider switch。
|
|
110
|
+
* 用 `defineSandbox()` 构造(见 src/define.ts)。`provider` 只用于展示 / 日志,不参与分发。
|
|
111
|
+
*/
|
|
112
|
+
export interface CustomSandboxSpec extends SandboxHooks<CustomSandboxSpec> {
|
|
113
|
+
readonly provider: string;
|
|
114
|
+
readonly runtime?: SandboxRuntime;
|
|
115
|
+
readonly recommendedConcurrency?: number;
|
|
116
|
+
readonly create: (opts: {
|
|
117
|
+
timeout?: number;
|
|
118
|
+
runtime?: SandboxRuntime;
|
|
119
|
+
}) => Promise<Sandbox>;
|
|
120
|
+
}
|
|
121
|
+
export type SandboxSpec = DockerSandboxSpec | VercelSandboxSpec | E2BSandboxSpec | CustomSandboxSpec;
|
|
122
|
+
/** config / experiment 的 `sandbox` 字段:必须是工厂函数产出的 spec 数据结构;沙箱型 agent 不能省略。 */
|
|
123
|
+
export type SandboxOption = SandboxSpec;
|
|
124
|
+
export interface CommandOptions {
|
|
125
|
+
/** 追加/覆盖本命令的环境变量(与沙箱默认环境叠加,不清空默认值;各 provider 会保留自己固定的 `PATH` 等变量,不保证能被这里覆盖)。 */
|
|
126
|
+
env?: Record<string, string>;
|
|
127
|
+
/** 本命令的工作目录;省略时落到 `Sandbox.workdir`。相对路径按 workdir 解析,绝对路径原样使用。 */
|
|
128
|
+
cwd?: string;
|
|
129
|
+
/**
|
|
130
|
+
* 把本命令的输出也送进沙箱的「原生日志流」(于是 `docker logs` / Docker UI 的 Logs
|
|
131
|
+
* 标签页能实时看到它)。给 agent 命令(codex exec / bub run / claude)开它,就能在容器
|
|
132
|
+
* 日志里看到 agent 的【原始输出】。provider 各自实现(docker:tee 到 PID1 tail 的文件;
|
|
133
|
+
* 不支持的 provider 忽略)—— 日志怎么浮现是 provider 的事,adapter 只声明意图。
|
|
134
|
+
*/
|
|
135
|
+
stream?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* 以 root 跑本命令。默认 `false` —— 命令以沙箱的标准**非 root** 用户跑(agent 的自然环境)。
|
|
138
|
+
* 给 setup 阶段装系统依赖用(`apt-get install …`、`pip install --break-system-packages …`)。
|
|
139
|
+
*
|
|
140
|
+
* 语义跨 provider 一致:"本命令以 root 跑,否则以标准非 root 用户跑"。各 provider 映射到自己的原生机制
|
|
141
|
+
* (docker:`exec --user root`;E2B:`{ user: "root" }`;Vercel:`{ sudo: true }`;Daytona:`{ user }`)。
|
|
142
|
+
* 本就全程 root 的 provider(如 Modal)视作 no-op;完全无法提权的 provider 可不支持(抛错)—— 但**默认值与
|
|
143
|
+
* 语义保持一致**,不因 provider 而变。
|
|
144
|
+
*/
|
|
145
|
+
root?: boolean;
|
|
146
|
+
}
|
|
147
|
+
export interface Sandbox {
|
|
148
|
+
/** 沙箱内项目/工作区根目录的绝对路径(agent 命令的默认 cwd,也是 git baseline 提交的位置)。各方法的相对路径都以此为基准解析,省略 `cwd`/`targetDir` 时也落到这里。 */
|
|
149
|
+
readonly workdir: string;
|
|
150
|
+
/**
|
|
151
|
+
* 执行单个命令,`args` 作为独立 argv 传递、不经 shell 解释(无 `&&`、管道、通配符展开)。
|
|
152
|
+
* 只想跑一个可执行文件、参数来自外部输入、担心注入时优先用它。
|
|
153
|
+
*/
|
|
154
|
+
runCommand(cmd: string, args?: string[], opts?: CommandOptions): Promise<CommandResult>;
|
|
155
|
+
/**
|
|
156
|
+
* 执行一整段脚本,经 shell(bash)解释,支持 `&&`、管道、`$()`、重定向等。
|
|
157
|
+
* 需要拼多条命令或做条件判断时用它。
|
|
158
|
+
*/
|
|
159
|
+
runShell(script: string, opts?: CommandOptions): Promise<CommandResult>;
|
|
160
|
+
/** 读取沙箱内文件的文本内容(UTF-8)。文件不存在时抛错,不返回空字符串——需要容错请自行 `.catch()`。 */
|
|
161
|
+
readFile(path: string): Promise<string>;
|
|
162
|
+
/** 检查沙箱内路径是否存在。跨 provider 语义不完全一致:仅保证对普通文件可靠,对目录路径的行为不同 provider 不保证一致。 */
|
|
163
|
+
fileExists(path: string): Promise<boolean>;
|
|
164
|
+
/**
|
|
165
|
+
* 一次 shell 往返读全部源码文件(按扩展名收、按目录/文件名忽略)。
|
|
166
|
+
* 取代每个 eval 目录里手写的 find + 逐文件 readFile。
|
|
167
|
+
*/
|
|
168
|
+
readSourceFiles(opts?: ReadSourceFilesOptions): Promise<SourceFiles>;
|
|
169
|
+
/** 写入若干文本文件(内容已在内存里的字符串);是 `uploadFiles` 的文本特化,省略 `targetDir` 落到 workdir。 */
|
|
170
|
+
writeFiles(files: Record<string, string>, targetDir?: string): Promise<void>;
|
|
171
|
+
/** 批量写入若干文件,内容可以是文本或二进制 Buffer;省略 `targetDir` 落到 workdir。 */
|
|
172
|
+
uploadFiles(files: SandboxFile[], targetDir?: string): Promise<void>;
|
|
173
|
+
/** 把本地磁盘上的一个目录整体上传进沙箱(递归读取本地文件后按 `uploadFiles` 写入);`opts.ignore` 是排除规则,省略 `targetDir` 落到 workdir。 */
|
|
174
|
+
uploadDirectory(localDir: string, targetDir?: string, opts?: {
|
|
175
|
+
ignore?: string[];
|
|
176
|
+
}): Promise<void>;
|
|
177
|
+
/** 销毁沙箱占用的计算资源(容器/microVM)。调用后沙箱不可再用;是否可安全重复调用因 provider 而异,不要依赖这一点。 */
|
|
178
|
+
stop(): Promise<void>;
|
|
179
|
+
/** 本沙箱的稳定标识(各 provider 原生 ID,如 Docker 容器 ID 前缀);用于跨调用关联同一沙箱的会话状态,也用于日志展示。 */
|
|
180
|
+
readonly sandboxId: string;
|
|
181
|
+
/**
|
|
182
|
+
* 本地 OTLP 接收器的目标 host。
|
|
183
|
+
* - `string`:沙箱内可通过该 hostname 回连宿主 OTLP 端口(如 docker 的 `host.docker.internal`)。
|
|
184
|
+
* - `null`:沙箱运行在远程云端(如 e2b/vercel),无法访问宿主本地端口 → 跳过 tracing。
|
|
185
|
+
* 可通过环境变量 `NICEEVAL_OTLP_HOST` 强制覆盖(如配置 tunnel 时)。
|
|
186
|
+
*/
|
|
187
|
+
readonly otlpHost: string | null;
|
|
188
|
+
/**
|
|
189
|
+
* 可选:把一行写进容器的「主日志」(PID1 在 tail 它)——于是 `docker logs` /
|
|
190
|
+
* Docker UI 的 Logs 标签页能实时看到 agent 逐轮活动。docker provider 实现,其它可省略。
|
|
191
|
+
*/
|
|
192
|
+
appendLog?(line: string): Promise<void>;
|
|
193
|
+
/**
|
|
194
|
+
* 从沙箱内任意路径读取文件,返回二进制 Buffer。
|
|
195
|
+
* 对应各 provider:Docker getArchive / Vercel readFileToBuffer / e2b files.read(bytes) / …
|
|
196
|
+
*/
|
|
197
|
+
downloadFile(path: string): Promise<Buffer>;
|
|
198
|
+
/**
|
|
199
|
+
* 向沙箱内任意路径写入文件(二进制)。
|
|
200
|
+
* 对应各 provider:Docker putArchive / Vercel fs.writeFile(Buffer) / e2b files.write / …
|
|
201
|
+
*/
|
|
202
|
+
uploadFile(path: string, content: Buffer): Promise<void>;
|
|
203
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Sandbox, CommandResult, CommandOptions, SandboxFile, SourceFiles, ReadSourceFilesOptions } from "../types.ts";
|
|
2
|
+
import type { SandboxProvisionErrorKind } from "./errors.ts";
|
|
3
|
+
/**
|
|
4
|
+
* vercel SDK 对单次 fetch 的 429 已有内部重试(见 @vercel/sandbox 的 with-retry.js,
|
|
5
|
+
* 5 次指数退避);这里再分类是为了给 create() 整体重试兜底——耗尽内部重试后仍返回 429
|
|
6
|
+
* 响应的 APIError,或 create() 轮询 session 状态过程里撞到的限流,都会走到这里。
|
|
7
|
+
*/
|
|
8
|
+
export declare function classifyProvisionError(e: unknown): SandboxProvisionErrorKind;
|
|
9
|
+
export declare class VercelSandbox implements Sandbox {
|
|
10
|
+
readonly workdir = "/vercel/sandbox";
|
|
11
|
+
readonly otlpHost: null;
|
|
12
|
+
private vsb;
|
|
13
|
+
private commandTimeoutMs;
|
|
14
|
+
private sessionCreatedAt;
|
|
15
|
+
private runtime;
|
|
16
|
+
readonly sandboxId: string;
|
|
17
|
+
private constructor();
|
|
18
|
+
static create(opts?: {
|
|
19
|
+
timeout?: number;
|
|
20
|
+
runtime?: "node20" | "node24";
|
|
21
|
+
snapshotId?: string;
|
|
22
|
+
}): Promise<VercelSandbox>;
|
|
23
|
+
private rotateIfNeeded;
|
|
24
|
+
runCommand(cmd: string, args?: string[], opts?: CommandOptions): Promise<CommandResult>;
|
|
25
|
+
runShell(script: string, opts?: CommandOptions): Promise<CommandResult>;
|
|
26
|
+
readFile(path: string): Promise<string>;
|
|
27
|
+
fileExists(path: string): Promise<boolean>;
|
|
28
|
+
readSourceFiles(opts?: ReadSourceFilesOptions): Promise<SourceFiles>;
|
|
29
|
+
writeFiles(files: Record<string, string>, targetDir?: string): Promise<void>;
|
|
30
|
+
uploadFiles(files: SandboxFile[], targetDir?: string): Promise<void>;
|
|
31
|
+
uploadDirectory(localDir: string, targetDir?: string, opts?: {
|
|
32
|
+
ignore?: string[];
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
stop(): Promise<void>;
|
|
35
|
+
downloadFile(path: string): Promise<Buffer>;
|
|
36
|
+
uploadFile(path: string, content: Buffer): Promise<void>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Severity, SourceLoc } from "../shared/types.ts";
|
|
2
|
+
import type { DerivedFacts, StreamEvent, Usage } from "../o11y/types.ts";
|
|
3
|
+
/** 值断言(expect 匹配器)。纯函数 score + 可链式改严重度 / 阈值。 */
|
|
4
|
+
export interface ValueAssertion {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly severity: Severity;
|
|
7
|
+
readonly threshold?: number;
|
|
8
|
+
score(value: unknown): number | Promise<number>;
|
|
9
|
+
/** 转成硬门槛断言:未达阈值(省略 threshold 则按 score > 0 判定)整条 eval 判为 failed。返回新实例,不改原对象。 */
|
|
10
|
+
gate(threshold?: number): ValueAssertion;
|
|
11
|
+
/**
|
|
12
|
+
* 转成软阈值断言:未达 threshold 时该条记为 failed,但默认不拖累整条 eval 的 verdict;
|
|
13
|
+
* `--strict` 运行下,软阈值失败也会把整条 eval 的 verdict 计为 failed。返回新实例,不改原对象。
|
|
14
|
+
*/
|
|
15
|
+
atLeast(threshold: number): ValueAssertion;
|
|
16
|
+
}
|
|
17
|
+
/** 收集到 collector 里的一条断言记录(评估前)。 */
|
|
18
|
+
export interface AssertionSpec {
|
|
19
|
+
name: string;
|
|
20
|
+
severity: Severity;
|
|
21
|
+
threshold?: number;
|
|
22
|
+
/** 延迟评估:final 时拿到完整运行结果再算分。 */
|
|
23
|
+
evaluate(ctx: ScoringContext): Promise<number> | number;
|
|
24
|
+
}
|
|
25
|
+
/** 断言评估完的结果(进判定 / 报告)。 */
|
|
26
|
+
export interface AssertionResult {
|
|
27
|
+
name: string;
|
|
28
|
+
severity: Severity;
|
|
29
|
+
threshold?: number;
|
|
30
|
+
score: number;
|
|
31
|
+
passed: boolean;
|
|
32
|
+
detail?: string;
|
|
33
|
+
/** 这条分数是看着什么材料算出来的(judge 收到的输入,或 t.check 失败时实际被检查的值)。view 展开排查「为什么是这个分」,默认不展示。 */
|
|
34
|
+
evidence?: string;
|
|
35
|
+
/** 所属分组(t.group 标题)。纯报告用,不影响 passed/score。 */
|
|
36
|
+
group?: string;
|
|
37
|
+
/** 断言在 eval 源码里的调用点(栈回溯抠出);view 把判定叠回这一行。 */
|
|
38
|
+
loc?: SourceLoc;
|
|
39
|
+
}
|
|
40
|
+
/** eval 作者拿到的可链式句柄(t.judge.autoevals.closedQA(...).atLeast(0.7))。 */
|
|
41
|
+
export interface AssertionHandle {
|
|
42
|
+
atLeast(threshold: number): AssertionHandle;
|
|
43
|
+
gate(threshold?: number): AssertionHandle;
|
|
44
|
+
}
|
|
45
|
+
/** scoped / judge 断言在 final 评估时拿到的运行结果。 */
|
|
46
|
+
export interface ScoringContext {
|
|
47
|
+
readonly events: readonly StreamEvent[];
|
|
48
|
+
readonly facts: DerivedFacts;
|
|
49
|
+
readonly diff: DiffData;
|
|
50
|
+
readonly scripts: Record<string, ScriptResult>;
|
|
51
|
+
readonly usage: Usage;
|
|
52
|
+
readonly status: "completed" | "failed" | "waiting";
|
|
53
|
+
/** 读沙箱里某文件的最终内容(judge / file 断言用)。 */
|
|
54
|
+
readFile(path: string): Promise<string | undefined>;
|
|
55
|
+
}
|
|
56
|
+
export interface ScriptResult {
|
|
57
|
+
success: boolean;
|
|
58
|
+
output: string;
|
|
59
|
+
}
|
|
60
|
+
export interface DiffData {
|
|
61
|
+
generatedFiles: Record<string, string>;
|
|
62
|
+
deletedFiles: string[];
|
|
63
|
+
}
|
|
64
|
+
export type Verdict = "passed" | "failed" | "errored" | "skipped";
|
|
65
|
+
export interface JudgeConfig {
|
|
66
|
+
model: string;
|
|
67
|
+
/** OpenAI 兼容 base url + key 来源;省略则从 env 探测(见 scoring/judge.ts)。 */
|
|
68
|
+
baseUrl?: string;
|
|
69
|
+
apiKeyEnv?: string;
|
|
70
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Usage } from "../o11y/types.ts";
|
|
2
|
+
import type { EvalResult, ExperimentRunInfo } from "../runner/types.ts";
|
|
3
|
+
import type { Verdict } from "../scoring/types.ts";
|
|
4
|
+
/** 明细行排序:失败最靠前(failed > errored > skipped > passed 的紧急程度)。 */
|
|
5
|
+
export declare const VERDICT_ORDER: Record<Verdict, number>;
|
|
6
|
+
export declare function totalTokens(items: Array<Usage | undefined>): number;
|
|
7
|
+
/** 求和,但「全都没报」时返回 undefined(区别于真 0 成本)。 */
|
|
8
|
+
export declare function sumMaybe(items: Array<number | undefined>): number | undefined;
|
|
9
|
+
export declare function avg(items: number[]): number;
|
|
10
|
+
/** 实验 id 的展示名:取路径最后一段(exp 分组用目录表达)。 */
|
|
11
|
+
export declare function displayExperimentName(id: string | undefined): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* 实验 id 的组推导:去掉末段的目录前缀("compare/bub-low" → "compare");
|
|
14
|
+
* 无 "/" 的顶层实验不属于任何组,返回 undefined。view 榜单分组与自定义报告
|
|
15
|
+
* 的分组用同一份,两边的「组」永远指同一个东西。
|
|
16
|
+
*/
|
|
17
|
+
export declare function experimentGroupOf(experimentId: string): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* eval id 前缀过滤,同 CLI 位置参数的分段语义(src/runner/discover.ts):
|
|
20
|
+
* "algebra" 匹配自身与 "algebra/..." 子级,不误配 "algebra2";允许 "algebra/" 尾斜杠写法,等价。
|
|
21
|
+
*/
|
|
22
|
+
export declare function evalPrefixPredicate(evals?: string | string[]): (id: string) => boolean;
|
|
23
|
+
/** 无 experimentId 时的兜底标签。 */
|
|
24
|
+
export declare function fallbackExperimentLabel(result: {
|
|
25
|
+
experiment?: ExperimentRunInfo;
|
|
26
|
+
agent: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
}): string;
|
|
29
|
+
export type { EvalResult };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// CLI 表格(runner/reporters/table.ts)与 view 榜单(view/aggregate.ts)共用的聚合小工具。
|
|
2
|
+
// 实验标签推导、token/成本求和、verdict 排序各只有一份 —— 否则同一个实验在终端和网页上
|
|
3
|
+
// 会显示成两个名字 / 两组数。保持环境无关(纯函数,只 type import)。
|
|
4
|
+
/** 明细行排序:失败最靠前(failed > errored > skipped > passed 的紧急程度)。 */
|
|
5
|
+
export const VERDICT_ORDER = {
|
|
6
|
+
failed: 0,
|
|
7
|
+
errored: 1,
|
|
8
|
+
skipped: 2,
|
|
9
|
+
passed: 3,
|
|
10
|
+
};
|
|
11
|
+
export function totalTokens(items) {
|
|
12
|
+
return items.reduce((n, u) => n + (u?.inputTokens ?? 0) + (u?.outputTokens ?? 0), 0);
|
|
13
|
+
}
|
|
14
|
+
/** 求和,但「全都没报」时返回 undefined(区别于真 0 成本)。 */
|
|
15
|
+
export function sumMaybe(items) {
|
|
16
|
+
const known = items.filter((n) => n !== undefined);
|
|
17
|
+
return known.length ? known.reduce((sum, n) => sum + n, 0) : undefined;
|
|
18
|
+
}
|
|
19
|
+
export function avg(items) {
|
|
20
|
+
return items.length ? items.reduce((sum, n) => sum + n, 0) / items.length : 0;
|
|
21
|
+
}
|
|
22
|
+
/** 实验 id 的展示名:取路径最后一段(exp 分组用目录表达)。 */
|
|
23
|
+
export function displayExperimentName(id) {
|
|
24
|
+
if (!id)
|
|
25
|
+
return undefined;
|
|
26
|
+
return id.split("/").filter(Boolean).at(-1) ?? id;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 实验 id 的组推导:去掉末段的目录前缀("compare/bub-low" → "compare");
|
|
30
|
+
* 无 "/" 的顶层实验不属于任何组,返回 undefined。view 榜单分组与自定义报告
|
|
31
|
+
* 的分组用同一份,两边的「组」永远指同一个东西。
|
|
32
|
+
*/
|
|
33
|
+
export function experimentGroupOf(experimentId) {
|
|
34
|
+
if (!experimentId.includes("/"))
|
|
35
|
+
return undefined;
|
|
36
|
+
return experimentId.split("/").slice(0, -1).join("/");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* eval id 前缀过滤,同 CLI 位置参数的分段语义(src/runner/discover.ts):
|
|
40
|
+
* "algebra" 匹配自身与 "algebra/..." 子级,不误配 "algebra2";允许 "algebra/" 尾斜杠写法,等价。
|
|
41
|
+
*/
|
|
42
|
+
export function evalPrefixPredicate(evals) {
|
|
43
|
+
if (evals === undefined)
|
|
44
|
+
return () => true;
|
|
45
|
+
const prefixes = (Array.isArray(evals) ? evals : [evals]).map((p) => p.replace(/\/+$/, ""));
|
|
46
|
+
return (id) => prefixes.some((prefix) => id === prefix || id.startsWith(prefix + "/"));
|
|
47
|
+
}
|
|
48
|
+
/** 无 experimentId 时的兜底标签。 */
|
|
49
|
+
export function fallbackExperimentLabel(result) {
|
|
50
|
+
if (result.experiment?.id)
|
|
51
|
+
return displayExperimentName(result.experiment.id) ?? result.experiment.id;
|
|
52
|
+
if (result.model)
|
|
53
|
+
return `${result.agent}/${result.model}`;
|
|
54
|
+
return result.agent || "ad hoc run";
|
|
55
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** JSON 可表达的任意值(递归定义),用于事件流 / 工具输入输出等跨进程/跨语言传递的数据。 */
|
|
2
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
/** 断言的严重度:"gate" 失败必判整轮 failed;"soft" 默认只记录不拦截,仅在 `--strict` 模式或显式设阈值未达标时才计入失败。 */
|
|
6
|
+
export type Severity = "gate" | "soft";
|
|
7
|
+
/**
|
|
8
|
+
* eval 源码里一次调用的位置(`t.send` / 各断言),运行期从栈回溯抠出来(见 src/source-loc.ts)。
|
|
9
|
+
* view 据此把运行结果叠回真实源码行(github-diff 式代码视图)。`file` 为相对项目根的路径。
|
|
10
|
+
*/
|
|
11
|
+
export interface SourceLoc {
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column?: number;
|
|
15
|
+
}
|
|
16
|
+
/** 随结果回传的一份 eval 源码(相对项目根的路径 + 文本),供 view 渲染代码视图。 */
|
|
17
|
+
export interface SourceArtifact {
|
|
18
|
+
path: string;
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
/** 通用清理闭包(setup 返回值 / teardown 的形状),异步同步皆可,统一在 finally 里执行。 */
|
|
22
|
+
export type Cleanup = () => Promise<void> | void;
|
|
23
|
+
/**
|
|
24
|
+
* 可本地化文案:纯字符串,或按 locale 代码(如 "en"、"zh-CN")映射多语言。
|
|
25
|
+
* view 按当前界面语言挑一条,挑不到回退到 en / 第一条。
|
|
26
|
+
*/
|
|
27
|
+
export type LocalizedText = string | Record<string, string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Verdict } from "../types.ts";
|
|
2
|
+
/** 折叠/计票只需要 verdict 字段;server 传 EvalResult,前端传 ViewResult 都满足。 */
|
|
3
|
+
export interface VerdictLike {
|
|
4
|
+
verdict: Verdict;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 把同一个 eval 的多轮 attempt 折叠成单一判定:任一轮通过 → 该 eval 通过(对齐 earlyExit
|
|
8
|
+
* 「先过一次即停」语义),否则按 failed > errored > skipped 取最严重的一个。
|
|
9
|
+
*/
|
|
10
|
+
export declare function foldEvalVerdict(attempts: VerdictLike[]): Verdict;
|
|
11
|
+
export interface EvalLevelStats {
|
|
12
|
+
/** 去重后的 eval 数(成功率分母的口径)。 */
|
|
13
|
+
evals: number;
|
|
14
|
+
passed: number;
|
|
15
|
+
failed: number;
|
|
16
|
+
errored: number;
|
|
17
|
+
skipped: number;
|
|
18
|
+
passRate: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 通过率与 passed/failed 一律按 eval 计票,不按 attempt:每个 eval 不管跑几轮都只占一票,先把它
|
|
22
|
+
* 的多轮折叠成单一判定再计数。否则 runs>1 时同一 eval 的 N 次 attempt 各算一票 —— 尤其 earlyExit
|
|
23
|
+
* 开时通过的 eval 只留 1 次、失败的 eval 跑满 N 次,失败 eval 被重复计入分母,把通过率拉低
|
|
24
|
+
* (见 docs/runner.md、docs/feature/scoring/README.md)。keyOf 决定「一个 eval」的粒度:单实验按 eval id,
|
|
25
|
+
* 跨实验组按 experimentId|eval id。
|
|
26
|
+
*/
|
|
27
|
+
export declare function evalLevelStats<T extends VerdictLike>(results: T[], keyOf: (r: T) => string): EvalLevelStats;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// server(aggregate.ts)与前端(app/lib/verdict.ts)共用的判定折叠口径。
|
|
2
|
+
// 必须保持环境无关且只依赖 type import,vite 前端会直接打包它。
|
|
3
|
+
// 单独成模块的原因:折叠/计票口径两边必须逐字一致,否则折叠行状态会和 KPI / 成功率对不上。
|
|
4
|
+
/**
|
|
5
|
+
* 把同一个 eval 的多轮 attempt 折叠成单一判定:任一轮通过 → 该 eval 通过(对齐 earlyExit
|
|
6
|
+
* 「先过一次即停」语义),否则按 failed > errored > skipped 取最严重的一个。
|
|
7
|
+
*/
|
|
8
|
+
export function foldEvalVerdict(attempts) {
|
|
9
|
+
const verdicts = attempts.map((a) => a.verdict);
|
|
10
|
+
if (verdicts.some((o) => o === "passed"))
|
|
11
|
+
return "passed";
|
|
12
|
+
if (verdicts.some((o) => o === "failed"))
|
|
13
|
+
return "failed";
|
|
14
|
+
if (verdicts.some((o) => o === "errored"))
|
|
15
|
+
return "errored";
|
|
16
|
+
return "skipped";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 通过率与 passed/failed 一律按 eval 计票,不按 attempt:每个 eval 不管跑几轮都只占一票,先把它
|
|
20
|
+
* 的多轮折叠成单一判定再计数。否则 runs>1 时同一 eval 的 N 次 attempt 各算一票 —— 尤其 earlyExit
|
|
21
|
+
* 开时通过的 eval 只留 1 次、失败的 eval 跑满 N 次,失败 eval 被重复计入分母,把通过率拉低
|
|
22
|
+
* (见 docs/runner.md、docs/feature/scoring/README.md)。keyOf 决定「一个 eval」的粒度:单实验按 eval id,
|
|
23
|
+
* 跨实验组按 experimentId|eval id。
|
|
24
|
+
*/
|
|
25
|
+
export function evalLevelStats(results, keyOf) {
|
|
26
|
+
const byEval = new Map();
|
|
27
|
+
for (const r of results)
|
|
28
|
+
byEval.set(keyOf(r), [...(byEval.get(keyOf(r)) ?? []), r]);
|
|
29
|
+
const counts = { passed: 0, failed: 0, errored: 0, skipped: 0 };
|
|
30
|
+
for (const group of byEval.values())
|
|
31
|
+
counts[foldEvalVerdict(group)] += 1;
|
|
32
|
+
const ran = counts.passed + counts.failed + counts.errored; // skipped 不进分母
|
|
33
|
+
return { evals: byEval.size, ...counts, passRate: ran ? counts.passed / ran : 0 };
|
|
34
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** live.ts 用来订阅"即将有一行独立诊断消息落地"。返回取消订阅函数。 */
|
|
2
|
+
export declare function onBeforeExternalTerminalWrite(fn: () => void): () => void;
|
|
3
|
+
/** 任何要绕开 Reporter 直接往终端打一行独立诊断消息的地方,写之前都先调用这个。 */
|
|
4
|
+
export declare function beforeExternalTerminalWrite(): void;
|
|
5
|
+
/** process.stderr.write 的替代:文本需自带换行(沿用现有 i18n 字符串的约定)。 */
|
|
6
|
+
export declare function writeStderrLine(text: string): void;
|
package/dist/types.d.ts
ADDED
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** 读必需的环境变量,缺了就清晰报错(agent 鉴权用)。 */
|
|
2
|
+
export declare function requireEnv(name: string): string;
|
|
3
|
+
/** 取环境变量,缺了返回 undefined。 */
|
|
4
|
+
export declare function getEnv(name: string): string | undefined;
|
|
5
|
+
/** 去掉 JS/TS 注释(块注释 + 行注释),好让断言只对真实代码生效,不被注释里的迁移说明误伤。 */
|
|
6
|
+
export declare function stripComments(code: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* 把 catch 到的 e 转成报告用字符串。优先带 stack(定位到 eval 脚本抛错的具体 file:line),
|
|
9
|
+
* 只在没有 stack 时才退化到 `name: message`。EvalResult.error 走这个,别再手写
|
|
10
|
+
* `e instanceof Error ? e.message : String(e)`——那样用户永远看不出错误发生在哪一行。
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatThrown(e: unknown): string;
|
|
13
|
+
/** 零填充到 4 位(数据集扇出的 id:sql/0000)。 */
|
|
14
|
+
export declare function pad4(n: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* 在文本里维护一个带 BEGIN/END 标记的托管区块(AGENTS.md 的 niceeval 区块用)。
|
|
17
|
+
* 标记已存在 → 只替换两个标记之间的内容(升级时刷新指引,区块外的用户内容不动);
|
|
18
|
+
* 不存在 → 追加到末尾(与已有内容之间空一行)。
|
|
19
|
+
*/
|
|
20
|
+
export declare function upsertManagedBlock(source: string, begin: string, end: string, content: string): string;
|
|
21
|
+
/** 把任意值安全地转成简短字符串(报告 / 日志用)。 */
|
|
22
|
+
export declare function brief(value: unknown, max?: number): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Adapter:把 Agent 接入 NiceEval"
|
|
3
3
|
sidebarTitle: "Adapter"
|
|
4
|
-
description: "Adapter 是你写的适配器。本文讲清楚 send
|
|
4
|
+
description: "Adapter 是你写的适配器。本文讲清楚 send 函数传入什么返回什么、被测系统配置怎么传、三个接入等级,以及 t 上的能力从哪来。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
在 [NiceEval](https://niceeval.com/) 里,`Adapter` 是连接运行器和被测系统的适配层。Adapter 需要实现 `send` 函数:把 `t.send()` 等 eval 侧动作发给你的应用,再把应用返回翻译成 [NiceEval](https://niceeval.com/) 的标准 `Turn`。
|
|
@@ -85,9 +85,9 @@ export default defineExperiment({
|
|
|
85
85
|
});
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
##
|
|
88
|
+
## 接入等级
|
|
89
89
|
|
|
90
|
-
按「Adapter
|
|
90
|
+
按「Adapter 接到哪里、额外拿到什么观测数据」,接入分三级:**Tier 1 只接 send**(应用代码一行不改,全套断言在这一级就齐了)、**Tier 2 send + OTel**(应用把 span 发给 [NiceEval](https://niceeval.com/),换 `niceeval view` 的调用瀑布图)、**Tier 3 侵入改造 + experiment flags**(feature A/B)。每档投入什么、买到什么、什么时候升级,见 [Tier](/zh/concepts/tier)。
|
|
91
91
|
|
|
92
92
|
eval 侧的驱动 API——`t.send()`、`t.sendFile()`、`t.newSession()`、HITL 的 `t.respond()` / `t.respondAll()`
|
|
93
93
|
统一都是调用 Adapter 的 `send`。
|
|
@@ -110,7 +110,7 @@ interface Agent {
|
|
|
110
110
|
`send` 是唯一要实现的函数。它的签名里只有三个类型,分开看。
|
|
111
111
|
|
|
112
112
|
<Note>
|
|
113
|
-
这里的 `setup` / `teardown` 是 Agent 自己"怎么连自己"的私事(装 CLI、写鉴权配置)。按实验变化的环境准备(装某个实验专属的二进制、预热、跨 attempt 保存状态)不写在 Agent 上,挂在 `sandbox` 字段那个 Sandbox spec 自己的 `.setup()` / `.teardown()` 链式方法上,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-
|
|
113
|
+
这里的 `setup` / `teardown` 是 Agent 自己"怎么连自己"的私事(装 CLI、写鉴权配置)。按实验变化的环境准备(装某个实验专属的二进制、预热、跨 attempt 保存状态)不写在 Agent 上,挂在 `sandbox` 字段那个 Sandbox spec 自己的 `.setup()` / `.teardown()` 链式方法上,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
|
|
114
114
|
</Note>
|
|
115
115
|
|
|
116
116
|
### 传入:`TurnInput`
|
|
@@ -194,7 +194,7 @@ interface AgentContext {
|
|
|
194
194
|
readonly flags: Readonly<Record<string, unknown>>; // Tier 3 的 feature A/B:experiment.flags 透传;没配时是 {}
|
|
195
195
|
readonly experimentId?: string; // 路径推导的实验 id,与结果里的 experimentId 同源;不经 experiment 跑时是 undefined
|
|
196
196
|
|
|
197
|
-
readonly sandbox
|
|
197
|
+
readonly sandbox: Sandbox; // 非 sandbox 型 Agent 也会拿到一个 Sandbox(remote agent 是无操作的 stub)
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
interface AgentSession {
|
|
@@ -214,7 +214,7 @@ interface AgentSession {
|
|
|
214
214
|
}
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
-
`ctx` 里没有要你检查的标志位。三个档位字段都是"透传"语义:`model`、`flags` 由 experiment 声明、运行器原样递过来,Adapter 只负责随请求转发给应用,不解释它们的含义;`telemetry` 仅在配置了 OTel 接入时出现,`send` 里只需要把 `headers` spread 进请求头——接收端点在 `defineConfig` 里固定、由应用启动时指向,不从这里传,见[OTel 接入](/zh/guides/connect-otel)。`experimentId` 是路径推导出的稳定标识,典型用途是在 Sandbox 的环境钩子里按实验隔离跨 attempt
|
|
217
|
+
`ctx` 里没有要你检查的标志位。三个档位字段都是"透传"语义:`model`、`flags` 由 experiment 声明、运行器原样递过来,Adapter 只负责随请求转发给应用,不解释它们的含义;`telemetry` 仅在配置了 OTel 接入时出现,`send` 里只需要把 `headers` spread 进请求头——接收端点在 `defineConfig` 里固定、由应用启动时指向,不从这里传,见[OTel 接入](/zh/guides/connect-otel)。`experimentId` 是路径推导出的稳定标识,典型用途是在 Sandbox 的环境钩子里按实验隔离跨 attempt 的状态(缓存目录名、沙箱快照 tag 按它分区),见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
|
|
218
218
|
|
|
219
219
|
`session` 是本条会话线的自有状态,[NiceEval](https://niceeval.com/) 对它只承诺一件事:**同一条会话线的每次 `send` 拿到同一个 `ctx.session`,新会话线(eval 的第一轮,或 `t.newSession()` 之后)拿到一个全新的。** 会话续接(`id`/`capture`、`history`)和 HITL 停轮现场(`hold`/`take`)的存取器都在它上面,"第一轮"就是新会话线的自然形态——`id` 是 `undefined`、`history.get()` 是空数组,没有要判断的分支;`state` 是这些存取器之外的逃生舱,框架从不往里写数据。
|
|
220
220
|
|
|
@@ -239,7 +239,7 @@ interface Turn {
|
|
|
239
239
|
]
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
对象统共十种类型(`message`、`action.*`、`input.requested`……完整清单见[事件流参考](/zh/reference/events))。断言读的就是这个数组:`t.calledTool("get_weather")` 数 `action.called`,`t.reply` 取最后一条 assistant `message`。注意 `send` 每次只返回**本轮**的数组——跨轮拼成整条会话线是运行器的事,见下一节。多数情况下你也不手写这些对象:官方转换器的返回值就是一个填好 `events`、`usage`、`status` 的完整 `Turn`,怎么选转换器见[写 send](/zh/guides/write-send)。
|
|
243
243
|
|
|
244
244
|
`data` 不是"随便放点什么"的口袋,它只有一条规则:**要什么,由 eval 在 send 上用 schema 声明;声明了才有 data,拿到手就是声明的类型。**
|
|
245
245
|
|