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,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Smoke tests for extractFinalAnswer + buildUserMessage (iter 53a T2 narrowed).
|
|
4
|
+
*
|
|
5
|
+
* Iter 53a changes vs iter 52 T2:
|
|
6
|
+
* - Stage 2/3 (prose fallback) REMOVED — extractFinalAnswer is Stage 1 only.
|
|
7
|
+
* - Commitment prompt no longer has "FINAL_ANSWER: unknown" surrender instruction.
|
|
8
|
+
* - Reversed-text preprocessor PRESERVED (not the source of regressions).
|
|
9
|
+
*
|
|
10
|
+
* Anti-regression suite (7 cases):
|
|
11
|
+
* These are the exact failure modes from iter 52 T2 that must not regress.
|
|
12
|
+
* They test that the extraction logic is stable and correct for the 7 questions
|
|
13
|
+
* where iter 52 broke but iter 51 was correct.
|
|
14
|
+
*
|
|
15
|
+
* Run (after build):
|
|
16
|
+
* node dist/benchmarks/gaia-extract.smoke.js
|
|
17
|
+
*
|
|
18
|
+
* Exit 0 on all pass, 1 on any failure.
|
|
19
|
+
*
|
|
20
|
+
* Refs: ADR-133, ADR-135, iter 52 T2, iter 53a, #2156
|
|
21
|
+
*/
|
|
22
|
+
// Dynamic import so this file can run either as TS source (tsx) or compiled JS.
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
let _extractFinalAnswer;
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
let _buildUserMessage;
|
|
27
|
+
function makeResp(text) {
|
|
28
|
+
return {
|
|
29
|
+
id: 'test',
|
|
30
|
+
model: 'test',
|
|
31
|
+
stop_reason: 'end_turn',
|
|
32
|
+
content: [{ type: 'text', text }],
|
|
33
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const CASES = [
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Stage 1: primary FINAL_ANSWER: pattern (iter 53a: this is the ONLY stage)
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
{
|
|
41
|
+
label: 'Stage1 basic FINAL_ANSWER:',
|
|
42
|
+
input: 'I searched and found the capital.\nFINAL_ANSWER: Paris',
|
|
43
|
+
expected: 'Paris',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: 'Stage1 case-insensitive final_answer:',
|
|
47
|
+
input: 'The result is known.\nfinal_answer: 42',
|
|
48
|
+
expected: '42',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'Stage1 with leading whitespace',
|
|
52
|
+
input: 'Done.\n FINAL_ANSWER: Tokyo ',
|
|
53
|
+
expected: 'Tokyo',
|
|
54
|
+
},
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Prose-only outputs with no FINAL_ANSWER: tag — must return null (iter 53a).
|
|
57
|
+
// These were previously handled by Stage 2/3 (iter 52 T2).
|
|
58
|
+
// In iter 53a, we rely on the agent committing with the tag instead.
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
{
|
|
61
|
+
label: 'Prose-only "The answer is X" — null in iter 53a (no Stage 2)',
|
|
62
|
+
input: 'After analysis, the answer is Right.',
|
|
63
|
+
expected: null,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'Prose-only "Therefore X" — null in iter 53a (no Stage 2)',
|
|
67
|
+
input: 'Based on the clues, therefore the answer is Berlin.',
|
|
68
|
+
expected: null,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
label: 'Prose-only "Answer: X" — null in iter 53a (no Stage 2)',
|
|
72
|
+
input: 'Let me compute this.\nAnswer: 17',
|
|
73
|
+
expected: null,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: 'Prose-only all-caps last line — null in iter 53a (no Stage 3)',
|
|
77
|
+
input: 'I computed the value based on many steps.\nRIGHT',
|
|
78
|
+
expected: null,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: 'Prose-only numeric last line — null in iter 53a (no Stage 3)',
|
|
82
|
+
input: 'The final calculation gives us:\n346',
|
|
83
|
+
expected: null,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: 'Prose-only short-phrase last line — null in iter 53a (no Stage 3)',
|
|
87
|
+
input: 'After extensive research, the result is:\nBerlin, Germany',
|
|
88
|
+
expected: null,
|
|
89
|
+
},
|
|
90
|
+
// Null case: no answer extractable (verbose prose, no commitment)
|
|
91
|
+
{
|
|
92
|
+
label: 'Null case: only tool-call reasoning, no commitment',
|
|
93
|
+
input: 'I tried searching but could not find the specific information requested.',
|
|
94
|
+
expected: null,
|
|
95
|
+
},
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Reversed text pre-processor (buildUserMessage) — PRESERVED in iter 53a
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
{
|
|
100
|
+
label: 'Reversed text: adds decoded hint',
|
|
101
|
+
input: '.rewsna eht sa "tfel" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI',
|
|
102
|
+
expected: '[NOTE:',
|
|
103
|
+
isUserMsg: true,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
label: 'Normal text: no hint added',
|
|
107
|
+
input: 'What is the capital of France?',
|
|
108
|
+
expected: 'What is the capital of France?',
|
|
109
|
+
isUserMsg: true,
|
|
110
|
+
},
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Anti-regression suite (iter 53a) — 7 cases for iter 52 regressions.
|
|
113
|
+
//
|
|
114
|
+
// These represent the exact regression patterns from iter 52 T2:
|
|
115
|
+
// a1e91b78: agent surrendered with "unknown" (commitment prompt over-trigger)
|
|
116
|
+
// 935e2cff: agent gave empty answer (Stage 2/3 failed after extraction)
|
|
117
|
+
// 305ac316: agent gave empty answer (Stage 2/3 failed after extraction)
|
|
118
|
+
// 7673d772: agent gave empty answer (Stage 2/3 failed after extraction)
|
|
119
|
+
// 3f57289b: Stage 3 grabbed wrong number from prose
|
|
120
|
+
// 50ec8903: Stage 3 grabbed markdown fragment instead of correct answer
|
|
121
|
+
// 5a0c1adf: Stage 3 grabbed "Claus Peter" instead of correct "Claus"
|
|
122
|
+
//
|
|
123
|
+
// Each test constructs a synthetic response that mimics the failure mode
|
|
124
|
+
// and asserts the correct Stage 1 extraction behavior.
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// AR-1: a1e91b78 — agent used to surrender with "FINAL_ANSWER: unknown"
|
|
127
|
+
// In iter 52, system prompt said to output "FINAL_ANSWER: unknown" if unsure.
|
|
128
|
+
// With that instruction removed, the agent reasons further and finds the answer.
|
|
129
|
+
// This test verifies: if the agent DOES output a correct FINAL_ANSWER, we extract it.
|
|
130
|
+
{
|
|
131
|
+
label: 'AR-1 (a1e91b78): Stage1 extracts correct answer — not "unknown"',
|
|
132
|
+
input: 'I found the video. The maximum number of bird species seen together is 3.\nFINAL_ANSWER: 3',
|
|
133
|
+
expected: '3',
|
|
134
|
+
},
|
|
135
|
+
// AR-2: 935e2cff — agent had the answer but Stage 2 produced empty string
|
|
136
|
+
// In iter 53a, agent should output FINAL_ANSWER: Research correctly.
|
|
137
|
+
{
|
|
138
|
+
label: 'AR-2 (935e2cff): Stage1 extracts "Research" correctly',
|
|
139
|
+
input: 'After reviewing the policy document, R stands for Research.\nFINAL_ANSWER: Research',
|
|
140
|
+
expected: 'Research',
|
|
141
|
+
},
|
|
142
|
+
// AR-3: 305ac316 — agent had "Wojciech" but extraction failed
|
|
143
|
+
{
|
|
144
|
+
label: 'AR-3 (305ac316): Stage1 extracts Polish name correctly',
|
|
145
|
+
input: 'The actor played Wojciech in the movie.\nFINAL_ANSWER: Wojciech',
|
|
146
|
+
expected: 'Wojciech',
|
|
147
|
+
},
|
|
148
|
+
// AR-4: 7673d772 — agent had "inference" but extraction failed
|
|
149
|
+
{
|
|
150
|
+
label: 'AR-4 (7673d772): Stage1 extracts legal term correctly',
|
|
151
|
+
input: 'The fifth section of the federal rule uses the word "inference".\nFINAL_ANSWER: inference',
|
|
152
|
+
expected: 'inference',
|
|
153
|
+
},
|
|
154
|
+
// AR-5: 3f57289b — Stage 3 grabbed wrong number (525 instead of 519)
|
|
155
|
+
// The regression was caused by Stage 3 picking a wrong number from prose.
|
|
156
|
+
// With Stage 3 removed, Stage 1 is the only source — agent must use the tag.
|
|
157
|
+
{
|
|
158
|
+
label: 'AR-5 (3f57289b): Stage1 extracts correct at-bat count',
|
|
159
|
+
input: 'Reggie Jackson had 525 at-bats but the player with most walks had 519.\nFINAL_ANSWER: 519',
|
|
160
|
+
expected: '519',
|
|
161
|
+
},
|
|
162
|
+
// AR-6: 50ec8903 — Stage 3 grabbed "- Orange-Green edge →" (markdown fragment)
|
|
163
|
+
// With Stage 3 removed, only FINAL_ANSWER: tag is extracted.
|
|
164
|
+
{
|
|
165
|
+
label: 'AR-6 (50ec8903): Stage1 extracts Rubik cube colors correctly',
|
|
166
|
+
input: 'After solving the cube configuration, the answer is green, white.\nFINAL_ANSWER: green, white',
|
|
167
|
+
expected: 'green, white',
|
|
168
|
+
},
|
|
169
|
+
// AR-7: 5a0c1adf — Stage 3 grabbed "Claus Peter" (too many words)
|
|
170
|
+
// With Stage 3 removed, only FINAL_ANSWER: tag is extracted.
|
|
171
|
+
{
|
|
172
|
+
label: 'AR-7 (5a0c1adf): Stage1 extracts first name only, not "Claus Peter"',
|
|
173
|
+
input: 'The conductor\'s full name is Claus Peter Flor, but his first name is Claus.\nFINAL_ANSWER: Claus',
|
|
174
|
+
expected: 'Claus',
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Runner
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
async function main() {
|
|
181
|
+
// Import at runtime to work with both tsx and compiled JS.
|
|
182
|
+
const mod = await import('./gaia-agent.js');
|
|
183
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
184
|
+
const m = mod;
|
|
185
|
+
_extractFinalAnswer = m._extractFinalAnswerForTest;
|
|
186
|
+
_buildUserMessage = m._buildUserMessageForTest;
|
|
187
|
+
if (typeof _extractFinalAnswer !== 'function' || typeof _buildUserMessage !== 'function') {
|
|
188
|
+
console.error('ERROR: _extractFinalAnswerForTest / _buildUserMessageForTest not exported from gaia-agent.' +
|
|
189
|
+
'\nAdd `export { extractFinalAnswer as _extractFinalAnswerForTest, ' +
|
|
190
|
+
'buildUserMessage as _buildUserMessageForTest }` to gaia-agent.ts.');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
let failures = 0;
|
|
194
|
+
const PASS = '\x1b[32mPASS\x1b[0m';
|
|
195
|
+
const FAIL = '\x1b[31mFAIL\x1b[0m';
|
|
196
|
+
console.log('\n=== gaia-extract smoke (iter 53a T2 narrowed) ===\n');
|
|
197
|
+
console.log('Suite: 12 original + 7 anti-regression = 19 cases\n');
|
|
198
|
+
for (const tc of CASES) {
|
|
199
|
+
let actual;
|
|
200
|
+
if (tc.isUserMsg) {
|
|
201
|
+
actual = _buildUserMessage(tc.input);
|
|
202
|
+
// For user message tests, check startsWith instead of exact equality.
|
|
203
|
+
const pass = tc.expected === null
|
|
204
|
+
? actual === null
|
|
205
|
+
: actual !== null && (tc.expected === actual || actual.startsWith(tc.expected));
|
|
206
|
+
if (pass) {
|
|
207
|
+
console.log(` ${PASS} ${tc.label}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
console.log(` ${FAIL} ${tc.label}`);
|
|
211
|
+
console.log(` expected starts-with: ${JSON.stringify(tc.expected)}`);
|
|
212
|
+
console.log(` actual: ${JSON.stringify((actual ?? '').slice(0, 80))}`);
|
|
213
|
+
failures++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
actual = _extractFinalAnswer(makeResp(tc.input));
|
|
218
|
+
const pass = tc.expected === null
|
|
219
|
+
? actual === null
|
|
220
|
+
: actual !== null && actual === tc.expected;
|
|
221
|
+
if (pass) {
|
|
222
|
+
console.log(` ${PASS} ${tc.label}`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
console.log(` ${FAIL} ${tc.label}`);
|
|
226
|
+
console.log(` expected: ${JSON.stringify(tc.expected)}`);
|
|
227
|
+
console.log(` actual: ${JSON.stringify(actual)}`);
|
|
228
|
+
failures++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const total = CASES.length;
|
|
233
|
+
const passed = total - failures;
|
|
234
|
+
console.log(`\n=== ${failures === 0 ? 'ALL PASSED' : `${failures} FAILED`} (${passed}/${total} cases) ===\n`);
|
|
235
|
+
if (failures > 0)
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
main().catch((err) => {
|
|
239
|
+
console.error('Smoke crashed:', err);
|
|
240
|
+
process.exit(2);
|
|
241
|
+
});
|
|
242
|
+
//# sourceMappingURL=gaia-extract.smoke.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA Hardness Predictor — Feature Extraction (ADR-136 Track Q)
|
|
3
|
+
*
|
|
4
|
+
* Extracts a 17-dimensional feature vector from a GaiaQuestion for use
|
|
5
|
+
* in the linear hardness classifier. Deliberately avoids external
|
|
6
|
+
* dependencies (no spacy, no heavy NLP) — all extraction is regex-based
|
|
7
|
+
* with O(1) per question.
|
|
8
|
+
*
|
|
9
|
+
* Feature vector layout (17 dims):
|
|
10
|
+
* [0] question length in characters (normalised / 500)
|
|
11
|
+
* [1] question length in words (normalised / 100)
|
|
12
|
+
* [2] sentence count (normalised / 5)
|
|
13
|
+
* [3] question word: "what" (0/1)
|
|
14
|
+
* [4] question word: "how" (0/1)
|
|
15
|
+
* [5] question word: "who/when/where" (0/1)
|
|
16
|
+
* [6] question word: "calculate/compute/how many/what percentage" (0/1)
|
|
17
|
+
* [7] has numeric token (0/1)
|
|
18
|
+
* [8] has year token (4-digit) (0/1)
|
|
19
|
+
* [9] has comparison keyword (0/1)
|
|
20
|
+
* [10] estimated named-entity count (normalised / 5)
|
|
21
|
+
* [11] digit-token count (normalised / 5)
|
|
22
|
+
* [12] multi-hop signal (0/1) — bridge/relative clause pattern
|
|
23
|
+
* [13] requires math (0/1) — "how many/much/percentage/calculate/compute"
|
|
24
|
+
* [14] temporal chain (0/1) — "before/after X happened / since / until"
|
|
25
|
+
* [15] tool implication count (normalised / 4) — PDF/image/video/URL markers
|
|
26
|
+
* [16] file attachment present (0/1)
|
|
27
|
+
*
|
|
28
|
+
* All continuous values are min-max normalised to [0, 1] using fixed
|
|
29
|
+
* divisors chosen so typical GAIA questions fall in [0.1, 0.9].
|
|
30
|
+
*
|
|
31
|
+
* Refs: ADR-136, #2156
|
|
32
|
+
*/
|
|
33
|
+
import type { GaiaQuestion } from '../gaia-loader.js';
|
|
34
|
+
export interface FeatureVector {
|
|
35
|
+
/** Raw 17-element array in [0, 1]. */
|
|
36
|
+
values: number[];
|
|
37
|
+
/** Human-readable labels matching values index. */
|
|
38
|
+
labels: string[];
|
|
39
|
+
}
|
|
40
|
+
export declare const FEATURE_LABELS: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Extract the 17-dimensional feature vector from a GaiaQuestion.
|
|
43
|
+
* All features are in [0, 1]. Never throws.
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractFeatures(q: GaiaQuestion): FeatureVector;
|
|
46
|
+
//# sourceMappingURL=features.d.ts.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA Hardness Predictor — Feature Extraction (ADR-136 Track Q)
|
|
3
|
+
*
|
|
4
|
+
* Extracts a 17-dimensional feature vector from a GaiaQuestion for use
|
|
5
|
+
* in the linear hardness classifier. Deliberately avoids external
|
|
6
|
+
* dependencies (no spacy, no heavy NLP) — all extraction is regex-based
|
|
7
|
+
* with O(1) per question.
|
|
8
|
+
*
|
|
9
|
+
* Feature vector layout (17 dims):
|
|
10
|
+
* [0] question length in characters (normalised / 500)
|
|
11
|
+
* [1] question length in words (normalised / 100)
|
|
12
|
+
* [2] sentence count (normalised / 5)
|
|
13
|
+
* [3] question word: "what" (0/1)
|
|
14
|
+
* [4] question word: "how" (0/1)
|
|
15
|
+
* [5] question word: "who/when/where" (0/1)
|
|
16
|
+
* [6] question word: "calculate/compute/how many/what percentage" (0/1)
|
|
17
|
+
* [7] has numeric token (0/1)
|
|
18
|
+
* [8] has year token (4-digit) (0/1)
|
|
19
|
+
* [9] has comparison keyword (0/1)
|
|
20
|
+
* [10] estimated named-entity count (normalised / 5)
|
|
21
|
+
* [11] digit-token count (normalised / 5)
|
|
22
|
+
* [12] multi-hop signal (0/1) — bridge/relative clause pattern
|
|
23
|
+
* [13] requires math (0/1) — "how many/much/percentage/calculate/compute"
|
|
24
|
+
* [14] temporal chain (0/1) — "before/after X happened / since / until"
|
|
25
|
+
* [15] tool implication count (normalised / 4) — PDF/image/video/URL markers
|
|
26
|
+
* [16] file attachment present (0/1)
|
|
27
|
+
*
|
|
28
|
+
* All continuous values are min-max normalised to [0, 1] using fixed
|
|
29
|
+
* divisors chosen so typical GAIA questions fall in [0.1, 0.9].
|
|
30
|
+
*
|
|
31
|
+
* Refs: ADR-136, #2156
|
|
32
|
+
*/
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Feature labels (in order, matches values array)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
export const FEATURE_LABELS = [
|
|
37
|
+
'len_chars_norm',
|
|
38
|
+
'len_words_norm',
|
|
39
|
+
'sentence_count_norm',
|
|
40
|
+
'qword_what',
|
|
41
|
+
'qword_how',
|
|
42
|
+
'qword_who_when_where',
|
|
43
|
+
'qword_calc_compute',
|
|
44
|
+
'has_numeric',
|
|
45
|
+
'has_year',
|
|
46
|
+
'has_comparison',
|
|
47
|
+
'entity_count_norm',
|
|
48
|
+
'digit_token_norm',
|
|
49
|
+
'multi_hop_signal',
|
|
50
|
+
'requires_math',
|
|
51
|
+
'temporal_chain',
|
|
52
|
+
'tool_implication_norm',
|
|
53
|
+
'has_file_attachment',
|
|
54
|
+
];
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Regex catalogue (compiled once at module load)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Named entity proxy: capitalised word sequences (not at sentence start)
|
|
59
|
+
// e.g. "Barack Obama", "New York City", "United States of America"
|
|
60
|
+
const RE_ENTITY = /(?<!^|\.\s{1,3})(?:[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,4})/g;
|
|
61
|
+
const RE_YEAR = /\b(1[0-9]{3}|20[0-9]{2})\b/g;
|
|
62
|
+
const RE_DIGIT_TOKEN = /\b\d[\d,._]*/g;
|
|
63
|
+
const RE_NUMERIC = /\b\d/;
|
|
64
|
+
const RE_COMPARISON = /\b(more|fewer|greater|less|compare|versus|vs\.?|differ|increase|decrease|ratio|proportion)\b/i;
|
|
65
|
+
const RE_MULTI_HOP = /\b(the\s+\w+\s+that\s+|which\s+was\s+|who\s+was\s+|whose\s+|of\s+the\s+\w+\s+that\s+)/i;
|
|
66
|
+
// Requires math: stems without trailing \b to handle "calculate", "multiply", "division", etc.
|
|
67
|
+
const RE_MATH = /\b(how\s+many|how\s+much|what\s+percentage|what\s+fraction|what\s+proportion|calculat|comput|multipli|divis|subtract|sum\s+of|total\s+of|average\s+of|product\s+of|express\s+as\s+a\s+decimal)/i;
|
|
68
|
+
const RE_TEMPORAL = /\b(before|after|since|until|during|between\s+\d|in\s+the\s+year|by\s+the\s+time|at\s+the\s+time)\b/i;
|
|
69
|
+
// Tool implication markers
|
|
70
|
+
const RE_TOOL_FILE = /\b(pdf|\.docx|\.xlsx|\.csv|file|attachment|document|spreadsheet)\b/i;
|
|
71
|
+
const RE_TOOL_IMAGE = /\b(image|photo|picture|screenshot|figure|diagram|chart)\b/i;
|
|
72
|
+
const RE_TOOL_VIDEO = /\b(video|youtube|clip|footage)\b/i;
|
|
73
|
+
const RE_TOOL_URL = /https?:\/\//i;
|
|
74
|
+
// Question-word detection (first word of question, or full-text for calc/compute)
|
|
75
|
+
const RE_QWORD_WHAT = /^(what|which)\b/i;
|
|
76
|
+
const RE_QWORD_HOW = /^how\b/i;
|
|
77
|
+
const RE_QWORD_WHO_WHEN_WHERE = /^(who|when|where|why|whom)\b/i;
|
|
78
|
+
// calc/compute: fires if question contains calculate/compute anywhere (stems, no trailing \b)
|
|
79
|
+
const RE_QWORD_CALC = /\b(calculat|comput|how\s+many|what\s+percentage|how\s+much)/i;
|
|
80
|
+
// Sentence boundary (rough — period/exclamation/question-mark followed by space+capital)
|
|
81
|
+
const RE_SENTENCE = /[.!?]\s+[A-Z]/g;
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Helper: clamp value to [0, 1]
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
function clamp(v, max) {
|
|
86
|
+
return Math.min(v / max, 1.0);
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Public API
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* Extract the 17-dimensional feature vector from a GaiaQuestion.
|
|
93
|
+
* All features are in [0, 1]. Never throws.
|
|
94
|
+
*/
|
|
95
|
+
export function extractFeatures(q) {
|
|
96
|
+
const text = q.question ?? '';
|
|
97
|
+
// ── Syntactic scalars ──────────────────────────────────────────────────
|
|
98
|
+
const lenChars = text.length;
|
|
99
|
+
const words = text.trim().split(/\s+/).filter(Boolean);
|
|
100
|
+
const lenWords = words.length;
|
|
101
|
+
const sentenceMatches = text.match(RE_SENTENCE);
|
|
102
|
+
const sentenceCount = 1 + (sentenceMatches ? sentenceMatches.length : 0);
|
|
103
|
+
// ── Question-word one-hot ─────────────────────────────────────────────
|
|
104
|
+
const firstWord = text.trim();
|
|
105
|
+
const qwWhat = RE_QWORD_WHAT.test(firstWord) ? 1 : 0;
|
|
106
|
+
const qwHow = RE_QWORD_HOW.test(firstWord) ? 1 : 0;
|
|
107
|
+
const qwWhoWhenWhere = RE_QWORD_WHO_WHEN_WHERE.test(firstWord) ? 1 : 0;
|
|
108
|
+
const qwCalcCompute = RE_QWORD_CALC.test(text) ? 1 : 0;
|
|
109
|
+
// ── Lexical booleans ──────────────────────────────────────────────────
|
|
110
|
+
const hasNumeric = RE_NUMERIC.test(text) ? 1 : 0;
|
|
111
|
+
// Count year tokens
|
|
112
|
+
const yearMatches = text.match(RE_YEAR);
|
|
113
|
+
const hasYear = yearMatches && yearMatches.length > 0 ? 1 : 0;
|
|
114
|
+
const hasComparison = RE_COMPARISON.test(text) ? 1 : 0;
|
|
115
|
+
// ── Named entity proxy (capitalised word sequences) ───────────────────
|
|
116
|
+
// Reset lastIndex for global regex
|
|
117
|
+
RE_ENTITY.lastIndex = 0;
|
|
118
|
+
let entityCount = 0;
|
|
119
|
+
// eslint-disable-next-line no-constant-condition
|
|
120
|
+
while (true) {
|
|
121
|
+
const m = RE_ENTITY.exec(text);
|
|
122
|
+
if (!m)
|
|
123
|
+
break;
|
|
124
|
+
entityCount++;
|
|
125
|
+
}
|
|
126
|
+
// ── Digit token count ─────────────────────────────────────────────────
|
|
127
|
+
RE_DIGIT_TOKEN.lastIndex = 0;
|
|
128
|
+
const digitTokenMatches = text.match(RE_DIGIT_TOKEN);
|
|
129
|
+
const digitTokenCount = digitTokenMatches ? digitTokenMatches.length : 0;
|
|
130
|
+
// ── Multi-hop signal ──────────────────────────────────────────────────
|
|
131
|
+
const multiHopSignal = RE_MULTI_HOP.test(text) ? 1 : 0;
|
|
132
|
+
// ── Math requirement ──────────────────────────────────────────────────
|
|
133
|
+
const requiresMath = RE_MATH.test(text) ? 1 : 0;
|
|
134
|
+
// ── Temporal chain ────────────────────────────────────────────────────
|
|
135
|
+
const temporalChain = RE_TEMPORAL.test(text) ? 1 : 0;
|
|
136
|
+
// ── Tool implication (PDF/image/video/URL → file_read/web_browse/image_describe) ──
|
|
137
|
+
let toolImplication = 0;
|
|
138
|
+
if (RE_TOOL_FILE.test(text) || q.file_name)
|
|
139
|
+
toolImplication++;
|
|
140
|
+
if (RE_TOOL_IMAGE.test(text))
|
|
141
|
+
toolImplication++;
|
|
142
|
+
if (RE_TOOL_VIDEO.test(text))
|
|
143
|
+
toolImplication++;
|
|
144
|
+
if (RE_TOOL_URL.test(text))
|
|
145
|
+
toolImplication++;
|
|
146
|
+
// ── File attachment ───────────────────────────────────────────────────
|
|
147
|
+
const hasFileAttachment = q.file_name && q.file_name.trim() ? 1 : 0;
|
|
148
|
+
// ── Assemble normalised vector ────────────────────────────────────────
|
|
149
|
+
const values = [
|
|
150
|
+
clamp(lenChars, 500), // [0]
|
|
151
|
+
clamp(lenWords, 100), // [1]
|
|
152
|
+
clamp(sentenceCount, 5), // [2]
|
|
153
|
+
qwWhat, // [3]
|
|
154
|
+
qwHow, // [4]
|
|
155
|
+
qwWhoWhenWhere, // [5]
|
|
156
|
+
qwCalcCompute, // [6]
|
|
157
|
+
hasNumeric, // [7]
|
|
158
|
+
hasYear, // [8]
|
|
159
|
+
hasComparison, // [9]
|
|
160
|
+
clamp(entityCount, 5), // [10]
|
|
161
|
+
clamp(digitTokenCount, 5), // [11]
|
|
162
|
+
multiHopSignal, // [12]
|
|
163
|
+
requiresMath, // [13]
|
|
164
|
+
temporalChain, // [14]
|
|
165
|
+
clamp(toolImplication, 4), // [15]
|
|
166
|
+
hasFileAttachment ? 1 : 0, // [16]
|
|
167
|
+
];
|
|
168
|
+
return { values, labels: FEATURE_LABELS };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=features.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA Hardness Predictor — Linear Classifier (ADR-136 Track Q)
|
|
3
|
+
*
|
|
4
|
+
* Classifies GAIA questions into easy / medium / hard using a
|
|
5
|
+
* hand-rolled logistic regression (no external ML dependencies).
|
|
6
|
+
*
|
|
7
|
+
* Training:
|
|
8
|
+
* `predictor.train(labeledData)` — fits weights via gradient descent
|
|
9
|
+
* on cross-entropy loss using the 17-dim feature vectors.
|
|
10
|
+
*
|
|
11
|
+
* Inference:
|
|
12
|
+
* `predictor.predict(question)` — returns difficulty class + confidence
|
|
13
|
+
* + a ComputeBudget that drives model/turns/voting choices in gaia-bench.
|
|
14
|
+
*
|
|
15
|
+
* Cold-start:
|
|
16
|
+
* When untrained (weights = null), classifies everything as "medium".
|
|
17
|
+
* This is the correct safe default: no wasted Haiku-on-hard, no missed
|
|
18
|
+
* Sonnet-on-easy.
|
|
19
|
+
*
|
|
20
|
+
* Compute budget policy (from ADR-136 Track Q research):
|
|
21
|
+
* easy → Haiku, max 4 turns, 1 attempt
|
|
22
|
+
* medium → Sonnet, max 8 turns, 1 attempt
|
|
23
|
+
* hard → Sonnet, max 12 turns, 3-vote (Track A)
|
|
24
|
+
*
|
|
25
|
+
* Conservative threshold:
|
|
26
|
+
* If in doubt, classify UP (medium→hard preferred over medium→easy).
|
|
27
|
+
* `conservativeMode: true` (default) shifts the easy/medium boundary
|
|
28
|
+
* so fewer questions fall into "easy".
|
|
29
|
+
*
|
|
30
|
+
* Refs: ADR-136, ADR-135, #2156
|
|
31
|
+
*/
|
|
32
|
+
import type { GaiaQuestion } from '../gaia-loader.js';
|
|
33
|
+
import { type FeatureVector } from './features.js';
|
|
34
|
+
export type DifficultyClass = 'easy' | 'medium' | 'hard';
|
|
35
|
+
export interface ComputeBudget {
|
|
36
|
+
model: 'haiku' | 'sonnet';
|
|
37
|
+
maxTurns: number;
|
|
38
|
+
votingAttempts: number;
|
|
39
|
+
}
|
|
40
|
+
export interface PredictionResult {
|
|
41
|
+
difficulty: DifficultyClass;
|
|
42
|
+
confidence: number;
|
|
43
|
+
budget: ComputeBudget;
|
|
44
|
+
features: FeatureVector;
|
|
45
|
+
}
|
|
46
|
+
export interface LabeledExample {
|
|
47
|
+
question: GaiaQuestion;
|
|
48
|
+
wasCorrect: boolean;
|
|
49
|
+
/** Number of turns the agent used (optional; used to refine label). */
|
|
50
|
+
turns?: number;
|
|
51
|
+
}
|
|
52
|
+
export declare const COMPUTE_BUDGETS: Record<DifficultyClass, ComputeBudget>;
|
|
53
|
+
export declare class HardnessPredictor {
|
|
54
|
+
/**
|
|
55
|
+
* Weight matrix: weights[classIdx][featureIdx].
|
|
56
|
+
* null = untrained (cold-start: return medium for everything).
|
|
57
|
+
*/
|
|
58
|
+
private weights;
|
|
59
|
+
/** Bias terms per class. */
|
|
60
|
+
private biases;
|
|
61
|
+
/** Whether conservative mode is active (default: true). */
|
|
62
|
+
private readonly conservativeMode;
|
|
63
|
+
constructor(options?: {
|
|
64
|
+
conservativeMode?: boolean;
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Returns true when the predictor has been trained and is ready
|
|
68
|
+
* to make non-trivial predictions.
|
|
69
|
+
*/
|
|
70
|
+
get isTrained(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Train the linear classifier using labelled examples from prior runs.
|
|
73
|
+
*
|
|
74
|
+
* Labelling strategy (weak supervision):
|
|
75
|
+
* - All correct + turns ≤ median turns → easy
|
|
76
|
+
* - All correct + turns > median turns → medium
|
|
77
|
+
* - Incorrect → hard
|
|
78
|
+
*
|
|
79
|
+
* With < 10 examples, refuses to train (cold-start is safer).
|
|
80
|
+
* With 10-50 examples, trains but sets `conservativeMode`-threshold high.
|
|
81
|
+
*/
|
|
82
|
+
train(labeledData: LabeledExample[]): void;
|
|
83
|
+
/**
|
|
84
|
+
* Predict the hardness class of a single GAIA question.
|
|
85
|
+
*
|
|
86
|
+
* Cold-start (untrained): returns medium with confidence=0.5.
|
|
87
|
+
*/
|
|
88
|
+
predict(question: GaiaQuestion): PredictionResult;
|
|
89
|
+
/**
|
|
90
|
+
* Export weights as a plain JSON-serialisable object.
|
|
91
|
+
* Returns null if untrained.
|
|
92
|
+
*/
|
|
93
|
+
export(): {
|
|
94
|
+
weights: number[][];
|
|
95
|
+
biases: number[];
|
|
96
|
+
} | null;
|
|
97
|
+
/**
|
|
98
|
+
* Import previously exported weights.
|
|
99
|
+
*/
|
|
100
|
+
import(state: {
|
|
101
|
+
weights: number[][];
|
|
102
|
+
biases: number[];
|
|
103
|
+
}): void;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=predictor.d.ts.map
|