@aipack-ai/eval 1.1.6

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.
Files changed (37) hide show
  1. package/dist/index.d.ts +24 -0
  2. package/dist/index.js +26 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/src/cli.d.ts +12 -0
  5. package/dist/src/cli.js +149 -0
  6. package/dist/src/cli.js.map +1 -0
  7. package/dist/src/core/loader.d.ts +21 -0
  8. package/dist/src/core/loader.js +91 -0
  9. package/dist/src/core/loader.js.map +1 -0
  10. package/dist/src/core/mock-stream.d.ts +16 -0
  11. package/dist/src/core/mock-stream.js +71 -0
  12. package/dist/src/core/mock-stream.js.map +1 -0
  13. package/dist/src/core/mock-tools.d.ts +24 -0
  14. package/dist/src/core/mock-tools.js +161 -0
  15. package/dist/src/core/mock-tools.js.map +1 -0
  16. package/dist/src/core/report.d.ts +26 -0
  17. package/dist/src/core/report.js +149 -0
  18. package/dist/src/core/report.js.map +1 -0
  19. package/dist/src/core/runner.d.ts +18 -0
  20. package/dist/src/core/runner.js +213 -0
  21. package/dist/src/core/runner.js.map +1 -0
  22. package/dist/src/core/scorer/index.d.ts +19 -0
  23. package/dist/src/core/scorer/index.js +56 -0
  24. package/dist/src/core/scorer/index.js.map +1 -0
  25. package/dist/src/core/scorer/rule.d.ts +17 -0
  26. package/dist/src/core/scorer/rule.js +231 -0
  27. package/dist/src/core/scorer/rule.js.map +1 -0
  28. package/dist/src/core/trajectory.d.ts +13 -0
  29. package/dist/src/core/trajectory.js +41 -0
  30. package/dist/src/core/trajectory.js.map +1 -0
  31. package/dist/src/core/types.d.ts +251 -0
  32. package/dist/src/core/types.js +16 -0
  33. package/dist/src/core/types.js.map +1 -0
  34. package/dist/src/core/validate.d.ts +7 -0
  35. package/dist/src/core/validate.js +71 -0
  36. package/dist/src/core/validate.js.map +1 -0
  37. package/package.json +53 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @aipack-ai/eval —— aipack Eval 体系(M1:mock 模式 + 规则评分器)
