claude-flow 3.10.4 → 3.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +18 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.js +253 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.d.ts +198 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.js +651 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.d.ts +133 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.js +281 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.js +300 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.d.ts +138 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.js +260 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.js +246 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.d.ts +123 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.js +312 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.js +327 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.d.ts +125 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.js +350 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.js +228 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.d.ts +27 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.js +136 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.d.ts +45 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.js +242 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.d.ts +46 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.js +170 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.d.ts +105 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.js +260 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +235 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.js +179 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.js +437 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.d.ts +87 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.js +326 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.d.ts +35 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.js +403 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +126 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.js +225 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.d.ts +32 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.js +36 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.d.ts +62 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.js +12 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.d.ts +30 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.js +210 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.js +297 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.js +332 -0
- package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.js +597 -0
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +41 -4
- package/v3/@claude-flow/cli/dist/src/init/executor.js +25 -6
- package/v3/@claude-flow/cli/dist/src/init/mcp-generator.js +9 -4
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests for gaia-decomposer.ts (ADR-135 Track E)
|
|
3
|
+
*
|
|
4
|
+
* All HTTP calls are mocked — no live API calls, no cost.
|
|
5
|
+
* Covers 7 assertions:
|
|
6
|
+
* 1. Atomic question → decomposed=false, single sub-question = original
|
|
7
|
+
* 2. 3-step question → decomposed=true, 3 sub-questions in dependency order
|
|
8
|
+
* 3. Malformed JSON response → fallback to atomic (graceful degradation)
|
|
9
|
+
* 4. API error → fallback to atomic (graceful degradation)
|
|
10
|
+
* 5. synthesizeFromSubAnswers — atomic question returns sub-answer directly (no API call)
|
|
11
|
+
* 6. synthesizeFromSubAnswers — valid JSON response → finalAnswer + reasoning
|
|
12
|
+
* 7. synthesizeFromSubAnswers — malformed JSON → fallback to last sub-answer
|
|
13
|
+
*
|
|
14
|
+
* Run with:
|
|
15
|
+
* npx tsx src/benchmarks/gaia-decomposer.smoke.ts
|
|
16
|
+
*
|
|
17
|
+
* Expected cost: $0 (all mocked).
|
|
18
|
+
*/
|
|
19
|
+
import { decomposeQuestion, synthesizeFromSubAnswers } from './gaia-decomposer.js';
|
|
20
|
+
function installFetchMock(responder) {
|
|
21
|
+
globalThis.fetch = (url, init) => {
|
|
22
|
+
const r = responder(url, init);
|
|
23
|
+
return Promise.resolve(r);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function mockSuccessResponse(jsonBody) {
|
|
27
|
+
const bodyStr = JSON.stringify(jsonBody);
|
|
28
|
+
return {
|
|
29
|
+
ok: true,
|
|
30
|
+
status: 200,
|
|
31
|
+
text: () => Promise.resolve(bodyStr),
|
|
32
|
+
json: () => Promise.resolve(jsonBody),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function mockErrorResponse(status) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
status,
|
|
39
|
+
text: () => Promise.resolve(`HTTP ${status} error`),
|
|
40
|
+
json: () => Promise.reject(new Error('error response has no JSON')),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Build a minimal Anthropic Messages API response shape. */
|
|
44
|
+
function anthropicTextResponse(text, inputTokens = 100, outputTokens = 50) {
|
|
45
|
+
return {
|
|
46
|
+
id: 'msg_test',
|
|
47
|
+
type: 'message',
|
|
48
|
+
role: 'assistant',
|
|
49
|
+
content: [{ type: 'text', text }],
|
|
50
|
+
model: 'claude-haiku-4-5',
|
|
51
|
+
stop_reason: 'end_turn',
|
|
52
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Test runner
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
const PASS = '\x1b[32mPASS\x1b[0m';
|
|
59
|
+
const FAIL = '\x1b[31mFAIL\x1b[0m';
|
|
60
|
+
let failures = 0;
|
|
61
|
+
let passed = 0;
|
|
62
|
+
function check(label, condition) {
|
|
63
|
+
if (condition) {
|
|
64
|
+
console.log(` ${PASS} ${label}`);
|
|
65
|
+
passed++;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(` ${FAIL} ${label}`);
|
|
69
|
+
failures++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Smoke tests
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
async function runSmoke() {
|
|
76
|
+
console.log('\n=== gaia-decomposer smoke (mocked, $0) ===\n');
|
|
77
|
+
// Set a fake API key so resolveApiKey() doesn't throw
|
|
78
|
+
process.env.ANTHROPIC_API_KEY = 'sk-ant-test-key';
|
|
79
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
80
|
+
// Test 1: Atomic question → decomposed=false, single sub-question
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
82
|
+
console.log('-- Test 1: Atomic question --');
|
|
83
|
+
{
|
|
84
|
+
const atomicPayload = {
|
|
85
|
+
decomposed: false,
|
|
86
|
+
subQuestions: ['What year was the Eiffel Tower built?'],
|
|
87
|
+
synthesisHint: 'Use directly.',
|
|
88
|
+
};
|
|
89
|
+
installFetchMock(() => mockSuccessResponse(anthropicTextResponse(JSON.stringify(atomicPayload))));
|
|
90
|
+
const result = await decomposeQuestion('What year was the Eiffel Tower built?');
|
|
91
|
+
check('decomposed=false for atomic question', result.decomposed === false);
|
|
92
|
+
check('single sub-question equals original', result.subQuestions.length === 1 && result.subQuestions[0] === 'What year was the Eiffel Tower built?');
|
|
93
|
+
check('cost > 0 (Haiku tokens consumed)', result.cost > 0);
|
|
94
|
+
}
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// Test 2: 3-step complex question → decomposed=true, 3 sub-questions in order
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
98
|
+
console.log('\n-- Test 2: 3-step complex question --');
|
|
99
|
+
{
|
|
100
|
+
const complexPayload = {
|
|
101
|
+
decomposed: true,
|
|
102
|
+
subQuestions: [
|
|
103
|
+
'What year was the Eiffel Tower built?',
|
|
104
|
+
'What decade contains the year 1889?',
|
|
105
|
+
'Who directed the highest-grossing film of the 1880s?',
|
|
106
|
+
],
|
|
107
|
+
synthesisHint: 'The answer to sub-question 3 is the final answer.',
|
|
108
|
+
};
|
|
109
|
+
installFetchMock(() => mockSuccessResponse(anthropicTextResponse(JSON.stringify(complexPayload))));
|
|
110
|
+
const question = 'Who directed the highest-grossing film of the decade the Eiffel Tower was built?';
|
|
111
|
+
const result = await decomposeQuestion(question);
|
|
112
|
+
check('decomposed=true for 3-step question', result.decomposed === true);
|
|
113
|
+
check('3 sub-questions returned', result.subQuestions.length === 3);
|
|
114
|
+
check('first sub-question asks about Eiffel Tower year', result.subQuestions[0].toLowerCase().includes('eiffel tower'));
|
|
115
|
+
check('synthesisHint is non-empty', result.synthesisHint.length > 0);
|
|
116
|
+
}
|
|
117
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
118
|
+
// Test 3: Malformed JSON response → fallback to atomic
|
|
119
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
120
|
+
console.log('\n-- Test 3: Malformed JSON fallback --');
|
|
121
|
+
{
|
|
122
|
+
installFetchMock(() => mockSuccessResponse(anthropicTextResponse('This is not JSON at all { broken')));
|
|
123
|
+
const question = 'What is the capital of France?';
|
|
124
|
+
const result = await decomposeQuestion(question);
|
|
125
|
+
check('malformed JSON → decomposed=false', result.decomposed === false);
|
|
126
|
+
check('malformed JSON → sub-question is original', result.subQuestions[0] === question);
|
|
127
|
+
}
|
|
128
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
129
|
+
// Test 4: API error → fallback to atomic
|
|
130
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
131
|
+
console.log('\n-- Test 4: API error fallback --');
|
|
132
|
+
{
|
|
133
|
+
installFetchMock(() => mockErrorResponse(500));
|
|
134
|
+
const question = 'How many planets are in the solar system?';
|
|
135
|
+
const result = await decomposeQuestion(question);
|
|
136
|
+
check('API error → decomposed=false', result.decomposed === false);
|
|
137
|
+
check('API error → sub-question is original', result.subQuestions[0] === question);
|
|
138
|
+
check('API error → cost=0', result.cost === 0);
|
|
139
|
+
}
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
141
|
+
// Test 5: synthesizeFromSubAnswers — atomic question (decomposed=false)
|
|
142
|
+
// returns sub-answer directly WITHOUT an API call
|
|
143
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
144
|
+
console.log('\n-- Test 5: synthesize — atomic passthrough (no API call) --');
|
|
145
|
+
{
|
|
146
|
+
// Install a fetch mock that throws if called — should NOT be called
|
|
147
|
+
let fetchCalled = false;
|
|
148
|
+
installFetchMock(() => {
|
|
149
|
+
fetchCalled = true;
|
|
150
|
+
return mockSuccessResponse({ finalAnswer: 'unexpected', reasoning: 'should not reach here' });
|
|
151
|
+
});
|
|
152
|
+
const atomicDecomposed = {
|
|
153
|
+
originalQuestion: 'What year was the Eiffel Tower built?',
|
|
154
|
+
subQuestions: ['What year was the Eiffel Tower built?'],
|
|
155
|
+
synthesisHint: 'Use directly.',
|
|
156
|
+
decomposed: false,
|
|
157
|
+
cost: 0,
|
|
158
|
+
};
|
|
159
|
+
const result = await synthesizeFromSubAnswers(atomicDecomposed, ['1889']);
|
|
160
|
+
check('atomic synthesize returns sub-answer directly', result.finalAnswer === '1889');
|
|
161
|
+
check('atomic synthesize has cost=0', result.cost === 0);
|
|
162
|
+
check('atomic synthesize did NOT call API', fetchCalled === false);
|
|
163
|
+
}
|
|
164
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
165
|
+
// Test 6: synthesizeFromSubAnswers — valid JSON → finalAnswer + reasoning
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
167
|
+
console.log('\n-- Test 6: synthesize — valid JSON response --');
|
|
168
|
+
{
|
|
169
|
+
const synthPayload = {
|
|
170
|
+
finalAnswer: 'Georges Méliès',
|
|
171
|
+
reasoning: 'The highest-grossing film of the 1880s was X. Its director was Georges Méliès.',
|
|
172
|
+
};
|
|
173
|
+
installFetchMock(() => mockSuccessResponse(anthropicTextResponse(JSON.stringify(synthPayload))));
|
|
174
|
+
const decomposed = {
|
|
175
|
+
originalQuestion: 'Who directed the highest-grossing film of the decade the Eiffel Tower was built?',
|
|
176
|
+
subQuestions: [
|
|
177
|
+
'What year was the Eiffel Tower built?',
|
|
178
|
+
'What decade contains 1889?',
|
|
179
|
+
'Who directed the highest-grossing film of the 1880s?',
|
|
180
|
+
],
|
|
181
|
+
synthesisHint: 'The director from sub-question 3 is the answer.',
|
|
182
|
+
decomposed: true,
|
|
183
|
+
cost: 0.0003,
|
|
184
|
+
};
|
|
185
|
+
const result = await synthesizeFromSubAnswers(decomposed, ['1889', '1880s', 'Georges Méliès']);
|
|
186
|
+
check('synthesize returns correct finalAnswer', result.finalAnswer === 'Georges Méliès');
|
|
187
|
+
check('synthesize reasoning is non-empty', result.reasoning.length > 0);
|
|
188
|
+
check('synthesize cost > 0 (Sonnet tokens consumed)', result.cost > 0);
|
|
189
|
+
}
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
191
|
+
// Test 7: synthesizeFromSubAnswers — malformed JSON → fallback to last sub-answer
|
|
192
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
193
|
+
console.log('\n-- Test 7: synthesize — malformed JSON fallback --');
|
|
194
|
+
{
|
|
195
|
+
installFetchMock(() => mockSuccessResponse(anthropicTextResponse('{ bad json here ]]}')));
|
|
196
|
+
const decomposed = {
|
|
197
|
+
originalQuestion: 'How many people live in Tokyo?',
|
|
198
|
+
subQuestions: [
|
|
199
|
+
'What is the population of Tokyo metropolitan area?',
|
|
200
|
+
'What is the population of Tokyo proper?',
|
|
201
|
+
],
|
|
202
|
+
synthesisHint: 'Use the Tokyo proper figure.',
|
|
203
|
+
decomposed: true,
|
|
204
|
+
cost: 0.0003,
|
|
205
|
+
};
|
|
206
|
+
const result = await synthesizeFromSubAnswers(decomposed, ['37.4 million (metropolitan)', '13.96 million (proper)']);
|
|
207
|
+
check('malformed JSON fallback → returns last sub-answer', result.finalAnswer === '13.96 million (proper)');
|
|
208
|
+
check('malformed JSON fallback → reasoning mentions parse error', result.reasoning.toLowerCase().includes('parse error'));
|
|
209
|
+
}
|
|
210
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
211
|
+
// Summary
|
|
212
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
213
|
+
const total = passed + failures;
|
|
214
|
+
console.log(`\n=== smoke ${failures === 0 ? 'PASSED' : `FAILED (${failures}/${total} assertion(s))`} — ${passed}/${total} passed ===\n`);
|
|
215
|
+
if (failures > 0)
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
// Run when executed directly
|
|
219
|
+
const isMain = process.argv[1] && (process.argv[1].endsWith('gaia-decomposer.smoke.ts') ||
|
|
220
|
+
process.argv[1].endsWith('gaia-decomposer.smoke.js'));
|
|
221
|
+
if (isMain) {
|
|
222
|
+
runSmoke().catch((err) => {
|
|
223
|
+
console.error('Smoke failed with exception:', err);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
export { runSmoke };
|
|
228
|
+
//# sourceMappingURL=gaia-decomposer.smoke.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA End-to-End Smoke — ADR-133
|
|
3
|
+
*
|
|
4
|
+
* Wires gaia-agent.ts + gaia-judge.ts into a single end-to-end pipeline:
|
|
5
|
+
*
|
|
6
|
+
* for each question in SMOKE_FIXTURE:
|
|
7
|
+
* 1. runGaiaAgent(question) — Haiku agent loop, ≤8 turns
|
|
8
|
+
* 2. judgeAnswer(question, result.finalAnswer) — exact-match fast-path,
|
|
9
|
+
* Sonnet LLM-judge only if exact-match misses
|
|
10
|
+
*
|
|
11
|
+
* Reports: pass rate, total cost, mean turn count.
|
|
12
|
+
* Asserts: ≥ 3/5 questions pass (lenient — smoke fixture is not trivial).
|
|
13
|
+
*
|
|
14
|
+
* Cost discipline:
|
|
15
|
+
* - Agent: claude-haiku-4-5 at $0.25/$1.25 per M tokens
|
|
16
|
+
* - Judge: claude-sonnet-4-6 at $3/$15 per M tokens (only when needed)
|
|
17
|
+
* - Expected total for 5 questions × ~2 turns × Haiku + 1-2 Sonnet
|
|
18
|
+
* judge calls ≈ $0.02
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ANTHROPIC_API_KEY=sk-ant-... npx tsx src/benchmarks/gaia-e2e-smoke.ts
|
|
22
|
+
*
|
|
23
|
+
* Refs: ADR-133, #2156
|
|
24
|
+
*/
|
|
25
|
+
declare function runE2ESmoke(): Promise<void>;
|
|
26
|
+
export { runE2ESmoke };
|
|
27
|
+
//# sourceMappingURL=gaia-e2e-smoke.d.ts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA End-to-End Smoke — ADR-133
|
|
3
|
+
*
|
|
4
|
+
* Wires gaia-agent.ts + gaia-judge.ts into a single end-to-end pipeline:
|
|
5
|
+
*
|
|
6
|
+
* for each question in SMOKE_FIXTURE:
|
|
7
|
+
* 1. runGaiaAgent(question) — Haiku agent loop, ≤8 turns
|
|
8
|
+
* 2. judgeAnswer(question, result.finalAnswer) — exact-match fast-path,
|
|
9
|
+
* Sonnet LLM-judge only if exact-match misses
|
|
10
|
+
*
|
|
11
|
+
* Reports: pass rate, total cost, mean turn count.
|
|
12
|
+
* Asserts: ≥ 3/5 questions pass (lenient — smoke fixture is not trivial).
|
|
13
|
+
*
|
|
14
|
+
* Cost discipline:
|
|
15
|
+
* - Agent: claude-haiku-4-5 at $0.25/$1.25 per M tokens
|
|
16
|
+
* - Judge: claude-sonnet-4-6 at $3/$15 per M tokens (only when needed)
|
|
17
|
+
* - Expected total for 5 questions × ~2 turns × Haiku + 1-2 Sonnet
|
|
18
|
+
* judge calls ≈ $0.02
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ANTHROPIC_API_KEY=sk-ant-... npx tsx src/benchmarks/gaia-e2e-smoke.ts
|
|
22
|
+
*
|
|
23
|
+
* Refs: ADR-133, #2156
|
|
24
|
+
*/
|
|
25
|
+
import * as os from 'node:os';
|
|
26
|
+
import * as path from 'node:path';
|
|
27
|
+
import { SMOKE_FIXTURE, } from './gaia-loader.js';
|
|
28
|
+
import { runGaiaAgent, } from './gaia-agent.js';
|
|
29
|
+
import { judgeAnswer, } from './gaia-judge.js';
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Configuration
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
/** Agent model — Haiku only for cost discipline. */
|
|
34
|
+
const AGENT_MODEL = 'claude-haiku-4-5';
|
|
35
|
+
/** Judge model — Sonnet for semantic judgments (only when exact-match fails). */
|
|
36
|
+
const JUDGE_MODEL = 'claude-sonnet-4-6';
|
|
37
|
+
/** Minimum pass rate for smoke to succeed. */
|
|
38
|
+
const MIN_PASS_RATE = 3 / 5;
|
|
39
|
+
// Haiku pricing ($/M tokens)
|
|
40
|
+
const HAIKU_IN = 0.25;
|
|
41
|
+
const HAIKU_OUT = 1.25;
|
|
42
|
+
// Sonnet pricing ($/M tokens)
|
|
43
|
+
const SONNET_IN = 3.0;
|
|
44
|
+
const SONNET_OUT = 15.0;
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Runner
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
async function runE2ESmoke() {
|
|
49
|
+
const hasKey = !!(process.env.ANTHROPIC_API_KEY?.trim());
|
|
50
|
+
if (!hasKey) {
|
|
51
|
+
console.error('ANTHROPIC_API_KEY is required for the end-to-end smoke.\n' +
|
|
52
|
+
'Set it with: export ANTHROPIC_API_KEY=sk-ant-...');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const cacheDir = path.join(os.homedir(), '.cache', 'ruflo', 'gaia', 'judgments');
|
|
56
|
+
const judgeOpts = { judgeModel: JUDGE_MODEL, cacheDir };
|
|
57
|
+
const questions = SMOKE_FIXTURE;
|
|
58
|
+
const rows = [];
|
|
59
|
+
console.log(`\n=== GAIA End-to-End Smoke (${questions.length} questions) ===\n`);
|
|
60
|
+
console.log(`Agent: ${AGENT_MODEL} | Judge: ${JUDGE_MODEL}\n` +
|
|
61
|
+
`Questions: ${questions.length} | Min pass rate: ${(MIN_PASS_RATE * 100).toFixed(0)}%\n`);
|
|
62
|
+
for (const q of questions) {
|
|
63
|
+
process.stdout.write(`[${q.task_id}] "${q.question.slice(0, 60)}..." `);
|
|
64
|
+
// Run agent
|
|
65
|
+
const agentResult = await runGaiaAgent(q, { model: AGENT_MODEL });
|
|
66
|
+
// Judge
|
|
67
|
+
const judgeResult = await judgeAnswer({ id: q.task_id, expected: q.final_answer, questionText: q.question }, agentResult.finalAnswer, judgeOpts);
|
|
68
|
+
rows.push({ question: q, agentResult, judgeResult });
|
|
69
|
+
const verdict = judgeResult.passed ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
|
|
70
|
+
const path_ = judgeResult.scoringPath;
|
|
71
|
+
console.log(`${verdict} (turns=${agentResult.turns}, ` +
|
|
72
|
+
`answer="${agentResult.finalAnswer ?? 'null'}", ` +
|
|
73
|
+
`expected="${q.final_answer}", path=${path_})`);
|
|
74
|
+
}
|
|
75
|
+
// ── Summary ──
|
|
76
|
+
console.log('\n--- Summary ---\n');
|
|
77
|
+
const passed = rows.filter((r) => r.judgeResult.passed).length;
|
|
78
|
+
const total = rows.length;
|
|
79
|
+
const passRate = passed / total;
|
|
80
|
+
let totalAgentCostUsd = 0;
|
|
81
|
+
let totalJudgeCostUsd = 0;
|
|
82
|
+
let totalTurns = 0;
|
|
83
|
+
for (const row of rows) {
|
|
84
|
+
const { agentResult, judgeResult } = row;
|
|
85
|
+
totalTurns += agentResult.turns;
|
|
86
|
+
totalAgentCostUsd +=
|
|
87
|
+
(agentResult.totalInputTokens / 1_000_000) * HAIKU_IN +
|
|
88
|
+
(agentResult.totalOutputTokens / 1_000_000) * HAIKU_OUT;
|
|
89
|
+
totalJudgeCostUsd += judgeResult.judgeCostUsd ?? 0;
|
|
90
|
+
}
|
|
91
|
+
const totalCostUsd = totalAgentCostUsd + totalJudgeCostUsd;
|
|
92
|
+
const meanTurns = totalTurns / total;
|
|
93
|
+
console.log(`Pass rate : ${passed}/${total} (${(passRate * 100).toFixed(0)}%)`);
|
|
94
|
+
console.log(`Mean turns : ${meanTurns.toFixed(1)}`);
|
|
95
|
+
console.log(`Agent cost : $${totalAgentCostUsd.toFixed(5)} (Haiku)`);
|
|
96
|
+
console.log(`Judge cost : $${totalJudgeCostUsd.toFixed(5)} (Sonnet, only when needed)`);
|
|
97
|
+
console.log(`Total cost : $${totalCostUsd.toFixed(5)}`);
|
|
98
|
+
// ── Per-row detail ──
|
|
99
|
+
console.log('\n--- Per-question detail ---\n');
|
|
100
|
+
for (const row of rows) {
|
|
101
|
+
const { question, agentResult, judgeResult } = row;
|
|
102
|
+
const verdict = judgeResult.passed ? 'PASS' : 'FAIL';
|
|
103
|
+
console.log(` ${verdict} ${question.task_id} turns=${agentResult.turns} ` +
|
|
104
|
+
`path=${judgeResult.scoringPath} ` +
|
|
105
|
+
`answer="${agentResult.finalAnswer ?? 'null'}" ` +
|
|
106
|
+
`expected="${question.final_answer}"`);
|
|
107
|
+
if (judgeResult.judgeReason) {
|
|
108
|
+
console.log(` reason: ${judgeResult.judgeReason}`);
|
|
109
|
+
}
|
|
110
|
+
if (agentResult.error) {
|
|
111
|
+
console.log(` error : ${agentResult.error}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ── Assertion ──
|
|
115
|
+
console.log('');
|
|
116
|
+
if (passRate >= MIN_PASS_RATE) {
|
|
117
|
+
console.log(`\x1b[32mSmoke PASSED\x1b[0m — ${passed}/${total} ≥ ${(MIN_PASS_RATE * 100).toFixed(0)}% required.\n`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.error(`\x1b[31mSmoke FAILED\x1b[0m — ${passed}/${total} < ${(MIN_PASS_RATE * 100).toFixed(0)}% required.\n`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Entry point
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
const isMain = process.argv[1] && (process.argv[1].endsWith('gaia-e2e-smoke.ts') ||
|
|
128
|
+
process.argv[1].endsWith('gaia-e2e-smoke.js'));
|
|
129
|
+
if (isMain) {
|
|
130
|
+
runE2ESmoke().catch((err) => {
|
|
131
|
+
console.error('E2E smoke failed:', err);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
export { runE2ESmoke };
|
|
136
|
+
//# sourceMappingURL=gaia-e2e-smoke.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests for extractFinalAnswer + buildUserMessage (iter 53a T2 narrowed).
|
|
3
|
+
*
|
|
4
|
+
* Iter 53a changes vs iter 52 T2:
|
|
5
|
+
* - Stage 2/3 (prose fallback) REMOVED — extractFinalAnswer is Stage 1 only.
|
|
6
|
+
* - Commitment prompt no longer has "FINAL_ANSWER: unknown" surrender instruction.
|
|
7
|
+
* - Reversed-text preprocessor PRESERVED (not the source of regressions).
|
|
8
|
+
*
|
|
9
|
+
* Anti-regression suite (7 cases):
|
|
10
|
+
* These are the exact failure modes from iter 52 T2 that must not regress.
|
|
11
|
+
* They test that the extraction logic is stable and correct for the 7 questions
|
|
12
|
+
* where iter 52 broke but iter 51 was correct.
|
|
13
|
+
*
|
|
14
|
+
* Run (after build):
|
|
15
|
+
* node dist/benchmarks/gaia-extract.smoke.js
|
|
16
|
+
*
|
|
17
|
+
* Exit 0 on all pass, 1 on any failure.
|
|
18
|
+
*
|
|
19
|
+
* Refs: ADR-133, ADR-135, iter 52 T2, iter 53a, #2156
|
|
20
|
+
*/
|
|
21
|
+
interface AnthropicResponse {
|
|
22
|
+
id: string;
|
|
23
|
+
model: string;
|
|
24
|
+
stop_reason: string;
|
|
25
|
+
content: Array<{
|
|
26
|
+
type: string;
|
|
27
|
+
text?: string;
|
|
28
|
+
}>;
|
|
29
|
+
usage: {
|
|
30
|
+
input_tokens: number;
|
|
31
|
+
output_tokens: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
declare let _extractFinalAnswer: (resp: AnthropicResponse) => string | null;
|
|
35
|
+
declare let _buildUserMessage: (question: string) => string;
|
|
36
|
+
declare function makeResp(text: string): AnthropicResponse;
|
|
37
|
+
interface TestCase {
|
|
38
|
+
label: string;
|
|
39
|
+
input: string;
|
|
40
|
+
expected: string | null;
|
|
41
|
+
isUserMsg?: boolean;
|
|
42
|
+
}
|
|
43
|
+
declare const CASES: TestCase[];
|
|
44
|
+
declare function main(): Promise<void>;
|
|
45
|
+
//# sourceMappingURL=gaia-extract.smoke.d.ts.map
|