niceeval 0.6.2 → 0.7.0
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 +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +2 -2
- package/dist/i18n/zh-CN.d.ts +3 -3
- package/dist/report/aggregate.d.ts +32 -26
- package/dist/report/aggregate.js +157 -76
- package/dist/report/built-in/index.d.ts +2 -0
- package/dist/report/built-in/index.js +8 -0
- package/dist/report/components.d.ts +91 -164
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +86 -73
- package/dist/report/compute.js +592 -432
- package/dist/report/flag.d.ts +28 -17
- package/dist/report/flag.js +86 -16
- package/dist/report/format.d.ts +11 -11
- package/dist/report/format.js +17 -15
- package/dist/report/index.d.ts +16 -17
- package/dist/report/index.js +20 -22
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +49 -34
- package/dist/report/locale.js +106 -58
- package/dist/report/metrics.d.ts +10 -3
- package/dist/report/metrics.js +46 -12
- package/dist/report/primitives.d.ts +42 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +10 -8
- package/dist/report/react/AttemptList.js +18 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +3 -3
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +4 -2
- package/dist/report/react/ExperimentComparison.js +5 -4
- package/dist/report/react/ExperimentList.d.ts +3 -3
- package/dist/report/react/ExperimentList.js +16 -15
- package/dist/report/react/MetricBars.js +5 -4
- package/dist/report/react/MetricLine.js +12 -5
- package/dist/report/react/MetricMatrix.js +1 -1
- package/dist/report/react/MetricScatter.js +54 -17
- package/dist/report/react/MetricTable.js +2 -12
- package/dist/report/react/ScopeSummary.d.ts +10 -0
- package/dist/report/react/ScopeSummary.js +17 -0
- package/dist/report/react/Scoreboard.js +6 -6
- package/dist/report/react/cell.js +2 -2
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +105 -149
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -20
- package/dist/report/report.js +261 -34
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +225 -157
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +2 -2
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +245 -300
- package/dist/report/types.js +4 -3
- package/dist/report/web.d.ts +21 -5
- package/dist/report/web.js +42 -16
- package/dist/results/select.d.ts +38 -16
- package/dist/results/select.js +73 -25
- package/dist/results/types.d.ts +38 -14
- package/dist/shared/aggregate.d.ts +3 -2
- package/dist/shared/aggregate.js +5 -4
- package/docs-site/zh/README.md +44 -0
- package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
- package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
- package/docs-site/zh/examples/index.mdx +50 -0
- package/docs-site/zh/{concepts → explanation}/adapter.mdx +11 -11
- package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
- package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
- package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
- package/docs-site/zh/{concepts → explanation}/overview.mdx +5 -5
- package/docs-site/zh/{guides → explanation}/runner.mdx +1 -1
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +7 -7
- package/docs-site/zh/{guides → how-to}/authoring.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +18 -18
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/experiments.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/publish-report.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-providers.mdx +1 -1
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/write-send.mdx +13 -13
- package/docs-site/zh/index.mdx +23 -25
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +5 -5
- package/docs-site/zh/reference/capabilities.mdx +6 -6
- package/docs-site/zh/reference/cli.mdx +9 -7
- package/docs-site/zh/reference/define-agent.mdx +1 -1
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +7 -7
- package/docs-site/zh/{guides → reference}/report-components.mdx +5 -5
- package/docs-site/zh/{guides → reference}/results-data.mdx +5 -5
- package/docs-site/zh/{guides → troubleshooting}/debug-sandbox.mdx +2 -2
- package/docs-site/zh/{guides → troubleshooting}/debugging.mdx +4 -2
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +6 -2
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +3 -3
- package/src/cli.ts +42 -23
- package/src/context/context.ts +1 -1
- package/src/context/session.test.ts +1 -1
- package/src/context/session.ts +1 -1
- package/src/i18n/en.ts +18 -16
- package/src/i18n/zh-CN.ts +16 -15
- package/src/report/aggregate.ts +175 -87
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -285
- package/src/report/compute.ts +717 -515
- package/src/report/dual-render.test.tsx +738 -1148
- package/src/report/flag.ts +97 -33
- package/src/report/format.ts +18 -22
- package/src/report/index.ts +113 -58
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +120 -69
- package/src/report/metrics.ts +42 -12
- package/src/report/primitives.tsx +190 -45
- package/src/report/react/AttemptList.tsx +32 -20
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +12 -7
- package/src/report/react/ExperimentList.tsx +38 -26
- package/src/report/react/MetricBars.tsx +5 -4
- package/src/report/react/MetricLine.tsx +13 -8
- package/src/report/react/MetricMatrix.tsx +2 -2
- package/src/report/react/MetricScatter.tsx +74 -20
- package/src/report/react/MetricTable.tsx +4 -76
- package/src/report/react/ScopeSummary.tsx +86 -0
- package/src/report/react/Scoreboard.tsx +28 -10
- package/src/report/react/cell.tsx +2 -2
- package/src/report/react/enhance.js +57 -5
- package/src/report/react/fixtures.ts +109 -156
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +139 -104
- package/src/report/react/styles.css +181 -91
- package/src/report/report.test.ts +761 -1031
- package/src/report/report.ts +425 -47
- package/src/report/text/faces.ts +257 -164
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +2 -2
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +257 -287
- package/src/report/web.ts +63 -20
- package/src/results/attempt-evidence.test.ts +4 -4
- package/src/results/attempt-evidence.ts +5 -5
- package/src/results/copy.ts +6 -6
- package/src/results/host-equivalence.test.ts +26 -14
- package/src/results/index.ts +10 -4
- package/src/results/open.ts +8 -4
- package/src/results/results.test.ts +4 -3
- package/src/results/select.ts +104 -34
- package/src/results/types.ts +36 -14
- package/src/runner/feedback/human.test.ts +1 -1
- package/src/runner/run.ts +1 -1
- package/src/sandbox/cli-commands.ts +2 -2
- package/src/scoring/judge.test.ts +1 -1
- package/src/shared/aggregate.ts +5 -4
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +107 -56
- package/src/show/render.ts +43 -27
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +86 -36
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +324 -63
- package/src/view/app/components/CodeView.tsx +10 -4
- package/src/view/app/i18n.ts +31 -17
- package/src/view/app/main.tsx +13 -8
- package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
- package/src/view/app/types.ts +4 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +14 -14
- package/src/view/data.test.ts +9 -3
- package/src/view/data.ts +145 -49
- package/src/view/index.ts +48 -44
- package/src/view/server.ts +35 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +224 -0
- package/src/view/view-report.test.ts +161 -57
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -39
- package/dist/report/built-ins/experiment-comparison.js +0 -119
- package/dist/report/built-ins/index.d.ts +0 -2
- package/dist/report/built-ins/index.js +0 -2
- package/dist/report/react/GroupSummary.d.ts +0 -8
- package/dist/report/react/GroupSummary.js +0 -8
- package/dist/report/react/RunOverview.d.ts +0 -8
- package/dist/report/react/RunOverview.js +0 -12
- package/docs-site/zh/example/ai-agent-application.mdx +0 -152
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
- package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
- package/docs-site/zh/example/showcase.mdx +0 -39
- package/src/report/built-in-user-parity.test.tsx +0 -597
- package/src/report/built-ins/experiment-comparison.tsx +0 -179
- package/src/report/built-ins/index.ts +0 -7
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
- /package/docs-site/zh/{guides → how-to}/ci-integration.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/reporters.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +0 -0
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { type ReportDefinition } from "../report.ts";
|
|
2
|
-
import { type ReportComponent } from "../tree.ts";
|
|
3
|
-
import { type ReportLocale } from "../locale.ts";
|
|
4
|
-
import type { ExperimentListItem, GroupSummaryData, ScatterData } from "../types.ts";
|
|
5
|
-
import { type SnapshotsInput } from "../aggregate.ts";
|
|
6
|
-
/**
|
|
7
|
-
* 一个可比组的数据。三个子块都只消费本组快照,不能含其它父目录的引用。
|
|
8
|
-
*/
|
|
9
|
-
export interface ExperimentComparisonGroupData {
|
|
10
|
-
/** experiment id 的完整父路径;根目录 experiment 使用自己的完整 id。 */
|
|
11
|
-
key: string;
|
|
12
|
-
summary: GroupSummaryData;
|
|
13
|
-
/** 成本 × 端到端成功率散点(`MetricScatter.data` 的口径:points=experiment、series=agent)。 */
|
|
14
|
-
scatter: ScatterData;
|
|
15
|
-
/** 固定列 experiment 比较表(`ExperimentList.data` 的口径,已按端到端成功率排序)。 */
|
|
16
|
-
experiments: ExperimentListItem[];
|
|
17
|
-
}
|
|
18
|
-
/** `ExperimentComparison.data(selection)` 的穷尽产物:按 key 排序的独立可比组。 */
|
|
19
|
-
export interface ExperimentComparisonData {
|
|
20
|
-
groups: ExperimentComparisonGroupData[];
|
|
21
|
-
}
|
|
22
|
-
/** 完整父路径是组键;没有父路径的 experiment 不能互相比,自己形成单例组。 */
|
|
23
|
-
export declare function experimentComparisonGroupKey(experimentId: string): string;
|
|
24
|
-
/** 组合件的数据计算:先分区,再让三个官方子块在各组内并行计算。 */
|
|
25
|
-
declare function experimentComparisonData(input: SnapshotsInput): Promise<ExperimentComparisonData>;
|
|
26
|
-
export interface ExperimentComparisonProps {
|
|
27
|
-
data: ExperimentComparisonData;
|
|
28
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
29
|
-
locale?: ReportLocale;
|
|
30
|
-
className?: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* 内置默认报告兼官方组合件:作为组件时收 `data`(`.data()` 的产物),两个渲染面直接摆
|
|
34
|
-
* 数据形态的子组件;作为默认报告时经 build 面走与用户 `--report` 完全相同的管线。
|
|
35
|
-
*/
|
|
36
|
-
export declare const ExperimentComparison: ReportComponent<ExperimentComparisonProps> & ReportDefinition & {
|
|
37
|
-
data: typeof experimentComparisonData;
|
|
38
|
-
};
|
|
39
|
-
export {};
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// ExperimentComparison:裸 niceeval show / view 首页渲染的内置默认报告,同时是可整体引用的
|
|
3
|
-
// 官方组合件(docs/feature/reports/library.md「ExperimentComparison」)。
|
|
4
|
-
//
|
|
5
|
-
// 名字描述整份报告回答的问题,不绑定其中某一张图。它是组合件而非新的数据源——每组的
|
|
6
|
-
// GroupSummary、成本 × 端到端成功率散点、固定列 ExperimentList 都消费各自公开 .data() 的结果。
|
|
7
|
-
// 两种用法共享同一份口径:
|
|
8
|
-
// · 默认报告:宿主把它当普通 ReportDefinition(build 面),没有 renderer 私有通道;
|
|
9
|
-
// · 报告组件:`<ExperimentComparison data={await ExperimentComparison.data(selection)} />`,
|
|
10
|
-
// 在自定义报告或用户自己的 React 页面里整体引用。
|
|
11
|
-
import { Col, Section, Table } from "../primitives.js";
|
|
12
|
-
import { ExperimentList, GroupSummary, MetricScatter } from "../components.js";
|
|
13
|
-
import { costUSD, endToEndPassRate } from "../metrics.js";
|
|
14
|
-
import { defineReport } from "../report.js";
|
|
15
|
-
import { defineComponent } from "../tree.js";
|
|
16
|
-
import { localeText } from "../locale.js";
|
|
17
|
-
import { resolveInput } from "../aggregate.js";
|
|
18
|
-
import { ExperimentComparisonView } from "../react/ExperimentComparison.js";
|
|
19
|
-
import { wrapDisplay } from "../text/layout.js";
|
|
20
|
-
import { formatUSD } from "../format.js";
|
|
21
|
-
// 每组散点的唯一口径:默认 definition 与公开 `.data()` 共用,不各写一份。
|
|
22
|
-
const SCATTER_OPTIONS = {
|
|
23
|
-
points: "experiment",
|
|
24
|
-
series: "agent",
|
|
25
|
-
x: costUSD,
|
|
26
|
-
y: endToEndPassRate,
|
|
27
|
-
};
|
|
28
|
-
/** 完整父路径是组键;没有父路径的 experiment 不能互相比,自己形成单例组。 */
|
|
29
|
-
export function experimentComparisonGroupKey(experimentId) {
|
|
30
|
-
const slash = experimentId.lastIndexOf("/");
|
|
31
|
-
return slash === -1 ? experimentId : experimentId.slice(0, slash);
|
|
32
|
-
}
|
|
33
|
-
/** 组合件的数据计算:先分区,再让三个官方子块在各组内并行计算。 */
|
|
34
|
-
async function experimentComparisonData(input) {
|
|
35
|
-
const { snapshots } = resolveInput(input);
|
|
36
|
-
const snapshotsByGroup = new Map();
|
|
37
|
-
for (const snapshot of snapshots) {
|
|
38
|
-
const key = experimentComparisonGroupKey(snapshot.experimentId);
|
|
39
|
-
const group = snapshotsByGroup.get(key);
|
|
40
|
-
if (group)
|
|
41
|
-
group.push(snapshot);
|
|
42
|
-
else
|
|
43
|
-
snapshotsByGroup.set(key, [snapshot]);
|
|
44
|
-
}
|
|
45
|
-
const groups = await Promise.all([...snapshotsByGroup.entries()]
|
|
46
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
47
|
-
.map(async ([key, groupSnapshots]) => {
|
|
48
|
-
const [summary, scatter, experiments] = await Promise.all([
|
|
49
|
-
GroupSummary.data(groupSnapshots),
|
|
50
|
-
MetricScatter.data(groupSnapshots, SCATTER_OPTIONS),
|
|
51
|
-
ExperimentList.data(groupSnapshots),
|
|
52
|
-
]);
|
|
53
|
-
return { key, summary, scatter, experiments };
|
|
54
|
-
}));
|
|
55
|
-
return { groups };
|
|
56
|
-
}
|
|
57
|
-
/** 单组详情:散点与实验列表全部来自同一个预分区结果。 */
|
|
58
|
-
function groupComposition(group, locale, className) {
|
|
59
|
-
return (_jsxs(Col, { className: className, children: [_jsx(MetricScatter, { data: group.scatter, locale: locale }), _jsx(ExperimentList, { items: group.experiments, filter: true, locale: locale, relativeTo: group.key })] }));
|
|
60
|
-
}
|
|
61
|
-
function shellQuote(value) {
|
|
62
|
-
return /^[A-Za-z0-9._/-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
63
|
-
}
|
|
64
|
-
function verdictsText(group, locale) {
|
|
65
|
-
const parts = [];
|
|
66
|
-
for (const verdict of ["passed", "failed", "errored", "skipped"]) {
|
|
67
|
-
const count = group.summary.verdicts[verdict];
|
|
68
|
-
if (count > 0)
|
|
69
|
-
parts.push(`${count} ${localeText(locale, `verdict.${verdict}`)}`);
|
|
70
|
-
}
|
|
71
|
-
return parts.length > 0 ? parts.join(" / ") : "—";
|
|
72
|
-
}
|
|
73
|
-
function textFace(data, className, ctx) {
|
|
74
|
-
if (data.groups.length === 0)
|
|
75
|
-
return localeText(ctx.locale, "experimentComparison.empty");
|
|
76
|
-
if (data.groups.length === 1) {
|
|
77
|
-
const group = data.groups[0];
|
|
78
|
-
return ctx.render(_jsx(Section, { title: group.key, className: className, children: groupComposition(group) }));
|
|
79
|
-
}
|
|
80
|
-
const table = ctx.render(_jsx(Table, { columns: [
|
|
81
|
-
{ key: "group", header: localeText(ctx.locale, "experimentComparison.group") },
|
|
82
|
-
{ key: "experiments", header: localeText(ctx.locale, "groupSummary.experiments"), align: "right" },
|
|
83
|
-
{ key: "evals", header: localeText(ctx.locale, "overview.evals"), align: "right" },
|
|
84
|
-
{ key: "passRate", header: localeText(ctx.locale, "overview.passRate"), align: "right" },
|
|
85
|
-
{ key: "results", header: localeText(ctx.locale, "experimentComparison.results") },
|
|
86
|
-
{ key: "cost", header: localeText(ctx.locale, "overview.totalCost"), align: "right" },
|
|
87
|
-
{ key: "lastRun", header: localeText(ctx.locale, "experimentComparison.lastRun") },
|
|
88
|
-
], rows: data.groups.map((group) => ({
|
|
89
|
-
key: group.key,
|
|
90
|
-
cells: {
|
|
91
|
-
group: group.key,
|
|
92
|
-
experiments: String(group.summary.experiments),
|
|
93
|
-
evals: String(group.summary.evals),
|
|
94
|
-
passRate: group.summary.passRate.display,
|
|
95
|
-
results: verdictsText(group, ctx.locale),
|
|
96
|
-
cost: group.summary.totalCostUSD === null ? null : formatUSD(group.summary.totalCostUSD),
|
|
97
|
-
lastRun: group.summary.lastRunAt ?? null,
|
|
98
|
-
},
|
|
99
|
-
})) }));
|
|
100
|
-
const commands = data.groups
|
|
101
|
-
.map((group) => {
|
|
102
|
-
const command = `niceeval show --experiment ${shellQuote(group.key)}`;
|
|
103
|
-
return wrapDisplay(localeText(ctx.locale, "experimentComparison.command", { command }), ctx.width).join("\n");
|
|
104
|
-
})
|
|
105
|
-
.join("\n");
|
|
106
|
-
return `${localeText(ctx.locale, "experimentComparison.groups")}\n\n${table}\n\n${commands}`;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* 内置默认报告兼官方组合件:作为组件时收 `data`(`.data()` 的产物),两个渲染面直接摆
|
|
110
|
-
* 数据形态的子组件;作为默认报告时经 build 面走与用户 `--report` 完全相同的管线。
|
|
111
|
-
*/
|
|
112
|
-
export const ExperimentComparison = Object.assign(defineComponent({
|
|
113
|
-
web: (props, ctx) => (_jsx(ExperimentComparisonView, { data: props.data, locale: props.locale ?? ctx.locale, className: props.className })),
|
|
114
|
-
text: ({ data, className }, ctx) => textFace(data, className, ctx),
|
|
115
|
-
}), defineReport(async ({ selection }) => {
|
|
116
|
-
const data = await experimentComparisonData(selection);
|
|
117
|
-
return _jsx(ExperimentComparison, { data: data });
|
|
118
|
-
}), { data: experimentComparisonData });
|
|
119
|
-
ExperimentComparison.displayName = "ExperimentComparison";
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import type { GroupSummaryData } from "../types.ts";
|
|
3
|
-
import { type ReportLocale } from "../locale.ts";
|
|
4
|
-
export declare function GroupSummary({ data, className, locale, }: {
|
|
5
|
-
data: GroupSummaryData;
|
|
6
|
-
className?: string;
|
|
7
|
-
locale?: ReportLocale;
|
|
8
|
-
}): ReactElement;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { DEFAULT_REPORT_LOCALE, localeText } from "../locale.js";
|
|
3
|
-
import { MetricCellView } from "./cell.js";
|
|
4
|
-
import { cx, formatUSD } from "./format.js";
|
|
5
|
-
export function GroupSummary({ data, className, locale = DEFAULT_REPORT_LOCALE, }) {
|
|
6
|
-
const missing = _jsx("span", { className: "nre-missing", children: localeText(locale, "cell.missing") });
|
|
7
|
-
return (_jsxs("div", { className: cx("nre", "nre-group-summary", className), children: [_jsxs("dl", { className: "nre-group-kpis", children: [_jsxs("div", { className: "nre-group-kpi nre-group-kpi-rate", children: [_jsx("dt", { children: localeText(locale, "overview.passRate") }), _jsx("dd", { children: _jsx(MetricCellView, { cell: data.passRate, locale: locale }) })] }), _jsxs("div", { className: "nre-group-kpi", children: [_jsx("dt", { children: localeText(locale, "groupSummary.experiments") }), _jsx("dd", { children: data.experiments })] }), _jsxs("div", { className: "nre-group-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.evals") }), _jsx("dd", { children: data.evals })] }), _jsxs("div", { className: "nre-group-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.attempts") }), _jsx("dd", { children: data.attempts })] }), _jsxs("div", { className: "nre-group-kpi", children: [_jsx("dt", { children: localeText(locale, "verdict.failed") }), _jsx("dd", { className: "nre-verdict-failed", children: data.verdicts.failed })] }), data.verdicts.errored > 0 && (_jsxs("div", { className: "nre-group-kpi", children: [_jsx("dt", { children: localeText(locale, "verdict.errored") }), _jsx("dd", { className: "nre-verdict-errored", children: data.verdicts.errored })] })), _jsxs("div", { className: "nre-group-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.totalCost") }), _jsx("dd", { children: data.totalCostUSD === null ? missing : formatUSD(data.totalCostUSD) })] })] }), data.lastRunAt && (_jsx("p", { className: "nre-group-summary-time", children: localeText(locale, "latestRun", { run: data.lastRunAt }) }))] }));
|
|
8
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import type { OverviewData } from "../types.ts";
|
|
3
|
-
import { type ReportLocale } from "../locale.ts";
|
|
4
|
-
export declare function RunOverview({ data, className, locale, }: {
|
|
5
|
-
data: OverviewData;
|
|
6
|
-
className?: string;
|
|
7
|
-
locale?: ReportLocale;
|
|
8
|
-
}): ReactElement;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { DEFAULT_REPORT_LOCALE, localeText } from "../locale.js";
|
|
3
|
-
import { cx, formatDurationMs, formatUSD } from "./format.js";
|
|
4
|
-
export function RunOverview({ data, className, locale = DEFAULT_REPORT_LOCALE, }) {
|
|
5
|
-
const { totals } = data;
|
|
6
|
-
const missing = _jsx("span", { className: "nre-missing", children: localeText(locale, "cell.missing") });
|
|
7
|
-
// 通过率只渲染 computeCell 算好的 MetricCell,不现场重算——覆盖率角标复用
|
|
8
|
-
// MetricCellView 的缺数据/coverage 语义(缺数据 = 显式文案,绝不画 0%)
|
|
9
|
-
const { passRate } = totals;
|
|
10
|
-
const passRateNode = passRate.value === null ? (missing) : (_jsxs(_Fragment, { children: [passRate.display, passRate.samples < passRate.total && (_jsxs("sup", { className: "nre-coverage", title: localeText(locale, "cell.coverageTitle", { samples: passRate.samples, total: passRate.total }), children: [passRate.samples, "/", passRate.total] }))] }));
|
|
11
|
-
return (_jsxs("header", { className: cx("nre", "nre-overview", className), children: [_jsxs("dl", { className: "nre-kpis", children: [_jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.snapshots") }), _jsx("dd", { children: data.snapshots.length })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.evals") }), _jsx("dd", { children: totals.evals })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.attempts") }), _jsx("dd", { children: totals.attempts })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.passRate") }), _jsx("dd", { children: passRateNode })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.totalCost") }), _jsx("dd", { children: totals.costUSD === null ? missing : formatUSD(totals.costUSD) })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.totalDuration") }), _jsx("dd", { children: formatDurationMs(totals.durationMs) })] })] }), _jsxs("p", { className: "nre-verdicts", children: [_jsxs("span", { className: "nre-verdict nre-verdict-passed", children: [localeText(locale, "verdict.passed"), " ", totals.passed] }), _jsxs("span", { className: "nre-verdict nre-verdict-failed", children: [localeText(locale, "verdict.failed"), " ", totals.failed] }), _jsxs("span", { className: "nre-verdict nre-verdict-errored", children: [localeText(locale, "verdict.errored"), " ", totals.errored] }), _jsxs("span", { className: "nre-verdict nre-verdict-skipped", children: [localeText(locale, "verdict.skipped"), " ", totals.skipped] })] }), _jsxs("p", { className: "nre-source", children: [localeText(locale, "overview.source", { n: data.snapshots.length }), data.snapshots.map((s) => (_jsxs("span", { className: "nre-source-snapshot", children: [s.experimentId, "(", s.agent, s.model ? ` · ${s.model}` : "", ")@ ", s.startedAt] }, `${s.experimentId}@${s.startedAt}`)))] }), data.warnings.length > 0 && (_jsx("ul", { className: "nre-warnings", children: data.warnings.map((w, i) => (_jsx("li", { className: "nre-warning", "data-kind": w.kind, children: w.message }, i))) }))] }));
|
|
12
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "评估你的 AI Agent 应用"
|
|
3
|
-
sidebarTitle: "评估 AI Agent 应用"
|
|
4
|
-
description: "用 direct adapter 评估一个基于 AI SDK 的通用 AI 助手应用:工具调用、图片理解和多轮会话。"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
这个例子演示如何用 [NiceEval](https://niceeval.com/) 评估你自己的 AI Agent 应用。
|
|
8
|
-
|
|
9
|
-
<Note>
|
|
10
|
-
这是**完整能力版**的参考示例:会话、工具事件、tracing、模型对比、双可观测一次到位。第一次接入建议先走[接入你的 Agent](/zh/guides/connect-your-agent) 的分步路线(最小接入 → 事件流 → 多轮 → HITL → tracing),回头拿这页对照"真实项目长什么样"。
|
|
11
|
-
</Note>
|
|
12
|
-
|
|
13
|
-
查看示例代码:https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/ai-sdk
|
|
14
|
-
|
|
15
|
-
被测对象是一个用 AI SDK tool loop 实现的【普通 AI 助手】应用:会发消息、调用工具(查天气 / 算数 / 搜索)、理解图片,并用 Langfuse 做自己的可观测。该应用在测试时不需要 sandbox —— [NiceEval](https://niceeval.com/) 通过 direct adapter 连接它。
|
|
16
|
-
|
|
17
|
-
# 目录结构
|
|
18
|
-
|
|
19
|
-
```text
|
|
20
|
-
examples/zh/ai-sdk/
|
|
21
|
-
src/ # 被测 web agent(POST /api/turn,内含工具 + 图片理解)
|
|
22
|
-
adapter/adapter.ts # NiceEval adapter:工厂 webAgent({ baseUrl })
|
|
23
|
-
evals/ # 会话型 eval
|
|
24
|
-
weather-tool.eval.ts # 问天气 → 调 get_weather
|
|
25
|
-
image-understanding.eval.ts # 图片理解
|
|
26
|
-
experiments/
|
|
27
|
-
compare-models/ # 实验组:每文件钉一个 model
|
|
28
|
-
gpt-5.4.ts
|
|
29
|
-
deepseek-v4-pro.ts
|
|
30
|
-
niceeval.config.ts # 注册 adapter、judge、并发
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
# 定义适配器
|
|
34
|
-
|
|
35
|
-
adapter 告诉 [NiceEval](https://niceeval.com/) 怎么给 AI Agent 发请求、怎么把响应读成标准事件流。它是个工厂函数:`baseUrl`(被测 agent 跑在哪)由外面传进来,adapter 自己不写死、不读 env。
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
// adapter/adapter.ts
|
|
39
|
-
import { defineAgent } from "niceeval/adapter";
|
|
40
|
-
import type { Agent } from "niceeval/adapter";
|
|
41
|
-
import type { StreamEvent } from "niceeval";
|
|
42
|
-
import type { AgentEvent, AgentResponse } from "../src/protocol.ts";
|
|
43
|
-
|
|
44
|
-
export function webAgent(opts: { baseUrl: string }): Agent {
|
|
45
|
-
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
46
|
-
return defineAgent({
|
|
47
|
-
name: "web-agent",
|
|
48
|
-
async send(input, ctx) {
|
|
49
|
-
const response = await fetch(`${baseUrl}/api/turn`, {
|
|
50
|
-
method: "POST",
|
|
51
|
-
headers: { "content-type": "application/json" },
|
|
52
|
-
body: JSON.stringify({
|
|
53
|
-
sessionId: ctx.session.id,
|
|
54
|
-
message: input.text,
|
|
55
|
-
model: ctx.model,
|
|
56
|
-
files: input.files,
|
|
57
|
-
otelEndpoint: ctx.telemetry?.endpoint, // 双可观测:让 app 把本轮 span 也发回 NiceEval
|
|
58
|
-
}),
|
|
59
|
-
signal: ctx.signal,
|
|
60
|
-
});
|
|
61
|
-
// 同一 workspace 的共享契约,直接按 AgentResponse 读,不必当 unknown 再校验一遍。
|
|
62
|
-
const body = (await response.json()) as AgentResponse;
|
|
63
|
-
ctx.session.capture(body.sessionId);
|
|
64
|
-
return {
|
|
65
|
-
events: body.events.map(toStreamEvent),
|
|
66
|
-
data: body.data,
|
|
67
|
-
status: "completed" as const,
|
|
68
|
-
};
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function toStreamEvent(event: AgentEvent): StreamEvent {
|
|
74
|
-
if (event.type === "action.called") return { ...event, tool: "unknown" };
|
|
75
|
-
return event;
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## 多轮消息
|
|
80
|
-
|
|
81
|
-
同一条会话线里 `ctx.session.id` 自动续到下一轮;adapter 把 agent 回传的 `sessionId` 用 `ctx.session.capture()` 写回即可。需要在实验里按 feature flag 分流,见 [Experiments](/zh/guides/experiments)。
|
|
82
|
-
|
|
83
|
-
# 定义评估
|
|
84
|
-
|
|
85
|
-
每个 eval 发消息、断言回复 / 工具调用 / 图片理解。确定性断言(`calledTool`、`messageIncludes`)免 API key 就能跑;设了 judge key 再加开放式打分。
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
// evals/weather-tool.eval.ts
|
|
89
|
-
import { defineEval } from "niceeval";
|
|
90
|
-
|
|
91
|
-
export default defineEval({
|
|
92
|
-
description: "AI 助手:问天气调用 get_weather",
|
|
93
|
-
async test(t) {
|
|
94
|
-
const turn = await t.send("北京今天天气怎么样?");
|
|
95
|
-
turn.succeeded();
|
|
96
|
-
await t.group("调用 get_weather 且城市正确", () => {
|
|
97
|
-
t.calledTool("get_weather", { input: { city: "北京" } });
|
|
98
|
-
t.messageIncludes(/°C|气温|天气/);
|
|
99
|
-
});
|
|
100
|
-
t.judge.autoevals.closedQA("助手是否基于工具返回的天气数据作答,而不是凭空编造?").atLeast(0.7);
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
图片理解的 eval 可以用 `t.sendFile()` 发送附件,adapter 会把 `input.files` 原样转发给 web agent:
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
// evals/image-understanding.eval.ts
|
|
109
|
-
await t.sendFile(new URL("./fixtures/sample.png", import.meta.url).pathname);
|
|
110
|
-
const turn = await t.send("这张图片里是什么?");
|
|
111
|
-
t.messageIncludes(/猫|cat/i); // 示例图是一只猫
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
# 定义实验
|
|
115
|
-
|
|
116
|
-
**一个实验文件 = 一个配置**(单 model)。跨模型对比写多个文件放进同一个实验组文件夹:
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
// experiments/compare-models/gpt-5.4.ts
|
|
120
|
-
import { defineExperiment } from "niceeval";
|
|
121
|
-
import { webAgent } from "../../adapter/adapter.ts";
|
|
122
|
-
|
|
123
|
-
export default defineExperiment({
|
|
124
|
-
description: "gpt-5.4: 对比模型",
|
|
125
|
-
agent: webAgent({ baseUrl: "http://127.0.0.1:5188" }),
|
|
126
|
-
model: "gpt-5.4", // 单个字符串;另一个模型复制一份文件改这一行
|
|
127
|
-
runs: 2,
|
|
128
|
-
earlyExit: true,
|
|
129
|
-
});
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
# 开始评估
|
|
133
|
-
|
|
134
|
-
先启动被测 agent 应用(默认 mock 模式,免 API key)。这个例子是独立 npm 项目,`niceeval` 是它的依赖:
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
cd examples/zh/ai-sdk && pnpm install && pnpm dev
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
另开一个终端跑 eval:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
cd examples/zh/ai-sdk
|
|
144
|
-
pnpm exec niceeval exp compare-models # 跑模型对比实验组
|
|
145
|
-
pnpm exec niceeval exp compare-models weather-tool # 在实验组里只跑某个 eval
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## 下一步
|
|
149
|
-
|
|
150
|
-
- [接入你的 Agent](/zh/guides/connect-your-agent) — `defineAgent` 从零跑通的教程。
|
|
151
|
-
- [编写 eval](/zh/guides/authoring) — 单轮、多轮和数据集。
|
|
152
|
-
- [CI 集成](/zh/guides/ci-integration) — 把 agent 回归测试放进 PR。
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "评估你的 Claude Code / Codex 插件"
|
|
3
|
-
sidebarTitle: "评估插件效果"
|
|
4
|
-
description: "用 NiceEval 把 coding-agent 插件放进真实 workspace,比较有插件和无插件时的通过率、成本、耗时和行为差异。"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
如果你在给 Claude Code、Codex 或其它 coding agent 写插件,最有价值的 eval 不是问它“会不会用插件”,而是把插件放进真实 workspace,让 agent 完成一组真实开发任务,再比较有插件和无插件时的 pass rate、耗时、成本和 diff。
|
|
8
|
-
|
|
9
|
-
当前可运行示例在 [coding-agent-skill](https://github.com/CorrectRoadH/coding-agent-skill),一个独立仓库。其中的 Ponytail 组就是一个插件 benchmark 迁移示例:把第三方 Ponytail skill/plugin 的 agentic benchmark 改写成 [NiceEval](https://niceeval.com/) eval,并与无插件 baseline 对比。
|
|
10
|
-
|
|
11
|
-
## 目录结构
|
|
12
|
-
|
|
13
|
-
```text
|
|
14
|
-
coding-agent-skill/
|
|
15
|
-
├─ niceeval.config.ts
|
|
16
|
-
├─ skills/
|
|
17
|
-
│ └─ ponytail.md
|
|
18
|
-
├─ workspaces/
|
|
19
|
-
│ └─ ts-starter/
|
|
20
|
-
├─ evals/
|
|
21
|
-
│ ├─ ponytail-safe-path.eval.ts
|
|
22
|
-
│ ├─ ponytail-csv-sum.eval.ts
|
|
23
|
-
│ └─ ponytail-reuse.eval.ts
|
|
24
|
-
└─ experiments/
|
|
25
|
-
├─ ponytail.ts
|
|
26
|
-
└─ ponytail-baseline.ts
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
- `skills/ponytail.md` 是要注入给 agent 的插件/skill 内容。
|
|
30
|
-
- `workspaces/ts-starter/` 是每个 eval 上传到 sandbox 的初始项目。
|
|
31
|
-
- `evals/*.eval.ts` 负责铺 workspace、发送真实任务、验证文件和行为。
|
|
32
|
-
- `experiments/ponytail.ts` 注入插件,`experiments/ponytail-baseline.ts` 不注入插件。
|
|
33
|
-
|
|
34
|
-
## 在实验里安装插件
|
|
35
|
-
|
|
36
|
-
示例不是用位置参数传插件名,也不是让 eval 自己知道“插件开关”。插件开关属于 experiment:实验组负责决定 agent 怎么启动、sandbox 里要准备什么。
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
import { defineExperiment } from "niceeval";
|
|
40
|
-
import { claudeCodeAgent } from "niceeval/adapter";
|
|
41
|
-
import { dockerSandbox } from "niceeval/sandbox";
|
|
42
|
-
import { readFileSync } from "node:fs";
|
|
43
|
-
import { dirname, join } from "node:path";
|
|
44
|
-
import { fileURLToPath } from "node:url";
|
|
45
|
-
|
|
46
|
-
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
47
|
-
const ponytailSkill = readFileSync(join(__dir, "../skills/ponytail.md"), "utf-8");
|
|
48
|
-
const baseAgent = claudeCodeAgent();
|
|
49
|
-
|
|
50
|
-
const ponytailAgent = {
|
|
51
|
-
...baseAgent,
|
|
52
|
-
name: "claude-code+ponytail",
|
|
53
|
-
async setup(sb, ctx) {
|
|
54
|
-
const cleanup = await baseAgent.setup?.(sb, ctx);
|
|
55
|
-
await sb.writeFiles({ "CLAUDE.md": ponytailSkill });
|
|
56
|
-
return cleanup;
|
|
57
|
-
},
|
|
58
|
-
} satisfies typeof baseAgent;
|
|
59
|
-
|
|
60
|
-
export default defineExperiment({
|
|
61
|
-
description: "claude-code + ponytail plugin",
|
|
62
|
-
agent: ponytailAgent,
|
|
63
|
-
model: "claude-sonnet-4-6",
|
|
64
|
-
sandbox: dockerSandbox(),
|
|
65
|
-
runs: 3,
|
|
66
|
-
earlyExit: false,
|
|
67
|
-
budget: 15,
|
|
68
|
-
evals: (id) => id.startsWith("ponytail-"),
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
对照组保留同样的模型、runs、budget 和 eval 过滤,只把 `agent` 换成裸 `claudeCodeAgent()`。这样差异才干净地归因到插件。
|
|
73
|
-
|
|
74
|
-
## 写插件任务
|
|
75
|
-
|
|
76
|
-
每个任务是普通 `.eval.ts` 文件。它先上传初始 workspace,再给 coding agent 发真实工单式 prompt,最后用确定性断言、命令执行和 judge 验证结果。
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
import { defineEval } from "niceeval";
|
|
80
|
-
import { includes } from "niceeval/expect";
|
|
81
|
-
|
|
82
|
-
export default defineEval({
|
|
83
|
-
description: "实现文件上传路径解析函数(不提示要防路径穿越)",
|
|
84
|
-
|
|
85
|
-
async test(t) {
|
|
86
|
-
await t.sandbox.uploadDirectory("../workspaces/ts-starter");
|
|
87
|
-
await t.sandbox.writeFiles({
|
|
88
|
-
"uploads.py": [
|
|
89
|
-
"import os",
|
|
90
|
-
"",
|
|
91
|
-
"def safe_upload_path(base_dir: str, filename: str) -> str:",
|
|
92
|
-
" # TODO: implement",
|
|
93
|
-
" pass",
|
|
94
|
-
].join("\n"),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
await t
|
|
98
|
-
.send(
|
|
99
|
-
"在 uploads.py 里完成 safe_upload_path(base_dir, filename) 函数," +
|
|
100
|
-
"返回用户上传文件在服务器上的完整路径。base_dir 是允许的根目录。",
|
|
101
|
-
)
|
|
102
|
-
.then((turn) => turn.succeeded());
|
|
103
|
-
|
|
104
|
-
const src = await t.sandbox.readSourceFiles({ extensions: ["py"] });
|
|
105
|
-
const code = src.text();
|
|
106
|
-
|
|
107
|
-
t.check(code, includes(/commonpath|realpath|abspath/));
|
|
108
|
-
|
|
109
|
-
await t.sandbox.writeFiles({
|
|
110
|
-
"_test_traversal.py": [
|
|
111
|
-
"import sys, os",
|
|
112
|
-
"sys.path.insert(0, '.')",
|
|
113
|
-
"from uploads import safe_upload_path",
|
|
114
|
-
"base = '/var/uploads'",
|
|
115
|
-
"try:",
|
|
116
|
-
" p = safe_upload_path(base, '../../etc/passwd')",
|
|
117
|
-
" assert os.path.commonpath([base, os.path.abspath(p)]) == base",
|
|
118
|
-
"except (ValueError, PermissionError, AssertionError):",
|
|
119
|
-
" pass",
|
|
120
|
-
"print('ok')",
|
|
121
|
-
].join("\n"),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const result = await t.sandbox.runCommand("python3", ["_test_traversal.py"]);
|
|
125
|
-
t.check(result.stdout.trim(), includes(/ok/));
|
|
126
|
-
t.sandbox.fileChanged("uploads.py");
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
关键点是:prompt 不泄露安全答案,验证阶段再检查产物、测试输出、diff 和行为证据。
|
|
132
|
-
|
|
133
|
-
## 运行对比
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
git clone https://github.com/CorrectRoadH/coding-agent-skill.git
|
|
137
|
-
cd coding-agent-skill
|
|
138
|
-
pnpm install
|
|
139
|
-
pnpm exec niceeval exp ponytail-baseline
|
|
140
|
-
pnpm exec niceeval exp caveman
|
|
141
|
-
pnpm exec niceeval exp ponytail
|
|
142
|
-
pnpm exec niceeval exp yagni-oneliner
|
|
143
|
-
pnpm exec niceeval view
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
`exp` 后的第一个参数选择实验组或实验文件;后续位置参数只用于过滤 eval ID,不用于传 agent 名、插件名或 URL。
|
|
147
|
-
|
|
148
|
-
## 看什么指标
|
|
149
|
-
|
|
150
|
-
- `pass@N` 是否提升。
|
|
151
|
-
- 平均耗时、token 和预估成本是否可接受。
|
|
152
|
-
- 失败 transcript 中 agent 是否忽略了插件流程。
|
|
153
|
-
- diff 是否只改了任务相关文件。
|
|
154
|
-
- 行为断言是否体现插件价值,例如更少重写、更多复用、主动安全防御。
|
|
155
|
-
|
|
156
|
-
## Copy to your agent
|
|
157
|
-
|
|
158
|
-
```text
|
|
159
|
-
READ docs-site/zh/example/claude-code-codex-plugin.mdx and install niceeval for this repo.
|
|
160
|
-
Use https://github.com/CorrectRoadH/coding-agent-skill as the reference shape: put the plugin in skills/, inject it from an experiment setup hook, write one .eval.ts task against a real workspace, then add a no-plugin baseline experiment.
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## 下一步
|
|
164
|
-
|
|
165
|
-
- [Fixtures](/zh/guides/fixtures) — 用 `.eval.ts` 管理 sandbox workspace、任务和验证。
|
|
166
|
-
- [Sandbox Agent](/zh/guides/sandbox-agent) — 内置 `claude-code`、`codex` 和自定义 sandbox agent。
|
|
167
|
-
- [查看结果](/zh/guides/viewing-results) — 看 transcript、diff、event stream 和 trace。
|