3
+ *
4
+ * 快速上手:
5
+ * import { loadCases, runEval, renderMarkdown } from '@aipack-ai/eval';
6
+ * const { cases } = await loadCases(); // 默认 eval/cases
7
+ * const report = await runEval(cases, { mode: 'mock' });
8
+ * console.log(renderMarkdown(report));
9
+ *
10
+ * CLI:
11
+ * pnpm --filter @aipack-ai/eval eval -- --suite tool-calling
12
+ */
13
+ export type { CaseOrigin, MockToolCall, MockAssistantTurn, MockScript, AgentInput, ExpectedToolCall, ExpectedResult, RuleScorerType, ScorerType, ScorerConfig, EvalCase, ToolCallRecord, RunTrace, ScoreResult, CaseResult, SuiteSummary, EvalReport, RunConfig, BaselineFile, BaselineComparison, } from './src/core/types';
14
+ export { createMockStreamFn } from './src/core/mock-stream';
15
+ export { createMockTools, STANDARD_MOCK_TOOLS, } from './src/core/mock-tools';
16
+ export type { StandardMockToolName } from './src/core/mock-tools';
17
+ export { extractTrajectory, countTurns } from './src/core/trajectory';
18
+ export { expectedToScorers, resolveScorers, scoreTrace, RULE_SCORER_TYPES, runRuleScorer, partialMatch, } from './src/core/scorer';
19
+ export type { CaseScore } from './src/core/scorer';
20
+ export { validateEvalCase } from './src/core/validate';
21
+ export { loadCases, defaultCasesDir, packageRoot, } from './src/core/loader';
22
+ export type { LoadCasesResult } from './src/core/loader';
23
+ export { runEval } from './src/core/runner';
24
+ export { renderMarkdown, writeReport, writeBaseline, readBaseline, reportToBaseline, compareBaseline, finalizeReport, } from './src/core/report';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @aipack-ai/eval —— aipack Eval 体系(M1:mock 模式 + 规则评分器)
3
+ *
4
+ * 快速上手:
5
+ * import { loadCases, runEval, renderMarkdown } from '@aipack-ai/eval';
6
+ * const { cases } = await loadCases(); // 默认 eval/cases
7
+ * const report = await runEval(cases, { mode: 'mock' });
8
+ * console.log(renderMarkdown(report));
9
+ *
10
+ * CLI:
11
+ * pnpm --filter @aipack-ai/eval eval -- --suite tool-calling
12
+ */
13
+ // ─── Mock 层(fixture replay)─────────────────────────────────────
14
+ export { createMockStreamFn } from './src/core/mock-stream.js';
15
+ export { createMockTools, STANDARD_MOCK_TOOLS, } from './src/core/mock-tools.js';
16
+ // ─── 轨迹提取 ─────────────────────────────────────────────────────
17
+ export { extractTrajectory, countTurns } from './src/core/trajectory.js';
18
+ // ─── 评分器 ───────────────────────────────────────────────────────
19
+ export { expectedToScorers, resolveScorers, scoreTrace, RULE_SCORER_TYPES, runRuleScorer, partialMatch, } from './src/core/scorer/index.js';
20
+ // ─── 校验 / 加载 ──────────────────────────────────────────────────
21
+ export { validateEvalCase } from './src/core/validate.js';
22
+ export { loadCases, defaultCasesDir, packageRoot, } from './src/core/loader.js';
23
+ // ─── Runner 与报告 ────────────────────────────────────────────────
24
+ export { runEval } from './src/core/runner.js';
25
+ export { renderMarkdown, writeReport, writeBaseline, readBaseline, reportToBaseline, compareBaseline, finalizeReport, } from './src/core/report.js';
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0BH,kEAAkE;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAG/B,iEAAiE;AACjE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEtE,kEAAkE;AAClE,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,YAAY,GACb,MAAM,mBAAmB,CAAC;AAG3B,iEAAiE;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,SAAS,EACT,eAAe,EACf,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAG3B,kEAAkE;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * packages/eval/src/cli.ts - aipack-eval 命令行入口
4
+ *
5
+ * 用法:
6
+ * aipack-eval run [--suite <name>]... [--cases-dir <dir>] [--repeats <n>]
7
+ * [--report-dir <dir>] [--baseline <path>]
8
+ * [--update-baseline] [--threshold <0.02>]
9
+ *
10
+ * 退出码:0 = 全过且无回归;1 = 有失败或回归;2 = 用法错误。
11
+ */
12
+ export {};
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * packages/eval/src/cli.ts - aipack-eval 命令行入口
4
+ *
5
+ * 用法:
6
+ * aipack-eval run [--suite <name>]... [--cases-dir <dir>] [--repeats <n>]
7
+ * [--report-dir <dir>] [--baseline <path>]
8
+ * [--update-baseline] [--threshold <0.02>]
9
+ *
10
+ * 退出码:0 = 全过且无回归;1 = 有失败或回归;2 = 用法错误。
11
+ */
12
+ import { loadCases } from './core/loader.js';
13
+ import { runEval } from './core/runner.js';
14
+ import { finalizeReport, renderMarkdown } from './core/report.js';
15
+ const USAGE = `用法: aipack-eval run [options]
16
+
17
+ 选项:
18
+ --suite <name> 只跑指定套件(可多次)
19
+ --cases-dir <dir> 用例目录(缺省 <包根>/eval/cases)
20
+ --report-dir <dir> 报告输出目录(缺省 ./eval-results;'none' 不写盘)
21
+ --baseline <path> baseline 文件(门禁对比)
22
+ --update-baseline 用本次结果更新 baseline
23
+ --threshold <x> 通过率回归阈值(缺省 0.02)
24
+ --repeats <n> 每用例重复次数(缺省 1)
25
+ -h, --help 显示帮助`;
26
+ function parseArgs(argv) {
27
+ const args = {
28
+ suites: [],
29
+ updateBaseline: false,
30
+ threshold: 0.02,
31
+ };
32
+ let i = 0;
33
+ while (i < argv.length) {
34
+ const a = argv[i];
35
+ const next = () => {
36
+ const v = argv[++i];
37
+ if (v === undefined)
38
+ throw new Error(`选项 ${a} 缺少参数值`);
39
+ return v;
40
+ };
41
+ try {
42
+ switch (a) {
43
+ case 'run':
44
+ args.command = 'run';
45
+ break;
46
+ case '--suite':
47
+ args.suites.push(next());
48
+ break;
49
+ case '--cases-dir':
50
+ args.casesDir = next();
51
+ break;
52
+ case '--report-dir':
53
+ args.reportDir = next();
54
+ break;
55
+ case '--baseline':
56
+ args.baselinePath = next();
57
+ break;
58
+ case '--update-baseline':
59
+ args.updateBaseline = true;
60
+ break;
61
+ case '--threshold': {
62
+ const v = Number(next());
63
+ if (!Number.isFinite(v) || v < 0 || v > 1)
64
+ throw new Error('阈值须为 0~1');
65
+ args.threshold = v;
66
+ break;
67
+ }
68
+ case '--repeats': {
69
+ const v = Number(next());
70
+ if (!Number.isInteger(v) || v < 1)
71
+ throw new Error('repeats 须为正整数');
72
+ args.repeats = v;
73
+ break;
74
+ }
75
+ case '-h':
76
+ case '--help':
77
+ return { error: 'HELP' };
78
+ default:
79
+ return { error: `未知参数: ${a}\n${USAGE}` };
80
+ }
81
+ }
82
+ catch (e) {
83
+ return { error: `${e.message}\n${USAGE}` };
84
+ }
85
+ i += 1;
86
+ }
87
+ if (args.command !== 'run')
88
+ return { error: `缺少子命令 'run'\n${USAGE}` };
89
+ return args;
90
+ }
91
+ async function main() {
92
+ const parsed = parseArgs(process.argv.slice(2));
93
+ if ('error' in parsed) {
94
+ if (parsed.error === 'HELP') {
95
+ console.log(USAGE);
96
+ return 0;
97
+ }
98
+ console.error(parsed.error);
99
+ return 2;
100
+ }
101
+ const { cases, errors } = await loadCases(parsed.casesDir);
102
+ if (errors.length > 0) {
103
+ console.error('用例加载错误:');
104
+ for (const e of errors)
105
+ console.error(` - ${e}`);
106
+ return 1;
107
+ }
108
+ if (cases.length === 0) {
109
+ console.error('没有可运行的用例');
110
+ return 1;
111
+ }
112
+ const config = {
113
+ mode: 'mock',
114
+ suites: parsed.suites,
115
+ repeats: parsed.repeats,
116
+ casesDir: parsed.casesDir,
117
+ reportDir: parsed.reportDir === 'none' ? undefined : parsed.reportDir,
118
+ baselinePath: parsed.baselinePath,
119
+ updateBaseline: parsed.updateBaseline,
120
+ regressionThreshold: parsed.threshold,
121
+ };
122
+ console.error(`加载 ${cases.length} 个用例${parsed.suites.length ? `(套件: ${parsed.suites.join(', ')})` : ''},mode=mock`);
123
+ const report = await runEval(cases, config);
124
+ const finalized = await finalizeReport(report, config);
125
+ console.log(renderMarkdown(report));
126
+ if (finalized.jsonPath)
127
+ console.error(`\n报告已写入: ${finalized.jsonPath}`);
128
+ if (config.updateBaseline && finalized.baseline === undefined) {
129
+ console.error(`baseline 已更新: ${config.baselinePath}`);
130
+ }
131
+ let exitCode = report.totals.passed === report.totals.cases ? 0 : 1;
132
+ if (finalized.comparison && !finalized.comparison.ok) {
133
+ exitCode = 1;
134
+ const { overallDelta, regressions } = finalized.comparison;
135
+ console.error(`\n⚠️ baseline 回归(整体 ${overallDelta >= 0 ? '+' : ''}${(overallDelta * 100).toFixed(1)}%):`);
136
+ for (const r of regressions) {
137
+ console.error(` - ${r.suite}: ${(r.baseline * 100).toFixed(1)}% → ${(r.current * 100).toFixed(1)}%(${(r.delta * 100).toFixed(1)}%)`);
138
+ }
139
+ }
140
+ else if (finalized.comparison) {
141
+ console.error(`\nbaseline 门禁通过(${(finalized.comparison.overallDelta * 100).toFixed(1)}%)`);
142
+ }
143
+ return exitCode;
144
+ }
145
+ main().then((code) => process.exit(code), (e) => {
146
+ console.error(e);
147
+ process.exit(1);
148
+ });
149
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * packages/eval/src/core/loader.ts - 用例加载器
3
+ *
4
+ * 递归扫描用例目录(缺省 <包根>/eval/cases),解析 JSON 为 EvalCase,
5
+ * 加载即校验(fail fast),重复 id 报错。
6
+ */
7
+ import type { EvalCase } from './types';
8
+ /** 包根目录:从当前文件向上查找最近的 package.json(源码与 dist 产物通用) */
9
+ export declare function packageRoot(): string;
10
+ export declare function defaultCasesDir(): string;
11
+ export interface LoadCasesResult {
12
+ cases: EvalCase[];
13
+ /** 相对用例目录的文件路径(诊断用) */
14
+ sources: Map<string, string>;
15
+ errors: string[];
16
+ }
17
+ /**
18
+ * 加载用例目录。校验失败的文件进入 errors(不中断整体加载),
19
+ * 但重复 id 一律报错(防套件意外覆盖)。
20
+ */
21
+ export declare function loadCases(casesDir?: string): Promise<LoadCasesResult>;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * packages/eval/src/core/loader.ts - 用例加载器
3
+ *
4
+ * 递归扫描用例目录(缺省 <包根>/eval/cases),解析 JSON 为 EvalCase,
5
+ * 加载即校验(fail fast),重复 id 报错。
6
+ */
7
+ import { readdir, readFile, stat } from 'node:fs/promises';
8
+ import { existsSync } from 'node:fs';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { dirname, join, resolve } from 'node:path';
11
+ import { validateEvalCase } from './validate.js';
12
+ /** 包根目录:从当前文件向上查找最近的 package.json(源码与 dist 产物通用) */
13
+ export function packageRoot() {
14
+ let dir = dirname(fileURLToPath(import.meta.url));
15
+ for (let i = 0; i < 10; i += 1) {
16
+ if (existsSync(join(dir, 'package.json')))
17
+ return dir;
18
+ const parent = dirname(dir);
19
+ if (parent === dir)
20
+ break;
21
+ dir = parent;
22
+ }
23
+ // 兜底:回到源码相对位置(src/core → 上两级)
24
+ return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
25
+ }
26
+ export function defaultCasesDir() {
27
+ return join(packageRoot(), 'eval', 'cases');
28
+ }
29
+ async function* walk(dir) {
30
+ const entries = await readdir(dir, { withFileTypes: true });
31
+ for (const e of entries) {
32
+ const full = join(dir, e.name);
33
+ if (e.isDirectory())
34
+ yield* walk(full);
35
+ else if (e.isFile() && (e.name.endsWith('.json') || e.name.endsWith('.jsonc'))) {
36
+ yield full;
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * 加载用例目录。校验失败的文件进入 errors(不中断整体加载),
42
+ * 但重复 id 一律报错(防套件意外覆盖)。
43
+ */
44
+ export async function loadCases(casesDir) {
45
+ const dir = resolve(casesDir ?? defaultCasesDir());
46
+ const cases = [];
47
+ const sources = new Map();
48
+ const errors = [];
49
+ const seenIds = new Map();
50
+ let dirExists = true;
51
+ try {
52
+ await stat(dir);
53
+ }
54
+ catch {
55
+ dirExists = false;
56
+ }
57
+ if (!dirExists) {
58
+ return { cases, sources, errors: [`用例目录不存在: ${dir}`] };
59
+ }
60
+ for await (const file of walk(dir)) {
61
+ const rel = file.slice(dir.length + 1);
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(await readFile(file, 'utf-8'));
65
+ }
66
+ catch (e) {
67
+ errors.push(`${rel}: JSON 解析失败 - ${e.message}`);
68
+ continue;
69
+ }
70
+ // 支持单文件单用例或数组
71
+ const list = Array.isArray(parsed) ? parsed : [parsed];
72
+ for (const item of list) {
73
+ const c = item;
74
+ const caseErrors = validateEvalCase(c);
75
+ if (caseErrors.length > 0) {
76
+ errors.push(`${rel}: ${caseErrors.join('; ')}`);
77
+ continue;
78
+ }
79
+ const dup = seenIds.get(c.id);
80
+ if (dup) {
81
+ errors.push(`${rel}: id '${c.id}' 与 ${dup} 重复`);
82
+ continue;
83
+ }
84
+ seenIds.set(c.id, rel);
85
+ cases.push(c);
86
+ sources.set(c.id, rel);
87
+ }
88
+ }
89
+ return { cases, sources, errors };
90
+ }
91
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/core/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * packages/eval/src/core/mock-stream.ts - Mock StreamFn(fixture replay)
3
+ *
4
+ * 通过 RuntimeOptions.streamFn 注入脚本化的 LLM 响应,零改动 Runtime。
5
+ * 每次 streamFn 调用消费 MockScript.turns 中的一条;轮次耗尽后按
6
+ * fallbackText / infiniteTool 兜底。
7
+ */
8
+ import type { StreamFn } from '@aipack-ai/agent';
9
+ import type { MockScript } from './types';
10
+ /**
11
+ * 创建脚本式 StreamFn:第 n 次调用返回第 n 轮脚本。
12
+ *
13
+ * - 轮次耗尽且未配置 infiniteTool:返回 stopReason 'stop' 的兜底文本轮
14
+ * - 配置 infiniteTool:持续返回该工具调用(模拟死循环,配合 maxTurns 断言)
15
+ */
16
+ export declare function createMockStreamFn(script: MockScript): StreamFn;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * packages/eval/src/core/mock-stream.ts - Mock StreamFn(fixture replay)
3
+ *
4
+ * 通过 RuntimeOptions.streamFn 注入脚本化的 LLM 响应,零改动 Runtime。
5
+ * 每次 streamFn 调用消费 MockScript.turns 中的一条;轮次耗尽后按
6
+ * fallbackText / infiniteTool 兜底。
7
+ */
8
+ const DEFAULT_USAGE = { input: 10, output: 5, total: 15 };
9
+ /** 把脚本轮次转成框架层 AssistantMessage */
10
+ function buildTurnMessage(turn, idBase) {
11
+ const content = [];
12
+ if (turn.text)
13
+ content.push({ type: 'text', text: turn.text });
14
+ for (const [i, tc] of (turn.toolCalls ?? []).entries()) {
15
+ content.push({
16
+ type: 'toolCall',
17
+ id: tc.id ?? `mock_tc_${idBase}_${i + 1}`,
18
+ name: tc.name,
19
+ arguments: tc.args ?? {},
20
+ });
21
+ }
22
+ const hasToolCalls = (turn.toolCalls ?? []).length > 0;
23
+ return {
24
+ role: 'assistant',
25
+ content,
26
+ stopReason: turn.stopReason ?? (hasToolCalls ? 'toolUse' : 'stop'),
27
+ usage: {
28
+ input: turn.usage?.input ?? DEFAULT_USAGE.input,
29
+ output: turn.usage?.output ?? DEFAULT_USAGE.output,
30
+ total: turn.usage?.total ?? DEFAULT_USAGE.total,
31
+ },
32
+ timestamp: Date.now(),
33
+ };
34
+ }
35
+ /**
36
+ * 创建脚本式 StreamFn:第 n 次调用返回第 n 轮脚本。
37
+ *
38
+ * - 轮次耗尽且未配置 infiniteTool:返回 stopReason 'stop' 的兜底文本轮
39
+ * - 配置 infiniteTool:持续返回该工具调用(模拟死循环,配合 maxTurns 断言)
40
+ */
41
+ export function createMockStreamFn(script) {
42
+ let callIndex = 0;
43
+ return async function* () {
44
+ // 每次调用让出一次事件循环(模拟网络延迟):
45
+ // 保证墙钟超时 / 并发调度有执行机会,行为更接近真实 provider
46
+ await new Promise((resolve) => setImmediate(resolve));
47
+ const turn = script.turns[callIndex];
48
+ if (turn) {
49
+ callIndex += 1;
50
+ yield { type: 'done', message: buildTurnMessage(turn, callIndex) };
51
+ return;
52
+ }
53
+ if (script.infiniteTool) {
54
+ callIndex += 1;
55
+ yield {
56
+ type: 'done',
57
+ message: buildTurnMessage({
58
+ toolCalls: [
59
+ { name: script.infiniteTool, args: script.infiniteToolArgs ?? {} },
60
+ ],
61
+ }, callIndex),
62
+ };
63
+ return;
64
+ }
65
+ yield {
66
+ type: 'done',
67
+ message: buildTurnMessage({ text: script.fallbackText ?? 'done' }, callIndex + 1),
68
+ };
69
+ };
70
+ }
71
+ //# sourceMappingURL=mock-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-stream.js","sourceRoot":"","sources":["../../../src/core/mock-stream.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,MAAM,aAAa,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAE1D,kCAAkC;AAClC,SAAS,gBAAgB,CACvB,IAKC,EACD,MAAc;IAEd,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/D,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,UAAU;YAChB,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,WAAW,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE;YACzC,IAAI,EAAE,EAAE,CAAC,IAAI;YACb,SAAS,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE;SACzB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACvD,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,OAAO;QACP,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;QAClE,KAAK,EAAE;YACL,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,aAAa,CAAC,KAAK;YAC/C,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,aAAa,CAAC,MAAM;YAClD,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,aAAa,CAAC,KAAK;SAChD;QACD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACtB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,OAAO,KAAK,SAAS,CAAC;QACpB,wBAAwB;QACxB,sCAAsC;QACtC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,IAAI,EAAE,CAAC;YACT,SAAS,IAAI,CAAC,CAAC;YACf,MAAM,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;YAC5E,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,SAAS,IAAI,CAAC,CAAC;YACf,MAAM;gBACJ,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE,gBAAgB,CACvB;oBACE,SAAS,EAAE;wBACT,EAAE,IAAI,EAAE,MAAM,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,gBAAgB,IAAI,EAAE,EAAE;qBACnE;iBACF,EACD,SAAS,CACV;aACF,CAAC;YACF,OAAO;QACT,CAAC;QACD,MAAM;YACJ,IAAI,EAAE,MAAe;YACrB,OAAO,EAAE,gBAAgB,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,YAAY,IAAI,MAAM,EAAE,EAAE,SAAS,GAAG,CAAC,CAAC;SAClF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * packages/eval/src/core/mock-tools.ts - 标准 mock 工具集
3
+ *
4
+ * 提供一组确定性的内存工具,供 golden 用例构造真实的工作流:
5
+ * echo - 回显参数
6
+ * readFile - 读 case 预置的内存文件系统
7
+ * writeFile - 写内存文件系统
8
+ * listDir - 列出前缀匹配的文件
9
+ * search - 跨文件全文检索
10
+ * fail - 恒定失败(测错误恢复轨迹)
11
+ *
12
+ * 工具按 case 隔离:每次 createMockTools 都返回独立 fs 副本。
13
+ */
14
+ import type { Tool } from '@aipack-ai/agent';
15
+ /** 标准工具名列表(用例 input.tools 缺省时的注册范围) */
16
+ export declare const STANDARD_MOCK_TOOLS: readonly ["echo", "readFile", "writeFile", "listDir", "search", "fail"];
17
+ export type StandardMockToolName = (typeof STANDARD_MOCK_TOOLS)[number];
18
+ /**
19
+ * 创建一组隔离的 mock 工具。
20
+ *
21
+ * @param fs 预置文件系统(会被深拷贝,case 间互不影响)
22
+ * @param names 需要的工具名列表;缺省全部标准工具
23
+ */
24
+ export declare function createMockTools(fs?: Record<string, string>, names?: string[]): Tool[];
@@ -0,0 +1,161 @@
1
+ /**
2
+ * packages/eval/src/core/mock-tools.ts - 标准 mock 工具集
3
+ *
4
+ * 提供一组确定性的内存工具,供 golden 用例构造真实的工作流:
5
+ * echo - 回显参数
6
+ * readFile - 读 case 预置的内存文件系统
7
+ * writeFile - 写内存文件系统
8
+ * listDir - 列出前缀匹配的文件
9
+ * search - 跨文件全文检索
10
+ * fail - 恒定失败(测错误恢复轨迹)
11
+ *
12
+ * 工具按 case 隔离:每次 createMockTools 都返回独立 fs 副本。
13
+ */
14
+ const STRING_SCHEMA = {
15
+ type: 'object',
16
+ properties: {
17
+ message: { type: 'string', description: '要回显的消息' },
18
+ },
19
+ required: ['message'],
20
+ };
21
+ const READ_FILE_SCHEMA = {
22
+ type: 'object',
23
+ properties: {
24
+ path: { type: 'string', description: '文件路径' },
25
+ },
26
+ required: ['path'],
27
+ };
28
+ const WRITE_FILE_SCHEMA = {
29
+ type: 'object',
30
+ properties: {
31
+ path: { type: 'string', description: '文件路径' },
32
+ content: { type: 'string', description: '写入内容' },
33
+ },
34
+ required: ['path', 'content'],
35
+ };
36
+ const LIST_DIR_SCHEMA = {
37
+ type: 'object',
38
+ properties: {
39
+ path: { type: 'string', description: '目录前缀' },
40
+ },
41
+ };
42
+ const SEARCH_SCHEMA = {
43
+ type: 'object',
44
+ properties: {
45
+ query: { type: 'string', description: '检索关键词' },
46
+ },
47
+ required: ['query'],
48
+ };
49
+ const FAIL_SCHEMA = {
50
+ type: 'object',
51
+ properties: {},
52
+ };
53
+ function textResult(text, details = null) {
54
+ const content = [{ type: 'text', text }];
55
+ return { content, details };
56
+ }
57
+ function errorResult(message) {
58
+ const content = [{ type: 'text', text: `error: ${message}` }];
59
+ return { content, details: { error: message } };
60
+ }
61
+ /** 标准工具名列表(用例 input.tools 缺省时的注册范围) */
62
+ export const STANDARD_MOCK_TOOLS = [
63
+ 'echo',
64
+ 'readFile',
65
+ 'writeFile',
66
+ 'listDir',
67
+ 'search',
68
+ 'fail',
69
+ ];
70
+ /**
71
+ * 创建一组隔离的 mock 工具。
72
+ *
73
+ * @param fs 预置文件系统(会被深拷贝,case 间互不影响)
74
+ * @param names 需要的工具名列表;缺省全部标准工具
75
+ */
76
+ export function createMockTools(fs = {}, names) {
77
+ const files = new Map(Object.entries(structuredClone(fs)));
78
+ const all = {
79
+ echo: {
80
+ name: 'echo',
81
+ description: '回显输入消息(测试用)',
82
+ parameters: STRING_SCHEMA,
83
+ execute: async (_id, args) => {
84
+ const message = args?.message;
85
+ return textResult(String(message ?? ''));
86
+ },
87
+ },
88
+ readFile: {
89
+ name: 'readFile',
90
+ description: '读取内存文件系统中的文件内容',
91
+ parameters: READ_FILE_SCHEMA,
92
+ execute: async (_id, args) => {
93
+ const { path } = (args ?? {});
94
+ if (typeof path !== 'string')
95
+ return errorResult('path 必须为字符串');
96
+ const content = files.get(path);
97
+ if (content === undefined)
98
+ return errorResult(`文件不存在: ${path}`);
99
+ return textResult(content, { path, bytes: content.length });
100
+ },
101
+ },
102
+ writeFile: {
103
+ name: 'writeFile',
104
+ description: '写入内存文件系统(新建或覆盖)',
105
+ parameters: WRITE_FILE_SCHEMA,
106
+ execute: async (_id, args) => {
107
+ const { path, content } = (args ?? {});
108
+ if (typeof path !== 'string' || typeof content !== 'string') {
109
+ return errorResult('path 与 content 必须为字符串');
110
+ }
111
+ files.set(path, content);
112
+ return textResult(`ok: wrote ${content.length} bytes to ${path}`, {
113
+ path,
114
+ bytes: content.length,
115
+ });
116
+ },
117
+ },
118
+ listDir: {
119
+ name: 'listDir',
120
+ description: '列出内存文件系统中指定前缀下的文件',
121
+ parameters: LIST_DIR_SCHEMA,
122
+ execute: async (_id, args) => {
123
+ const { path } = (args ?? {});
124
+ const prefix = typeof path === 'string' ? path : '';
125
+ const entries = [...files.keys()]
126
+ .filter((k) => k.startsWith(prefix))
127
+ .sort();
128
+ return textResult(entries.join('\n') || '(empty)', { entries });
129
+ },
130
+ },
131
+ search: {
132
+ name: 'search',
133
+ description: '跨内存文件全文检索,返回 file:line:text 命中行',
134
+ parameters: SEARCH_SCHEMA,
135
+ execute: async (_id, args) => {
136
+ const { query } = (args ?? {});
137
+ if (typeof query !== 'string' || query.length === 0) {
138
+ return errorResult('query 必须为非空字符串');
139
+ }
140
+ const hits = [];
141
+ for (const [file, content] of files) {
142
+ content.split('\n').forEach((line, i) => {
143
+ if (line.includes(query))
144
+ hits.push(`${file}:${i + 1}:${line.trim()}`);
145
+ });
146
+ }
147
+ return textResult(hits.join('\n') || '(no matches)', { hits: hits.length });
148
+ },
149
+ },
150
+ fail: {
151
+ name: 'fail',
152
+ description: '恒定返回错误的工具(测错误恢复)',
153
+ parameters: FAIL_SCHEMA,
154
+ execute: async () => errorResult('intentional failure'),
155
+ },
156
+ };
157
+ if (!names || names.length === 0)
158
+ return [...STANDARD_MOCK_TOOLS].map((n) => all[n]);
159
+ return names.map((n) => all[n]).filter((t) => t !== undefined);
160
+ }
161
+ //# sourceMappingURL=mock-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-tools.js","sourceRoot":"","sources":["../../../src/core/mock-tools.ts"],"names":[],"mappings":"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"}