@mastra/longmemeval 0.0.0-error-handler-fix-20251020202607 → 0.0.0-execa-dynamic-import-20260304221256
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/CHANGELOG.md +847 -6
- package/LICENSE.md +15 -0
- package/docs/INVESTIGATION_WORKFLOW.md +314 -0
- package/package.json +34 -26
- package/scripts/download.ts +22 -1
- package/scripts/generate-wm-templates.ts +2 -1
- package/src/cli.ts +967 -155
- package/src/commands/__tests__/prepare.test.ts +11 -6
- package/src/commands/__tests__/run.test.ts +10 -8
- package/src/commands/clean.ts +147 -0
- package/src/commands/deterministic-ids.ts +126 -0
- package/src/commands/find-prohibited.ts +299 -0
- package/src/commands/investigate.ts +2437 -0
- package/src/commands/list-partial.ts +137 -0
- package/src/commands/obscure-thread-ids.ts +237 -0
- package/src/commands/partial-results.ts +198 -0
- package/src/commands/precompute-embeddings.ts +302 -0
- package/src/commands/prepare.ts +732 -105
- package/src/commands/reconcile.ts +383 -0
- package/src/commands/run.ts +1240 -121
- package/src/commands/sessions.ts +126 -0
- package/src/commands/sync.ts +152 -0
- package/src/commands/test-cleaned.ts +53 -0
- package/src/commands/test-incremental.ts +120 -0
- package/src/commands/test-messages.ts +95 -0
- package/src/commands/test-prohibited.ts +244 -0
- package/src/commands/test-thread.ts +136 -0
- package/src/commands/tokens.ts +625 -0
- package/src/config.ts +1253 -72
- package/src/data/types.ts +48 -6
- package/src/embeddings/cached-openai-embedding-model.ts +0 -1
- package/src/evaluation/longmemeval-metric.ts +186 -103
- package/src/processors/content-sanitizer.ts +153 -0
- package/src/processors/date-injector.ts +111 -0
- package/src/processors/index.ts +3 -0
- package/src/processors/observation-semantic-filter.ts +1142 -0
- package/src/retry-model.ts +5 -3
- package/src/storage/__tests__/benchmark-store.test.ts +18 -35
- package/src/storage/benchmark-store.ts +140 -153
- package/src/storage/benchmark-vector.ts +1 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/persistable-inmemory.ts +133 -0
- package/src/test-utils/mock-model.ts +35 -0
package/src/cli.ts
CHANGED
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import 'dotenv/config';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
5
|
import chalk from 'chalk';
|
|
5
|
-
import { readFile, readdir } from 'fs/promises';
|
|
6
|
+
import { readFile, readdir, writeFile } from 'fs/promises';
|
|
6
7
|
import { join } from 'path';
|
|
7
8
|
import { existsSync, statSync } from 'fs';
|
|
8
9
|
import { execSync } from 'child_process';
|
|
9
10
|
|
|
10
11
|
import { DatasetLoader } from './data/loader';
|
|
11
|
-
import type { EvaluationResult, BenchmarkMetrics, QuestionType } from './data/types';
|
|
12
|
+
import type { EvaluationResult, BenchmarkMetrics, QuestionType, DatasetType, MemoryConfigType } from './data/types';
|
|
12
13
|
import { PrepareCommand } from './commands/prepare';
|
|
13
14
|
import { RunCommand } from './commands/run';
|
|
15
|
+
import { SyncCommand } from './commands/sync';
|
|
16
|
+
import { CleanCommand } from './commands/clean';
|
|
17
|
+
import { ObscureThreadIdsCommand } from './commands/obscure-thread-ids';
|
|
18
|
+
import { SessionsCommand } from './commands/sessions';
|
|
19
|
+
import { DeterministicIdsCommand } from './commands/deterministic-ids';
|
|
20
|
+
import { ListPartialCommand } from './commands/list-partial';
|
|
21
|
+
import { TokensCommand } from './commands/tokens';
|
|
22
|
+
import { PrecomputeEmbeddingsCommand } from './commands/precompute-embeddings';
|
|
23
|
+
import { FindProhibitedCommand } from './commands/find-prohibited';
|
|
24
|
+
import { PartialResultsCommand } from './commands/partial-results';
|
|
25
|
+
import {
|
|
26
|
+
getRunVariant,
|
|
27
|
+
resolveConfigAlias,
|
|
28
|
+
getConfigAliases,
|
|
29
|
+
getMemoryConfig,
|
|
30
|
+
RUN_VARIANTS,
|
|
31
|
+
CONFIG_ALIASES,
|
|
32
|
+
} from './config';
|
|
14
33
|
|
|
15
34
|
const program = new Command();
|
|
16
35
|
|
|
@@ -86,52 +105,326 @@ function calculateMetrics(results: EvaluationResult[]): BenchmarkMetrics {
|
|
|
86
105
|
return metrics;
|
|
87
106
|
}
|
|
88
107
|
|
|
108
|
+
// Helper to load preparation token usage from om-debug.jsonl files
|
|
109
|
+
interface PreparationTokenUsage {
|
|
110
|
+
observerInputTokens: number;
|
|
111
|
+
observerOutputTokens: number;
|
|
112
|
+
reflectorInputTokens: number;
|
|
113
|
+
reflectorOutputTokens: number;
|
|
114
|
+
totalInputTokens: number;
|
|
115
|
+
totalOutputTokens: number;
|
|
116
|
+
questionCount: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Cache for per-question preparation token usage
|
|
120
|
+
interface QuestionTokenCache {
|
|
121
|
+
[questionId: string]: {
|
|
122
|
+
mtime: number; // mtime of om-debug.jsonl when cached
|
|
123
|
+
usage: {
|
|
124
|
+
observerInputTokens: number;
|
|
125
|
+
observerOutputTokens: number;
|
|
126
|
+
reflectorInputTokens: number;
|
|
127
|
+
reflectorOutputTokens: number;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface PrepTokenCache {
|
|
133
|
+
[memoryConfig: string]: QuestionTokenCache;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const PREP_TOKEN_CACHE_FILE = '.prep-token-cache.json';
|
|
137
|
+
|
|
138
|
+
async function loadPrepTokenCache(): Promise<PrepTokenCache> {
|
|
139
|
+
try {
|
|
140
|
+
const content = await readFile(PREP_TOKEN_CACHE_FILE, 'utf-8');
|
|
141
|
+
return JSON.parse(content);
|
|
142
|
+
} catch {
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function savePrepTokenCache(cache: PrepTokenCache): Promise<void> {
|
|
148
|
+
await writeFile(PREP_TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function parseDebugFileForTokens(debugFile: string): Promise<{
|
|
152
|
+
observerInputTokens: number;
|
|
153
|
+
observerOutputTokens: number;
|
|
154
|
+
reflectorInputTokens: number;
|
|
155
|
+
reflectorOutputTokens: number;
|
|
156
|
+
} | null> {
|
|
157
|
+
try {
|
|
158
|
+
const content = await readFile(debugFile, 'utf-8');
|
|
159
|
+
|
|
160
|
+
const result = {
|
|
161
|
+
observerInputTokens: 0,
|
|
162
|
+
observerOutputTokens: 0,
|
|
163
|
+
reflectorInputTokens: 0,
|
|
164
|
+
reflectorOutputTokens: 0,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Parse pretty-printed JSON objects
|
|
168
|
+
const events: any[] = [];
|
|
169
|
+
let currentJson = '';
|
|
170
|
+
let braceCount = 0;
|
|
171
|
+
|
|
172
|
+
for (const line of content.split('\n')) {
|
|
173
|
+
const trimmed = line.trim();
|
|
174
|
+
|
|
175
|
+
if (braceCount === 0 && trimmed.startsWith('{')) {
|
|
176
|
+
currentJson = line;
|
|
177
|
+
braceCount = (trimmed.match(/{/g) || []).length - (trimmed.match(/}/g) || []).length;
|
|
178
|
+
} else if (braceCount > 0) {
|
|
179
|
+
currentJson += '\n' + line;
|
|
180
|
+
braceCount += (trimmed.match(/{/g) || []).length;
|
|
181
|
+
braceCount -= (trimmed.match(/}/g) || []).length;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (braceCount === 0 && currentJson) {
|
|
185
|
+
try {
|
|
186
|
+
events.push(JSON.parse(currentJson));
|
|
187
|
+
} catch {
|
|
188
|
+
// Skip malformed JSON
|
|
189
|
+
}
|
|
190
|
+
currentJson = '';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const event of events) {
|
|
195
|
+
if (event.usage && event.usage.inputTokens) {
|
|
196
|
+
if (event.type === 'observation_complete') {
|
|
197
|
+
result.observerInputTokens += event.usage.inputTokens || 0;
|
|
198
|
+
result.observerOutputTokens += event.usage.outputTokens || 0;
|
|
199
|
+
} else if (event.type === 'reflection_complete') {
|
|
200
|
+
result.reflectorInputTokens += event.usage.inputTokens || 0;
|
|
201
|
+
result.reflectorOutputTokens += event.usage.outputTokens || 0;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function loadPreparationTokenUsage(
|
|
213
|
+
preparedDataDir: string,
|
|
214
|
+
memoryConfig: string,
|
|
215
|
+
questionIds?: string[],
|
|
216
|
+
): Promise<PreparationTokenUsage | null> {
|
|
217
|
+
const usage: PreparationTokenUsage = {
|
|
218
|
+
observerInputTokens: 0,
|
|
219
|
+
observerOutputTokens: 0,
|
|
220
|
+
reflectorInputTokens: 0,
|
|
221
|
+
reflectorOutputTokens: 0,
|
|
222
|
+
totalInputTokens: 0,
|
|
223
|
+
totalOutputTokens: 0,
|
|
224
|
+
questionCount: 0,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const configDir = join(preparedDataDir, 'longmemeval_s', memoryConfig);
|
|
229
|
+
if (!existsSync(configDir)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Load cache
|
|
234
|
+
const cache = await loadPrepTokenCache();
|
|
235
|
+
if (!cache[memoryConfig]) {
|
|
236
|
+
cache[memoryConfig] = {};
|
|
237
|
+
}
|
|
238
|
+
const configCache = cache[memoryConfig];
|
|
239
|
+
let cacheUpdated = false;
|
|
240
|
+
|
|
241
|
+
const questionDirs = await readdir(configDir);
|
|
242
|
+
const dirsToProcess = questionIds ? questionDirs.filter(d => questionIds.includes(d)) : questionDirs;
|
|
243
|
+
|
|
244
|
+
for (const questionDir of dirsToProcess) {
|
|
245
|
+
const debugFile = join(configDir, questionDir, 'om-debug.jsonl');
|
|
246
|
+
if (!existsSync(debugFile)) continue;
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const fileStat = statSync(debugFile);
|
|
250
|
+
const fileMtime = fileStat.mtimeMs;
|
|
251
|
+
|
|
252
|
+
// Check if we have a valid cache entry
|
|
253
|
+
const cached = configCache[questionDir];
|
|
254
|
+
if (cached && cached.mtime === fileMtime) {
|
|
255
|
+
// Use cached values
|
|
256
|
+
usage.observerInputTokens += cached.usage.observerInputTokens;
|
|
257
|
+
usage.observerOutputTokens += cached.usage.observerOutputTokens;
|
|
258
|
+
usage.reflectorInputTokens += cached.usage.reflectorInputTokens;
|
|
259
|
+
usage.reflectorOutputTokens += cached.usage.reflectorOutputTokens;
|
|
260
|
+
usage.questionCount++;
|
|
261
|
+
} else {
|
|
262
|
+
// Parse the file and update cache
|
|
263
|
+
const parsed = await parseDebugFileForTokens(debugFile);
|
|
264
|
+
if (parsed) {
|
|
265
|
+
usage.observerInputTokens += parsed.observerInputTokens;
|
|
266
|
+
usage.observerOutputTokens += parsed.observerOutputTokens;
|
|
267
|
+
usage.reflectorInputTokens += parsed.reflectorInputTokens;
|
|
268
|
+
usage.reflectorOutputTokens += parsed.reflectorOutputTokens;
|
|
269
|
+
usage.questionCount++;
|
|
270
|
+
|
|
271
|
+
// Update cache
|
|
272
|
+
configCache[questionDir] = {
|
|
273
|
+
mtime: fileMtime,
|
|
274
|
+
usage: parsed,
|
|
275
|
+
};
|
|
276
|
+
cacheUpdated = true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
// Skip files that can't be read
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Save cache if updated
|
|
285
|
+
if (cacheUpdated) {
|
|
286
|
+
await savePrepTokenCache(cache);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
usage.totalInputTokens = usage.observerInputTokens + usage.reflectorInputTokens;
|
|
290
|
+
usage.totalOutputTokens = usage.observerOutputTokens + usage.reflectorOutputTokens;
|
|
291
|
+
|
|
292
|
+
return usage.questionCount > 0 ? usage : null;
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
89
298
|
program.name('longmemeval').description('LongMemEval benchmark for Mastra Memory').version('0.1.0');
|
|
90
299
|
|
|
300
|
+
// Helper to show available variants and configs
|
|
301
|
+
function showAvailableOptions() {
|
|
302
|
+
console.log(chalk.blue('\n📋 Available Run Variants:\n'));
|
|
303
|
+
for (const [name, variant] of Object.entries(RUN_VARIANTS)) {
|
|
304
|
+
const subsetInfo = variant.subset ? `${variant.subset} questions` : 'all questions';
|
|
305
|
+
console.log(
|
|
306
|
+
chalk.bold(` ${name.padEnd(10)}`),
|
|
307
|
+
chalk.gray(`- ${variant.description}`),
|
|
308
|
+
chalk.dim(`(${subsetInfo}, prepare: ${variant.prepareConcurrency}x, bench: ${variant.benchConcurrency}x)`),
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
console.log(chalk.blue('\n📋 Available Memory Configs:\n'));
|
|
313
|
+
const configNames = getConfigAliases();
|
|
314
|
+
// Build reverse map: full name -> short aliases
|
|
315
|
+
const shortAliases: Record<string, string[]> = {};
|
|
316
|
+
for (const [alias, fullName] of Object.entries(CONFIG_ALIASES)) {
|
|
317
|
+
if (alias !== fullName) {
|
|
318
|
+
if (!shortAliases[fullName]) shortAliases[fullName] = [];
|
|
319
|
+
shortAliases[fullName].push(alias);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const configName of configNames) {
|
|
323
|
+
const configAliases = shortAliases[configName];
|
|
324
|
+
if (configAliases && configAliases.length > 0) {
|
|
325
|
+
console.log(chalk.bold(` ${configName.padEnd(35)}`), chalk.gray(`(alias: ${configAliases.join(', ')})`));
|
|
326
|
+
} else {
|
|
327
|
+
console.log(chalk.bold(` ${configName}`));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log(chalk.gray('\n\nUsage:'));
|
|
332
|
+
console.log(chalk.gray(' pnpm run prepare <config> [-v quick|full]'));
|
|
333
|
+
console.log(chalk.gray(' pnpm run bench <config> [-v quick|full]'));
|
|
334
|
+
console.log(chalk.gray('\nExamples:'));
|
|
335
|
+
console.log(chalk.gray(' pnpm run prepare om # uses quick variant by default'));
|
|
336
|
+
console.log(chalk.gray(' pnpm run prepare om -v full # full benchmark'));
|
|
337
|
+
console.log(chalk.gray(' pnpm run bench om'));
|
|
338
|
+
console.log(chalk.gray(' pnpm run bench om-glm -v full'));
|
|
339
|
+
console.log(chalk.gray('\nAdditional flags:'));
|
|
340
|
+
console.log(chalk.gray(' -v, --variant Run variant (quick, full, rip) - default: quick'));
|
|
341
|
+
console.log(chalk.gray(' --offset <n> Skip first n questions'));
|
|
342
|
+
console.log(chalk.gray(' --question-id <id> Process specific question'));
|
|
343
|
+
console.log(chalk.gray(' -y, --yes Skip confirmation prompt'));
|
|
344
|
+
}
|
|
345
|
+
|
|
91
346
|
// Prepare command
|
|
92
347
|
program
|
|
93
|
-
.command('prepare')
|
|
348
|
+
.command('prepare [config]')
|
|
94
349
|
.description('Prepare LongMemEval data by processing through mock agents')
|
|
95
|
-
.option('-
|
|
96
|
-
.option(
|
|
97
|
-
'-c, --memory-config <config>',
|
|
98
|
-
'Memory configuration (last-k, semantic-recall, semantic-recall-reranked, working-memory, working-memory-tailored, combined, combined-tailored)',
|
|
99
|
-
'semantic-recall',
|
|
100
|
-
)
|
|
350
|
+
.option('-v, --variant <variant>', 'Run variant (quick, full, rip)', 'quick')
|
|
101
351
|
.option('-o, --output <dir>', 'Output directory for prepared data', './prepared-data')
|
|
102
|
-
.option('--subset <n>', '
|
|
103
|
-
.option('--
|
|
352
|
+
.option('--subset <n>', 'Override subset size', parseInt)
|
|
353
|
+
.option('--offset <n>', 'Skip first n questions', parseInt)
|
|
354
|
+
.option('--concurrency <n>', 'Override concurrency', parseInt)
|
|
104
355
|
.option('--question-id <id>', 'Prepare a specific question by ID')
|
|
105
356
|
.option('--resume-from-message-id <id>', 'Resume processing from a specific message ID')
|
|
106
357
|
.option('--session-limit <n>', 'Limit processing to n sessions after resume point', parseInt)
|
|
107
358
|
.option('--session-offset <n>', 'Start processing from the nth session (1-based)', parseInt)
|
|
108
|
-
.
|
|
359
|
+
.option('--from-failures [path]', 'Re-prepare failed questions (uses latest failures.json if no path given)')
|
|
360
|
+
.option('--older-than <duration>', 'Only re-prepare questions older than this duration (e.g., "1h", "30m", "2d")')
|
|
361
|
+
.option('--dry-run', 'Show what would be re-prepared without actually doing it (use with --from-failures)')
|
|
362
|
+
.option('--force-regenerate', 'Force regeneration by deleting existing prepared data first')
|
|
363
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
364
|
+
// Legacy options for backwards compatibility
|
|
365
|
+
.option('-d, --dataset <dataset>', 'Dataset to use (legacy)')
|
|
366
|
+
.option('-c, --memory-config <config>', 'Memory configuration (legacy)')
|
|
367
|
+
.action(async (config, options) => {
|
|
109
368
|
try {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
console.log(chalk.gray(`Subset: ${options.subset} questions`));
|
|
369
|
+
// If no config provided, show help
|
|
370
|
+
if (!config && !options.dataset) {
|
|
371
|
+
showAvailableOptions();
|
|
372
|
+
process.exit(0);
|
|
115
373
|
}
|
|
116
|
-
|
|
117
|
-
|
|
374
|
+
|
|
375
|
+
// Resolve variant and config (support legacy options)
|
|
376
|
+
let resolvedVariant: ReturnType<typeof getRunVariant>;
|
|
377
|
+
let resolvedConfig: string;
|
|
378
|
+
let dataset: string;
|
|
379
|
+
|
|
380
|
+
if (options.dataset && options.memoryConfig) {
|
|
381
|
+
// Legacy mode: use -d and -c flags
|
|
382
|
+
dataset = options.dataset;
|
|
383
|
+
resolvedConfig = options.memoryConfig;
|
|
384
|
+
resolvedVariant = {
|
|
385
|
+
name: 'custom',
|
|
386
|
+
description: 'Custom run',
|
|
387
|
+
dataset: dataset as any,
|
|
388
|
+
subset: options.subset,
|
|
389
|
+
prepareConcurrency: options.concurrency ?? 4,
|
|
390
|
+
benchConcurrency: options.concurrency ?? 10,
|
|
391
|
+
};
|
|
392
|
+
} else if (config) {
|
|
393
|
+
// New mode: config as positional, variant as flag (default: quick)
|
|
394
|
+
resolvedVariant = getRunVariant(options.variant);
|
|
395
|
+
resolvedConfig = resolveConfigAlias(config);
|
|
396
|
+
dataset = resolvedVariant.dataset;
|
|
397
|
+
} else {
|
|
398
|
+
console.error(chalk.red('Error: Please provide a <config>'));
|
|
399
|
+
console.error(chalk.gray('Run without arguments to see available options'));
|
|
400
|
+
process.exit(1);
|
|
118
401
|
}
|
|
119
|
-
|
|
120
|
-
|
|
402
|
+
|
|
403
|
+
// Apply overrides
|
|
404
|
+
const subset = options.subset ?? resolvedVariant.subset;
|
|
405
|
+
const perTypeCount = resolvedVariant.perTypeCount;
|
|
406
|
+
const concurrency = options.concurrency ?? resolvedVariant.prepareConcurrency;
|
|
407
|
+
|
|
408
|
+
console.log(chalk.blue('\n🚀 LongMemEval Data Preparation\n'));
|
|
409
|
+
console.log(chalk.gray(`Variant: ${resolvedVariant.name}`));
|
|
410
|
+
console.log(chalk.gray(`Dataset: ${dataset}`));
|
|
411
|
+
console.log(chalk.gray(`Memory Config: ${resolvedConfig}`));
|
|
412
|
+
console.log(chalk.gray(`Concurrency: ${concurrency}`));
|
|
413
|
+
if (perTypeCount) {
|
|
414
|
+
console.log(chalk.gray(`Stratified Sample: ${perTypeCount} per type`));
|
|
415
|
+
} else if (subset) {
|
|
416
|
+
console.log(chalk.gray(`Subset: ${subset} questions`));
|
|
121
417
|
}
|
|
122
|
-
if (options.
|
|
123
|
-
console.log(chalk.gray(`
|
|
418
|
+
if (options.offset) {
|
|
419
|
+
console.log(chalk.gray(`Offset: skipping first ${options.offset} questions`));
|
|
124
420
|
}
|
|
125
|
-
if (options.
|
|
126
|
-
console.log(chalk.gray(`
|
|
421
|
+
if (options.questionId) {
|
|
422
|
+
console.log(chalk.gray(`Question ID: ${options.questionId}`));
|
|
127
423
|
}
|
|
128
424
|
console.log();
|
|
129
425
|
|
|
130
426
|
// Check for OpenAI API key (needed for embeddings in semantic-recall)
|
|
131
|
-
if (
|
|
132
|
-
(options.memoryConfig === 'semantic-recall' || options.memoryConfig === 'combined') &&
|
|
133
|
-
!process.env.OPENAI_API_KEY
|
|
134
|
-
) {
|
|
427
|
+
if ((resolvedConfig === 'semantic-recall' || resolvedConfig === 'combined') && !process.env.OPENAI_API_KEY) {
|
|
135
428
|
console.error(chalk.red('Error: OPENAI_API_KEY environment variable is required for semantic recall'));
|
|
136
429
|
console.error(chalk.gray('Please set it in your environment or .env file'));
|
|
137
430
|
process.exit(1);
|
|
@@ -139,55 +432,74 @@ program
|
|
|
139
432
|
|
|
140
433
|
// Validate dataset option
|
|
141
434
|
const validDatasets = ['longmemeval_s', 'longmemeval_m', 'longmemeval_oracle'];
|
|
142
|
-
if (!validDatasets.includes(
|
|
143
|
-
console.error(chalk.red(`Invalid dataset: ${
|
|
435
|
+
if (!validDatasets.includes(dataset)) {
|
|
436
|
+
console.error(chalk.red(`Invalid dataset: ${dataset}`));
|
|
144
437
|
console.error(chalk.gray(`Valid options: ${validDatasets.join(', ')}`));
|
|
145
438
|
process.exit(1);
|
|
146
439
|
}
|
|
147
440
|
|
|
148
441
|
// Check if dataset exists and download if needed
|
|
149
|
-
await ensureDatasetExists(
|
|
150
|
-
|
|
151
|
-
// Show warning and ask for confirmation
|
|
152
|
-
console.log(chalk.yellow('\n⚠️ WARNING'));
|
|
153
|
-
console.log(chalk.yellow('━'.repeat(50)));
|
|
154
|
-
console.log(chalk.bold('\nPreparing this data can be very expensive!\n'));
|
|
155
|
-
console.log('This process will:');
|
|
156
|
-
console.log(' • Process many conversations through AI models');
|
|
157
|
-
console.log(' • Generate embeddings for semantic recall');
|
|
158
|
-
console.log(' • Potentially use significant API credits\n');
|
|
159
|
-
console.log(chalk.gray('Memory configs like "working-memory" and "combined" are especially costly.\n'));
|
|
160
|
-
|
|
161
|
-
const readline = await import('readline');
|
|
162
|
-
const rl = readline.createInterface({
|
|
163
|
-
input: process.stdin,
|
|
164
|
-
output: process.stdout,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const answer = await new Promise<string>(resolve => {
|
|
168
|
-
rl.question(chalk.bold('Are you sure you want to continue? (y/N): '), resolve);
|
|
169
|
-
});
|
|
170
|
-
rl.close();
|
|
442
|
+
await ensureDatasetExists(dataset);
|
|
171
443
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
444
|
+
// For readOnlyConfig, no preparation is needed
|
|
445
|
+
const configDef = getMemoryConfig(resolvedConfig as MemoryConfigType);
|
|
446
|
+
if (configDef.readOnlyConfig && configDef.baseConfig) {
|
|
447
|
+
console.log(
|
|
448
|
+
chalk.green(`✓ Config "${resolvedConfig}" is read-only and uses data from "${configDef.baseConfig}"`),
|
|
449
|
+
);
|
|
450
|
+
console.log(chalk.gray(` No preparation needed. Run benchmark directly with: pnpm bench ${resolvedConfig}`));
|
|
451
|
+
console.log(chalk.gray(` Make sure "${configDef.baseConfig}" is prepared first.\n`));
|
|
452
|
+
return;
|
|
175
453
|
}
|
|
176
454
|
|
|
177
|
-
|
|
455
|
+
// Show warning and ask for confirmation (skip if -y flag is passed)
|
|
456
|
+
if (!options.yes) {
|
|
457
|
+
console.log(chalk.yellow('\n⚠️ WARNING'));
|
|
458
|
+
console.log(chalk.yellow('━'.repeat(50)));
|
|
459
|
+
console.log(chalk.bold('\nPreparing this data can be very expensive!\n'));
|
|
460
|
+
console.log('This process will:');
|
|
461
|
+
console.log(' • Process many conversations through AI models');
|
|
462
|
+
console.log(' • Generate embeddings for semantic recall');
|
|
463
|
+
console.log(' • Potentially use significant API credits\n');
|
|
464
|
+
console.log(chalk.gray('Memory configs like "working-memory" and "combined" are especially costly.\n'));
|
|
465
|
+
|
|
466
|
+
const readline = await import('readline');
|
|
467
|
+
const rl = readline.createInterface({
|
|
468
|
+
input: process.stdin,
|
|
469
|
+
output: process.stdout,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const answer = await new Promise<string>(resolve => {
|
|
473
|
+
rl.question(chalk.bold('Are you sure you want to continue? (y/N): '), resolve);
|
|
474
|
+
});
|
|
475
|
+
rl.close();
|
|
476
|
+
|
|
477
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
478
|
+
console.log(chalk.gray('\nCancelled by user.'));
|
|
479
|
+
process.exit(0);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
console.log(); // Add spacing before continuing
|
|
483
|
+
}
|
|
178
484
|
|
|
179
485
|
// Run prepare command
|
|
180
486
|
const prepareCommand = new PrepareCommand();
|
|
181
487
|
await prepareCommand.run({
|
|
182
|
-
dataset:
|
|
183
|
-
memoryConfig:
|
|
488
|
+
dataset: dataset as DatasetType,
|
|
489
|
+
memoryConfig: resolvedConfig as MemoryConfigType,
|
|
184
490
|
outputDir: options.output,
|
|
185
|
-
subset:
|
|
186
|
-
|
|
491
|
+
subset: perTypeCount ? undefined : subset, // Don't use subset if using perTypeCount
|
|
492
|
+
perTypeCount,
|
|
493
|
+
offset: options.offset,
|
|
494
|
+
concurrency,
|
|
187
495
|
questionId: options.questionId,
|
|
188
496
|
resumeFromMessageId: options.resumeFromMessageId,
|
|
189
497
|
sessionLimit: options.sessionLimit,
|
|
190
498
|
sessionOffset: options.sessionOffset,
|
|
499
|
+
fromFailures: options.fromFailures,
|
|
500
|
+
olderThan: options.olderThan,
|
|
501
|
+
dryRun: options.dryRun,
|
|
502
|
+
forceRegenerate: options.forceRegenerate,
|
|
191
503
|
});
|
|
192
504
|
|
|
193
505
|
// Force exit after completion
|
|
@@ -200,25 +512,99 @@ program
|
|
|
200
512
|
}
|
|
201
513
|
});
|
|
202
514
|
|
|
203
|
-
// Run benchmark command
|
|
515
|
+
// Run benchmark command (aliased as 'bench' too)
|
|
204
516
|
program
|
|
205
|
-
.command('run')
|
|
517
|
+
.command('run [config]')
|
|
518
|
+
.alias('bench')
|
|
206
519
|
.description('Run LongMemEval benchmark using prepared data')
|
|
207
|
-
.
|
|
208
|
-
.requiredOption('-m, --model <model>', 'Model to use (e.g., gpt-4o, claude-3-opus)')
|
|
209
|
-
.option(
|
|
210
|
-
'-c, --memory-config <config>',
|
|
211
|
-
'Memory configuration (last-k, semantic-recall, semantic-recall-reranked, working-memory, working-memory-tailored, combined, combined-tailored)',
|
|
212
|
-
'semantic-recall',
|
|
213
|
-
)
|
|
520
|
+
.option('-v, --variant <variant>', 'Run variant (quick, full, rip, sample, sample-comb)', 'quick')
|
|
214
521
|
.option('-o, --output <dir>', 'Output directory for results', './results')
|
|
215
522
|
.option('--prepared-data <dir>', 'Directory containing prepared data', './prepared-data')
|
|
216
|
-
.option('--subset <n>', '
|
|
217
|
-
.option('--
|
|
523
|
+
.option('--subset <n>', 'Override subset size', parseInt)
|
|
524
|
+
.option('--offset <n>', 'Skip first n questions', parseInt)
|
|
525
|
+
.option('--concurrency <n>', 'Override concurrency', parseInt)
|
|
218
526
|
.option('--question-id <id>', 'Focus on a specific question by ID')
|
|
219
|
-
.
|
|
527
|
+
.option('-t, --type <type>', 'Filter to a specific question type (e.g., multi-session, knowledge-update)')
|
|
528
|
+
.option('--comb-offset <n>', 'Comb sampling: stride between questions (for sample-comb variant)', parseInt)
|
|
529
|
+
.option('--start-offset <n>', 'Comb sampling: starting index (for sample-comb variant)', parseInt)
|
|
530
|
+
.option('--no-fixed', 'Skip improved/fixed question evaluation')
|
|
531
|
+
.option(
|
|
532
|
+
'--from-failures [path]',
|
|
533
|
+
'Re-run only failed questions from a previous run (path to failures.json or "latest")',
|
|
534
|
+
)
|
|
535
|
+
.option(
|
|
536
|
+
'--older-than <duration>',
|
|
537
|
+
'With --from-failures: only re-run questions prepared before this duration (e.g., "1h", "30m", "2d")',
|
|
538
|
+
)
|
|
539
|
+
.option('--resume [run-id]', 'Resume a partial run (omit run-id to auto-detect most recent)')
|
|
540
|
+
// Legacy options for backwards compatibility
|
|
541
|
+
.option('-d, --dataset <dataset>', 'Dataset to use (legacy)')
|
|
542
|
+
.option('-c, --memory-config <config>', 'Memory configuration (legacy)')
|
|
543
|
+
.action(async (config, options) => {
|
|
220
544
|
try {
|
|
545
|
+
// If no config provided, show help
|
|
546
|
+
if (!config && !options.dataset) {
|
|
547
|
+
showAvailableOptions();
|
|
548
|
+
process.exit(0);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Resolve variant and config (support legacy options)
|
|
552
|
+
let resolvedVariant: ReturnType<typeof getRunVariant>;
|
|
553
|
+
let resolvedConfig: string;
|
|
554
|
+
let dataset: string;
|
|
555
|
+
|
|
556
|
+
if (options.dataset && options.memoryConfig) {
|
|
557
|
+
// Legacy mode: use -d and -c flags
|
|
558
|
+
dataset = options.dataset;
|
|
559
|
+
resolvedConfig = options.memoryConfig;
|
|
560
|
+
resolvedVariant = {
|
|
561
|
+
name: 'custom',
|
|
562
|
+
description: 'Custom run',
|
|
563
|
+
dataset: dataset as any,
|
|
564
|
+
subset: options.subset,
|
|
565
|
+
prepareConcurrency: options.concurrency ?? 4,
|
|
566
|
+
benchConcurrency: options.concurrency ?? 10,
|
|
567
|
+
};
|
|
568
|
+
} else if (config) {
|
|
569
|
+
// New mode: config as positional, variant as flag (default: quick)
|
|
570
|
+
resolvedVariant = getRunVariant(options.variant);
|
|
571
|
+
resolvedConfig = resolveConfigAlias(config);
|
|
572
|
+
dataset = resolvedVariant.dataset;
|
|
573
|
+
} else {
|
|
574
|
+
console.error(chalk.red('Error: Please provide a <config>'));
|
|
575
|
+
console.error(chalk.gray('Run without arguments to see available options'));
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Apply overrides
|
|
580
|
+
const subset = options.subset ?? resolvedVariant.subset;
|
|
581
|
+
const perTypeCount = resolvedVariant.perTypeCount;
|
|
582
|
+
const combSampleSize = resolvedVariant.combSampleSize;
|
|
583
|
+
const combOffset = options.combOffset ?? resolvedVariant.combOffset;
|
|
584
|
+
const combStartOffset = options.startOffset ?? resolvedVariant.combStartOffset;
|
|
585
|
+
const concurrency = options.concurrency ?? resolvedVariant.benchConcurrency;
|
|
586
|
+
|
|
221
587
|
console.log(chalk.blue('\n🚀 LongMemEval Benchmark Runner\n'));
|
|
588
|
+
console.log(chalk.gray(`Variant: ${resolvedVariant.name}`));
|
|
589
|
+
console.log(chalk.gray(`Dataset: ${dataset}`));
|
|
590
|
+
console.log(chalk.gray(`Memory Config: ${resolvedConfig}`));
|
|
591
|
+
console.log(chalk.gray(`Concurrency: ${concurrency}`));
|
|
592
|
+
if (combSampleSize) {
|
|
593
|
+
console.log(
|
|
594
|
+
chalk.gray(`Comb Sample: ${combSampleSize} per type (offset=${combOffset}, start=${combStartOffset})`),
|
|
595
|
+
);
|
|
596
|
+
} else if (perTypeCount) {
|
|
597
|
+
console.log(chalk.gray(`Stratified Sample: ${perTypeCount} per type`));
|
|
598
|
+
} else if (subset) {
|
|
599
|
+
console.log(chalk.gray(`Subset: ${subset} questions`));
|
|
600
|
+
}
|
|
601
|
+
if (options.offset) {
|
|
602
|
+
console.log(chalk.gray(`Offset: skipping first ${options.offset} questions`));
|
|
603
|
+
}
|
|
604
|
+
if (options.questionId) {
|
|
605
|
+
console.log(chalk.gray(`Question ID: ${options.questionId}`));
|
|
606
|
+
}
|
|
607
|
+
console.log();
|
|
222
608
|
|
|
223
609
|
// Check for OpenAI API key
|
|
224
610
|
if (!process.env.OPENAI_API_KEY) {
|
|
@@ -229,8 +615,8 @@ program
|
|
|
229
615
|
|
|
230
616
|
// Validate dataset option
|
|
231
617
|
const validDatasets = ['longmemeval_s', 'longmemeval_m', 'longmemeval_oracle'];
|
|
232
|
-
if (!validDatasets.includes(
|
|
233
|
-
console.error(chalk.red(`Invalid dataset: ${
|
|
618
|
+
if (!validDatasets.includes(dataset)) {
|
|
619
|
+
console.error(chalk.red(`Invalid dataset: ${dataset}`));
|
|
234
620
|
console.error(chalk.gray(`Valid options: ${validDatasets.join(', ')}`));
|
|
235
621
|
process.exit(1);
|
|
236
622
|
}
|
|
@@ -238,14 +624,23 @@ program
|
|
|
238
624
|
// Run benchmark using prepared data
|
|
239
625
|
const runCommand = new RunCommand();
|
|
240
626
|
await runCommand.run({
|
|
241
|
-
dataset:
|
|
242
|
-
memoryConfig:
|
|
243
|
-
model: options.model,
|
|
627
|
+
dataset: dataset as DatasetType,
|
|
628
|
+
memoryConfig: resolvedConfig as MemoryConfigType,
|
|
244
629
|
preparedDataDir: options.preparedData,
|
|
245
630
|
outputDir: options.output,
|
|
246
|
-
subset:
|
|
247
|
-
|
|
631
|
+
subset: combSampleSize || perTypeCount ? undefined : subset, // Don't use subset if using sampling
|
|
632
|
+
perTypeCount,
|
|
633
|
+
combSampleSize,
|
|
634
|
+
combOffset,
|
|
635
|
+
combStartOffset,
|
|
636
|
+
offset: options.offset,
|
|
637
|
+
concurrency,
|
|
248
638
|
questionId: options.questionId,
|
|
639
|
+
questionType: options.type,
|
|
640
|
+
skipFixed: options.fixed === false, // --no-fixed sets options.fixed to false
|
|
641
|
+
fromFailures: options.fromFailures,
|
|
642
|
+
resume: options.resume,
|
|
643
|
+
olderThan: options.olderThan,
|
|
249
644
|
});
|
|
250
645
|
|
|
251
646
|
// Force exit after completion
|
|
@@ -387,14 +782,104 @@ program
|
|
|
387
782
|
}
|
|
388
783
|
});
|
|
389
784
|
|
|
785
|
+
// Sync command - sync improved_question/improved_answer from dataset to prepared meta.json files
|
|
786
|
+
program
|
|
787
|
+
.command('sync')
|
|
788
|
+
.description('Sync improved_question and improved_answer from dataset JSON to prepared meta.json files')
|
|
789
|
+
.requiredOption('-d, --dataset <dataset>', 'Dataset to sync from (longmemeval_s, longmemeval_m, longmemeval_oracle)')
|
|
790
|
+
.option('-c, --memory-config <config>', 'Memory configuration', 'working-memory')
|
|
791
|
+
.option('--prepared-data <dir>', 'Directory containing prepared data', './prepared-data')
|
|
792
|
+
.action(async options => {
|
|
793
|
+
try {
|
|
794
|
+
console.log(chalk.blue('\n🔄 Syncing Improved Questions/Answers\n'));
|
|
795
|
+
|
|
796
|
+
// Validate dataset option
|
|
797
|
+
const validDatasets = ['longmemeval_s', 'longmemeval_m', 'longmemeval_oracle'];
|
|
798
|
+
if (!validDatasets.includes(options.dataset)) {
|
|
799
|
+
console.error(chalk.red(`Invalid dataset: ${options.dataset}`));
|
|
800
|
+
console.error(chalk.gray(`Valid options: ${validDatasets.join(', ')}`));
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const syncCommand = new SyncCommand();
|
|
805
|
+
await syncCommand.run({
|
|
806
|
+
dataset: options.dataset,
|
|
807
|
+
memoryConfig: options.memoryConfig,
|
|
808
|
+
preparedDataDir: options.preparedData,
|
|
809
|
+
});
|
|
810
|
+
} catch (error) {
|
|
811
|
+
console.error(chalk.red('\nError:'), error);
|
|
812
|
+
process.exit(1);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
390
816
|
// Results command - shows latest results for each memory configuration
|
|
817
|
+
program
|
|
818
|
+
.command('clean')
|
|
819
|
+
.description('Delete prepared data by offset/subset')
|
|
820
|
+
.requiredOption('-d, --dataset <name>', 'Dataset name (longmemeval_s, longmemeval_m, longmemeval_oracle)')
|
|
821
|
+
.option('-c, --memory-config <type>', 'Memory configuration to clean', 'observational-memory')
|
|
822
|
+
.option('-o, --offset <n>', 'Skip first N questions (delete from N+1 onwards)', parseInt)
|
|
823
|
+
.option('-s, --subset <n>', 'Only delete N questions (after offset)', parseInt)
|
|
824
|
+
.option('-q, --question-id <id>', 'Delete a specific question by ID')
|
|
825
|
+
.option('-p, --prepared-data <dir>', 'Prepared data directory', './prepared-data')
|
|
826
|
+
.option('--partial', 'Only delete partially prepared questions (have progress.json but no meta.json)')
|
|
827
|
+
.option('--dry-run', 'Show what would be deleted without actually deleting')
|
|
828
|
+
.action(async options => {
|
|
829
|
+
try {
|
|
830
|
+
const cleanCommand = new CleanCommand();
|
|
831
|
+
await cleanCommand.run({
|
|
832
|
+
dataset: options.dataset,
|
|
833
|
+
memoryConfig: options.memoryConfig,
|
|
834
|
+
preparedDataDir: options.preparedData,
|
|
835
|
+
offset: options.offset,
|
|
836
|
+
subset: options.subset,
|
|
837
|
+
questionId: options.questionId,
|
|
838
|
+
dryRun: options.dryRun,
|
|
839
|
+
partial: options.partial,
|
|
840
|
+
});
|
|
841
|
+
} catch (error) {
|
|
842
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// Obscure thread IDs command - replace thread IDs with hashed versions to prevent LLM bias
|
|
848
|
+
program
|
|
849
|
+
.command('obscure-thread-ids')
|
|
850
|
+
.description('Replace thread IDs in prepared om.json files with hashed versions to prevent LLM bias')
|
|
851
|
+
.requiredOption('-d, --dataset <name>', 'Dataset name (longmemeval_s, longmemeval_m, longmemeval_oracle)')
|
|
852
|
+
.option('-c, --memory-config <type>', 'Memory configuration', 'observational-memory')
|
|
853
|
+
.option('-p, --prepared-data <dir>', 'Prepared data directory', './prepared-data')
|
|
854
|
+
.option('--dry-run', 'Show what would be changed without actually modifying files')
|
|
855
|
+
.action(async options => {
|
|
856
|
+
try {
|
|
857
|
+
const obscureCommand = new ObscureThreadIdsCommand();
|
|
858
|
+
await obscureCommand.run({
|
|
859
|
+
dataset: options.dataset,
|
|
860
|
+
memoryConfig: options.memoryConfig,
|
|
861
|
+
preparedDataDir: options.preparedData,
|
|
862
|
+
dryRun: options.dryRun,
|
|
863
|
+
});
|
|
864
|
+
} catch (error) {
|
|
865
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
|
|
391
870
|
program
|
|
392
871
|
.command('results')
|
|
393
872
|
.description('Show latest benchmark results for each memory configuration')
|
|
394
873
|
.option('-r, --results <dir>', 'Results directory', './results')
|
|
395
874
|
.option('-d, --dataset <dataset>', 'Filter by dataset')
|
|
396
|
-
.option('-
|
|
875
|
+
.option('-l, --latest', 'Show only the latest result per config')
|
|
876
|
+
.option('--min-questions <n>', 'Minimum questions to include (default: 20)', parseInt)
|
|
877
|
+
.option('-s, --sort <by>', 'Sort by: date, accuracy, fixed (default: date)', 'date')
|
|
878
|
+
.option('--no-fixed', 'Hide fixed accuracy numbers')
|
|
397
879
|
.action(async options => {
|
|
880
|
+
const minQuestions = options.minQuestions ?? 20;
|
|
881
|
+
const sortBy = options.sort ?? 'date';
|
|
882
|
+
const showFixed = options.fixed !== false;
|
|
398
883
|
try {
|
|
399
884
|
console.log(chalk.blue('\n📊 Benchmark Results Summary\n'));
|
|
400
885
|
|
|
@@ -414,6 +899,7 @@ program
|
|
|
414
899
|
metrics: any;
|
|
415
900
|
config: any;
|
|
416
901
|
timestamp: string;
|
|
902
|
+
metricsPath: string;
|
|
417
903
|
}> = [];
|
|
418
904
|
|
|
419
905
|
// First, try new structure
|
|
@@ -437,11 +923,17 @@ program
|
|
|
437
923
|
continue;
|
|
438
924
|
}
|
|
439
925
|
|
|
926
|
+
// Filter by minimum questions
|
|
927
|
+
if (data.total_questions < minQuestions) {
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
|
|
440
931
|
allRuns.push({
|
|
441
932
|
runId: runDir,
|
|
442
933
|
metrics: data,
|
|
443
934
|
config: data.config,
|
|
444
935
|
timestamp: data.timestamp,
|
|
936
|
+
metricsPath,
|
|
445
937
|
});
|
|
446
938
|
} catch (error) {
|
|
447
939
|
// Skip runs with missing or invalid metrics
|
|
@@ -465,11 +957,17 @@ program
|
|
|
465
957
|
continue;
|
|
466
958
|
}
|
|
467
959
|
|
|
960
|
+
// Filter by minimum questions
|
|
961
|
+
if (data.total_questions < minQuestions) {
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
|
|
468
965
|
allRuns.push({
|
|
469
966
|
runId: runDir,
|
|
470
967
|
metrics: data,
|
|
471
968
|
config: data.config,
|
|
472
969
|
timestamp: data.timestamp,
|
|
970
|
+
metricsPath,
|
|
473
971
|
});
|
|
474
972
|
} catch (error) {
|
|
475
973
|
// Skip runs with missing or invalid metrics
|
|
@@ -481,106 +979,175 @@ program
|
|
|
481
979
|
return;
|
|
482
980
|
}
|
|
483
981
|
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
982
|
+
// Sort all runs based on sortBy option (best/newest at bottom for terminal viewing)
|
|
983
|
+
let runsToShow = [...allRuns];
|
|
984
|
+
|
|
985
|
+
if (options.latest) {
|
|
986
|
+
// Group by config and take only the latest from each
|
|
987
|
+
const byConfig = new Map<string, (typeof allRuns)[0]>();
|
|
988
|
+
for (const run of allRuns) {
|
|
989
|
+
const key = `${run.config.dataset}_${run.config.memoryConfig}`;
|
|
990
|
+
const existing = byConfig.get(key);
|
|
991
|
+
if (!existing || run.timestamp > existing.timestamp) {
|
|
992
|
+
byConfig.set(key, run);
|
|
993
|
+
}
|
|
490
994
|
}
|
|
491
|
-
|
|
995
|
+
runsToShow = Array.from(byConfig.values());
|
|
492
996
|
}
|
|
493
997
|
|
|
494
|
-
// Sort
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
998
|
+
// Sort runs (oldest/worst first, newest/best at bottom for terminal viewing)
|
|
999
|
+
runsToShow.sort((a, b) => {
|
|
1000
|
+
if (sortBy === 'date') {
|
|
1001
|
+
// Sort by date (oldest first, newest at bottom)
|
|
1002
|
+
return a.timestamp.localeCompare(b.timestamp);
|
|
1003
|
+
} else if (sortBy === 'fixed') {
|
|
1004
|
+
// Sort by fixed accuracy (worst first, best at bottom), fall back to vanilla if no fixed
|
|
1005
|
+
const aFixed = a.metrics.fixed_overall_accuracy ?? a.metrics.overall_accuracy;
|
|
1006
|
+
const bFixed = b.metrics.fixed_overall_accuracy ?? b.metrics.overall_accuracy;
|
|
1007
|
+
return aFixed - bFixed;
|
|
1008
|
+
} else {
|
|
1009
|
+
// Sort by vanilla accuracy (worst first, best at bottom)
|
|
1010
|
+
return a.metrics.overall_accuracy - b.metrics.overall_accuracy;
|
|
1011
|
+
}
|
|
502
1012
|
});
|
|
503
1013
|
|
|
504
|
-
for (const
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
// Show latest or all
|
|
509
|
-
const runsToShow = options.all ? runs : [runs[0]];
|
|
510
|
-
|
|
511
|
-
for (const run of runsToShow) {
|
|
512
|
-
// Get terminal width, default to 80 if not available
|
|
513
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
514
|
-
const lineWidth = Math.min(terminalWidth - 1, 80); // Cap at 80 for readability
|
|
515
|
-
|
|
516
|
-
console.log(chalk.bold('\n' + '═'.repeat(lineWidth) + '\n'));
|
|
517
|
-
|
|
518
|
-
// Configuration header
|
|
519
|
-
console.log(chalk.bold('Configuration:\n'));
|
|
520
|
-
console.log(chalk.gray('Dataset:'), chalk.cyan(run.config.dataset));
|
|
521
|
-
console.log(chalk.gray('Model:'), chalk.cyan(run.config.model));
|
|
522
|
-
console.log(chalk.gray('Memory Config:'), chalk.cyan(run.config.memoryConfig));
|
|
523
|
-
if (run.config.subset) {
|
|
524
|
-
console.log(chalk.gray('Subset:'), chalk.cyan(`${run.config.subset} questions`));
|
|
525
|
-
}
|
|
526
|
-
console.log(chalk.gray('Run ID:'), chalk.dim(run.runId));
|
|
527
|
-
console.log(chalk.gray('Timestamp:'), chalk.dim(new Date(run.timestamp).toLocaleString()));
|
|
528
|
-
console.log(chalk.gray('─'.repeat(Math.min(lineWidth, 60))));
|
|
529
|
-
|
|
530
|
-
// Display metrics using same format as regular runs
|
|
531
|
-
const metrics = run.metrics;
|
|
1014
|
+
for (const run of runsToShow) {
|
|
1015
|
+
// Get terminal width, default to 80 if not available
|
|
1016
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
1017
|
+
const lineWidth = Math.min(terminalWidth - 1, 80); // Cap at 80 for readability
|
|
532
1018
|
|
|
533
|
-
|
|
534
|
-
const typeAccuracies = Object.values(metrics.accuracy_by_type).map((t: any) => t.accuracy);
|
|
535
|
-
const recalculatedOverall =
|
|
536
|
-
typeAccuracies.length > 0 ? typeAccuracies.reduce((sum, acc) => sum + acc, 0) / typeAccuracies.length : 0;
|
|
537
|
-
metrics.overall_accuracy = recalculatedOverall;
|
|
1019
|
+
console.log(chalk.bold('\n' + '═'.repeat(lineWidth) + '\n'));
|
|
538
1020
|
|
|
539
|
-
|
|
1021
|
+
// Configuration header
|
|
1022
|
+
console.log(chalk.bold('Configuration:\n'));
|
|
1023
|
+
console.log(chalk.gray('Dataset:'), chalk.cyan(run.config.dataset));
|
|
1024
|
+
console.log(chalk.gray('Model:'), chalk.cyan(run.config.model));
|
|
1025
|
+
console.log(chalk.gray('Memory Config:'), chalk.cyan(run.config.memoryConfig));
|
|
1026
|
+
if (run.config.subset) {
|
|
1027
|
+
console.log(chalk.gray('Subset:'), chalk.cyan(`${run.config.subset} questions`));
|
|
1028
|
+
}
|
|
1029
|
+
console.log(chalk.gray('Run ID:'), chalk.dim(run.runId));
|
|
1030
|
+
console.log(chalk.gray('Timestamp:'), chalk.dim(new Date(run.timestamp).toLocaleString()));
|
|
1031
|
+
// Make path relative to cwd
|
|
1032
|
+
const relativePath = require('path').relative(process.cwd(), require('path').resolve(run.metricsPath));
|
|
1033
|
+
console.log(chalk.gray('Metrics:'), chalk.blue(relativePath));
|
|
1034
|
+
console.log(chalk.gray('─'.repeat(Math.min(lineWidth, 60))));
|
|
1035
|
+
|
|
1036
|
+
// Display metrics using same format as regular runs
|
|
1037
|
+
const metrics = run.metrics;
|
|
1038
|
+
|
|
1039
|
+
// Recalculate overall accuracy using the new formula (average of type averages)
|
|
1040
|
+
const typeAccuracies = Object.values(metrics.accuracy_by_type).map((t: any) => t.accuracy);
|
|
1041
|
+
const recalculatedOverall =
|
|
1042
|
+
typeAccuracies.length > 0 ? typeAccuracies.reduce((sum, acc) => sum + acc, 0) / typeAccuracies.length : 0;
|
|
1043
|
+
metrics.overall_accuracy = recalculatedOverall;
|
|
1044
|
+
|
|
1045
|
+
// Check if fixed accuracy data exists
|
|
1046
|
+
const hasFixedAccuracy =
|
|
1047
|
+
showFixed && metrics.fixed_accuracy_by_type && Object.keys(metrics.fixed_accuracy_by_type).length > 0;
|
|
1048
|
+
|
|
1049
|
+
// Question type breakdown
|
|
1050
|
+
if (hasFixedAccuracy) {
|
|
1051
|
+
console.log(chalk.bold('\nAccuracy by Question Type:'), chalk.gray('(vanilla → fixed)'));
|
|
1052
|
+
} else {
|
|
540
1053
|
console.log(chalk.bold('\nAccuracy by Question Type:'));
|
|
1054
|
+
}
|
|
541
1055
|
|
|
542
|
-
|
|
543
|
-
|
|
1056
|
+
// Sort question types alphabetically
|
|
1057
|
+
const sortedTypes = Object.entries(metrics.accuracy_by_type).sort(([a], [b]) => a.localeCompare(b));
|
|
544
1058
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1059
|
+
for (const [type, typeMetrics] of sortedTypes) {
|
|
1060
|
+
const { correct, total, accuracy } = typeMetrics as any;
|
|
1061
|
+
const typeColor = accuracy >= 0.8 ? 'green' : accuracy >= 0.6 ? 'yellow' : 'red';
|
|
548
1062
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1063
|
+
// Create a simple progress bar
|
|
1064
|
+
const barLength = 20;
|
|
1065
|
+
const filledLength = Math.round(accuracy * barLength);
|
|
1066
|
+
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
553
1067
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
);
|
|
1068
|
+
let fixedPart = '';
|
|
1069
|
+
if (hasFixedAccuracy && metrics.fixed_accuracy_by_type[type]) {
|
|
1070
|
+
const fixedMetrics = metrics.fixed_accuracy_by_type[type] as any;
|
|
1071
|
+
const fixedColor = fixedMetrics.accuracy >= 0.8 ? 'green' : fixedMetrics.accuracy >= 0.6 ? 'yellow' : 'red';
|
|
1072
|
+
fixedPart = chalk.gray(' → ') + chalk[fixedColor](`${(fixedMetrics.accuracy * 100).toFixed(1)}%`);
|
|
560
1073
|
}
|
|
561
1074
|
|
|
562
|
-
|
|
1075
|
+
console.log(
|
|
1076
|
+
chalk.gray(` ${type.padEnd(25)}:`),
|
|
1077
|
+
chalk[typeColor](`${(accuracy * 100).toFixed(1).padStart(5)}%`) + fixedPart,
|
|
1078
|
+
chalk.gray(`[${bar}]`),
|
|
1079
|
+
chalk.gray(`(${correct}/${total})`),
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
563
1082
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
1083
|
+
// Abstention is hidden - it tests LLM reasoning ability rather than memory system performance
|
|
1084
|
+
|
|
1085
|
+
// Overall summary at the bottom
|
|
1086
|
+
console.log();
|
|
1087
|
+
const accuracyColor =
|
|
1088
|
+
metrics.overall_accuracy >= 0.8 ? 'green' : metrics.overall_accuracy >= 0.6 ? 'yellow' : 'red';
|
|
1089
|
+
|
|
1090
|
+
if (hasFixedAccuracy && metrics.fixed_overall_accuracy != null) {
|
|
1091
|
+
const fixedOverallColor =
|
|
1092
|
+
metrics.fixed_overall_accuracy >= 0.8 ? 'green' : metrics.fixed_overall_accuracy >= 0.6 ? 'yellow' : 'red';
|
|
1093
|
+
console.log(
|
|
1094
|
+
chalk.bold('Overall Accuracy:'),
|
|
1095
|
+
chalk[accuracyColor](`${(metrics.overall_accuracy * 100).toFixed(2)}%`),
|
|
1096
|
+
chalk.gray('→'),
|
|
1097
|
+
chalk[fixedOverallColor](`${(metrics.fixed_overall_accuracy * 100).toFixed(2)}%`),
|
|
1098
|
+
chalk.gray('(fixed)'),
|
|
1099
|
+
);
|
|
1100
|
+
} else {
|
|
568
1101
|
console.log(
|
|
569
1102
|
chalk.bold('Overall Accuracy:'),
|
|
570
1103
|
chalk[accuracyColor](`${(metrics.overall_accuracy * 100).toFixed(2)}%`),
|
|
571
1104
|
chalk.gray(`(average of ${Object.keys(metrics.accuracy_by_type).length} question types)`),
|
|
572
1105
|
);
|
|
573
1106
|
}
|
|
1107
|
+
|
|
1108
|
+
// Token usage summary (if available)
|
|
1109
|
+
const formatTokens = (n: number) => n.toLocaleString();
|
|
1110
|
+
|
|
1111
|
+
if (metrics.total_usage) {
|
|
1112
|
+
console.log(
|
|
1113
|
+
chalk.gray('Answering Tokens:'),
|
|
1114
|
+
chalk.cyan(formatTokens(metrics.total_usage.inputTokens)),
|
|
1115
|
+
chalk.gray('input,'),
|
|
1116
|
+
chalk.cyan(formatTokens(metrics.total_usage.outputTokens)),
|
|
1117
|
+
chalk.gray('output'),
|
|
1118
|
+
chalk.gray(
|
|
1119
|
+
`(avg ${formatTokens(Math.round(metrics.total_usage.inputTokens / metrics.total_questions))}/q)`,
|
|
1120
|
+
),
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Load preparation token usage from om-debug.jsonl files
|
|
1125
|
+
const prepUsage = await loadPreparationTokenUsage('./prepared-data', run.config.memoryConfig);
|
|
1126
|
+
if (prepUsage && prepUsage.totalInputTokens > 0) {
|
|
1127
|
+
console.log(
|
|
1128
|
+
chalk.gray('Preparation Tokens:'),
|
|
1129
|
+
chalk.cyan(formatTokens(prepUsage.totalInputTokens)),
|
|
1130
|
+
chalk.gray('input,'),
|
|
1131
|
+
chalk.cyan(formatTokens(prepUsage.totalOutputTokens)),
|
|
1132
|
+
chalk.gray('output'),
|
|
1133
|
+
chalk.gray(
|
|
1134
|
+
`(Observer: ${formatTokens(prepUsage.observerInputTokens)}, Reflector: ${formatTokens(prepUsage.reflectorInputTokens)})`,
|
|
1135
|
+
),
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
574
1138
|
}
|
|
575
1139
|
|
|
576
1140
|
// Get terminal width for final separator
|
|
577
1141
|
const terminalWidth = process.stdout.columns || 80;
|
|
578
1142
|
const lineWidth = Math.min(terminalWidth - 1, 80);
|
|
579
1143
|
|
|
1144
|
+
// Count unique configurations
|
|
1145
|
+
const uniqueConfigs = new Set(allRuns.map(r => `${r.config.dataset}_${r.config.memoryConfig}`));
|
|
1146
|
+
|
|
580
1147
|
console.log(chalk.bold('\n' + '═'.repeat(lineWidth)));
|
|
581
|
-
console.log(chalk.gray(`\nFound ${allRuns.length} total runs across ${
|
|
582
|
-
if (!options.
|
|
583
|
-
console.log(chalk.gray('Use --
|
|
1148
|
+
console.log(chalk.gray(`\nFound ${allRuns.length} total runs across ${uniqueConfigs.size} configurations`));
|
|
1149
|
+
if (!options.latest && uniqueConfigs.size > 0 && allRuns.length > uniqueConfigs.size) {
|
|
1150
|
+
console.log(chalk.gray('Use --latest to see only the latest run per config'));
|
|
584
1151
|
}
|
|
585
1152
|
} catch (error) {
|
|
586
1153
|
console.error(chalk.red('\nError:'), error);
|
|
@@ -670,8 +1237,8 @@ async function ensureDatasetExists(dataset: string) {
|
|
|
670
1237
|
console.log(chalk.blue('Downloading dataset...\n'));
|
|
671
1238
|
|
|
672
1239
|
try {
|
|
673
|
-
// Run the download script
|
|
674
|
-
execSync(
|
|
1240
|
+
// Run the download script with specific dataset
|
|
1241
|
+
execSync(`pnpm download -- --dataset ${dataset}`, { stdio: 'inherit' });
|
|
675
1242
|
|
|
676
1243
|
// Verify download succeeded
|
|
677
1244
|
if (!existsSync(datasetPath) || statSync(datasetPath).size < 1000000) {
|
|
@@ -687,4 +1254,249 @@ async function ensureDatasetExists(dataset: string) {
|
|
|
687
1254
|
}
|
|
688
1255
|
}
|
|
689
1256
|
|
|
1257
|
+
// Sessions command - browse answer sessions for a question
|
|
1258
|
+
program
|
|
1259
|
+
.command('sessions')
|
|
1260
|
+
.description('Browse answer sessions for a specific question ID')
|
|
1261
|
+
.requiredOption('-d, --dataset <dataset>', 'Dataset to use (longmemeval_s, longmemeval_m, longmemeval_oracle)')
|
|
1262
|
+
.requiredOption('-q, --question-id <id>', 'Question ID to browse')
|
|
1263
|
+
.option('-a, --all', 'Show all haystack sessions, not just answer sessions')
|
|
1264
|
+
.action(async options => {
|
|
1265
|
+
try {
|
|
1266
|
+
const sessionsCommand = new SessionsCommand();
|
|
1267
|
+
await sessionsCommand.run({
|
|
1268
|
+
dataset: options.dataset,
|
|
1269
|
+
questionId: options.questionId,
|
|
1270
|
+
showAll: options.all,
|
|
1271
|
+
});
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1274
|
+
process.exit(1);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
// Deterministic IDs command - update message IDs to be deterministic
|
|
1279
|
+
program
|
|
1280
|
+
.command('deterministic-ids')
|
|
1281
|
+
.description('Update message IDs in prepared data to be deterministic (thread_id_msg_index)')
|
|
1282
|
+
.option('--prepared-data-dir <dir>', 'Directory containing prepared data')
|
|
1283
|
+
.option('-q, --question-id <id>', 'Only update a specific question')
|
|
1284
|
+
.action(async options => {
|
|
1285
|
+
try {
|
|
1286
|
+
const command = new DeterministicIdsCommand({
|
|
1287
|
+
preparedDataDir: options.preparedDataDir,
|
|
1288
|
+
questionId: options.questionId,
|
|
1289
|
+
});
|
|
1290
|
+
await command.run();
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
// List partial command - find partially prepared questions
|
|
1298
|
+
program
|
|
1299
|
+
.command('list-partial')
|
|
1300
|
+
.description('List partially prepared or failed questions')
|
|
1301
|
+
.option('--prepared-data-dir <dir>', 'Directory containing prepared data')
|
|
1302
|
+
.action(async options => {
|
|
1303
|
+
try {
|
|
1304
|
+
const command = new ListPartialCommand({
|
|
1305
|
+
preparedDataDir: options.preparedDataDir,
|
|
1306
|
+
});
|
|
1307
|
+
await command.run();
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1310
|
+
process.exit(1);
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
// Tokens command - estimate token counts for questions
|
|
1315
|
+
program
|
|
1316
|
+
.command('tokens')
|
|
1317
|
+
.description('Estimate token counts for LongMemEval questions')
|
|
1318
|
+
.requiredOption('-d, --dataset <dataset>', 'Dataset to use (longmemeval_s, longmemeval_m, longmemeval_oracle)')
|
|
1319
|
+
.option('-q, --question-id <id>', 'Analyze a specific question by ID')
|
|
1320
|
+
.option('-o, --offset <n>', 'Skip first n questions', parseInt)
|
|
1321
|
+
.option('-s, --subset <n>', 'Analyze only n questions (after offset)', parseInt)
|
|
1322
|
+
.option('-p, --prepared-data <dir>', 'Directory containing prepared data', './prepared-data')
|
|
1323
|
+
.option('--sessions', 'Show per-session breakdown')
|
|
1324
|
+
.option('--top <n>', 'Show top N largest questions in aggregate view', parseInt)
|
|
1325
|
+
.option('--observations-only', 'Only count observation tokens from prepared data (fast)')
|
|
1326
|
+
.option('-c, --config <config>', 'Config to use for observations-only mode', 'observational-memory')
|
|
1327
|
+
.action(async options => {
|
|
1328
|
+
try {
|
|
1329
|
+
const tokensCommand = new TokensCommand();
|
|
1330
|
+
await tokensCommand.run({
|
|
1331
|
+
dataset: options.dataset,
|
|
1332
|
+
questionId: options.questionId,
|
|
1333
|
+
offset: options.offset,
|
|
1334
|
+
subset: options.subset,
|
|
1335
|
+
preparedDataDir: options.preparedData,
|
|
1336
|
+
showSessions: options.sessions,
|
|
1337
|
+
topN: options.top,
|
|
1338
|
+
observationsOnly: options.observationsOnly,
|
|
1339
|
+
config: options.config,
|
|
1340
|
+
});
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1343
|
+
process.exit(1);
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
program
|
|
1348
|
+
.command('precompute-embeddings')
|
|
1349
|
+
.description('Precompute embeddings for RAG-based observation filtering')
|
|
1350
|
+
.requiredOption('-d, --dataset <dataset>', 'Dataset to use (longmemeval_s, longmemeval_m, longmemeval_oracle)')
|
|
1351
|
+
.requiredOption('-c, --memory-config <config>', 'Memory configuration to use')
|
|
1352
|
+
.option('-o, --offset <n>', 'Skip first n questions', parseInt)
|
|
1353
|
+
.option('-s, --subset <n>', 'Process only n questions (after offset)', parseInt)
|
|
1354
|
+
.option('-p, --prepared-data <dir>', 'Directory containing prepared data', './prepared-data')
|
|
1355
|
+
.option('-b, --batch-size <n>', 'Batch size for embedding (default: 100)', parseInt)
|
|
1356
|
+
.option('--cooldown <ms>', 'Cooldown in ms between questions (default: 1000)', parseInt)
|
|
1357
|
+
.action(async options => {
|
|
1358
|
+
try {
|
|
1359
|
+
const command = new PrecomputeEmbeddingsCommand();
|
|
1360
|
+
await command.run({
|
|
1361
|
+
dataset: options.dataset,
|
|
1362
|
+
memoryConfig: options.memoryConfig,
|
|
1363
|
+
preparedDataDir: options.preparedData,
|
|
1364
|
+
offset: options.offset,
|
|
1365
|
+
subset: options.subset,
|
|
1366
|
+
batchSize: options.batchSize,
|
|
1367
|
+
cooldown: options.cooldown,
|
|
1368
|
+
});
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1371
|
+
process.exit(1);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
program
|
|
1376
|
+
.command('find-prohibited')
|
|
1377
|
+
.description('Find messages that trigger Gemini PROHIBITED_CONTENT filter using binary search')
|
|
1378
|
+
.requiredOption('-d, --dataset <dataset>', 'Dataset to use (longmemeval_s, longmemeval_m, longmemeval_oracle)')
|
|
1379
|
+
.requiredOption('-q, --question-id <id>', 'Question ID to analyze')
|
|
1380
|
+
.action(async options => {
|
|
1381
|
+
try {
|
|
1382
|
+
const command = new FindProhibitedCommand();
|
|
1383
|
+
await command.run({
|
|
1384
|
+
dataset: options.dataset,
|
|
1385
|
+
questionId: options.questionId,
|
|
1386
|
+
});
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
program
|
|
1394
|
+
.command('partial [config]')
|
|
1395
|
+
.description('Check partial or complete benchmark results')
|
|
1396
|
+
.option('-o, --output <dir>', 'Results directory', './results')
|
|
1397
|
+
.option('--run-id <id>', 'Specific run ID to check')
|
|
1398
|
+
.action(async (config, options) => {
|
|
1399
|
+
try {
|
|
1400
|
+
const command = new PartialResultsCommand();
|
|
1401
|
+
await command.run({
|
|
1402
|
+
config,
|
|
1403
|
+
runId: options.runId,
|
|
1404
|
+
outputDir: options.output,
|
|
1405
|
+
});
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1408
|
+
process.exit(1);
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
// Investigate command
|
|
1413
|
+
import { InvestigateCommand } from './commands/investigate';
|
|
1414
|
+
|
|
1415
|
+
program
|
|
1416
|
+
.command('investigate [run-id]')
|
|
1417
|
+
.description('Setup and manage investigation of failed questions')
|
|
1418
|
+
.option('-l, --list', 'List all runs with failures')
|
|
1419
|
+
.option('-c, --config <config>', 'Filter by config name (used with --list)')
|
|
1420
|
+
.option('--status', 'Show investigation status')
|
|
1421
|
+
.option('--next', 'Open next uninvestigated question')
|
|
1422
|
+
.option('--done <question-id>', 'Mark a question as investigated')
|
|
1423
|
+
.option('--fixed <question-id>', 'Mark a question as fix-implemented')
|
|
1424
|
+
.option('--sync', 'Sync fixes to longmemeval_s.json')
|
|
1425
|
+
.option('-o, --output <dir>', 'Investigation output directory', './investigations')
|
|
1426
|
+
.option('-r, --results <dir>', 'Results directory', './results')
|
|
1427
|
+
.option('-p, --prepared-data <dir>', 'Prepared data directory', './prepared-data')
|
|
1428
|
+
.option('--data <dir>', 'Dataset directory', './data')
|
|
1429
|
+
.option('--editor <cmd>', 'Editor command to open files', process.env.EDITOR || 'code')
|
|
1430
|
+
.option('--inspect <question-id>', "Inspect a question's data")
|
|
1431
|
+
.option('--search <keyword>', 'Search observations for a keyword')
|
|
1432
|
+
.option('--trace <keyword>', 'Trace information flow for a keyword')
|
|
1433
|
+
.option('-q, --question-id <id>', 'Question ID for search/trace/date')
|
|
1434
|
+
.option('--date <date>', 'View observations around a date (e.g., "2023/05/29", "May 29")')
|
|
1435
|
+
.option('--context <days>', 'Days of context around --date (default: 1)', '1')
|
|
1436
|
+
.option('--session <idx>', 'View a specific session from original dataset')
|
|
1437
|
+
.option('--list-sessions', 'List all sessions with dates for a question')
|
|
1438
|
+
.option('--check-stale', 'Check if prepared data is stale (pre-cursor-fix)')
|
|
1439
|
+
.option('--stale-only', 'Only list stale questions (use with --check-stale)')
|
|
1440
|
+
.option('--check-duplicates', 'Check for duplicate thread blocks in observations')
|
|
1441
|
+
.option('--baseline', 'Run comprehensive data quality check for a question')
|
|
1442
|
+
.option('--search-original <keyword>', 'Search original dataset for a keyword (shows full context)')
|
|
1443
|
+
.option('--improve <question-id>', 'Add improvements to a question in the dataset')
|
|
1444
|
+
.option('--improve-question <text>', 'Improved question text (use with --improve)')
|
|
1445
|
+
.option('--improve-answer <text>', 'Improved answer text (use with --improve)')
|
|
1446
|
+
.option('--improve-note <text>', 'Improvement note (use with --improve)')
|
|
1447
|
+
.option(
|
|
1448
|
+
'--category <category>',
|
|
1449
|
+
'Failure category: observer-miss, reflector-loss, agent-reasoning, dataset-error, data-freshness, knowledge-update, rag-miss, other',
|
|
1450
|
+
)
|
|
1451
|
+
.option('--clear-improved [fields]', 'Clear improved fields: all, question, answer, note, category (comma-separated)')
|
|
1452
|
+
.option('--prepare-stale', '(deprecated) Use --print-prepare-command instead')
|
|
1453
|
+
.option('--print-prepare-command', 'Print a prepare command for stale/partial questions')
|
|
1454
|
+
.option('--dry-run', 'Show what would be prepared without actually doing it')
|
|
1455
|
+
.action(async (runId, options) => {
|
|
1456
|
+
try {
|
|
1457
|
+
const command = new InvestigateCommand({
|
|
1458
|
+
outputDir: options.output,
|
|
1459
|
+
resultsDir: options.results,
|
|
1460
|
+
preparedDataDir: options.preparedData,
|
|
1461
|
+
datasetDir: options.data,
|
|
1462
|
+
editor: options.editor,
|
|
1463
|
+
});
|
|
1464
|
+
await command.run({
|
|
1465
|
+
runId,
|
|
1466
|
+
list: options.list,
|
|
1467
|
+
config: options.config,
|
|
1468
|
+
status: options.status,
|
|
1469
|
+
next: options.next,
|
|
1470
|
+
done: options.done,
|
|
1471
|
+
fixed: options.fixed,
|
|
1472
|
+
sync: options.sync,
|
|
1473
|
+
inspect: options.inspect,
|
|
1474
|
+
search: options.search,
|
|
1475
|
+
trace: options.trace,
|
|
1476
|
+
questionId: options.questionId,
|
|
1477
|
+
date: options.date,
|
|
1478
|
+
context: options.context ? parseInt(options.context, 10) : 1,
|
|
1479
|
+
session: options.session !== undefined ? parseInt(options.session, 10) : undefined,
|
|
1480
|
+
listSessions: options.listSessions,
|
|
1481
|
+
checkStale: options.checkStale,
|
|
1482
|
+
staleOnly: options.staleOnly,
|
|
1483
|
+
checkDuplicates: options.checkDuplicates,
|
|
1484
|
+
baseline: options.baseline,
|
|
1485
|
+
searchOriginal: options.searchOriginal,
|
|
1486
|
+
improve: options.improve,
|
|
1487
|
+
improveQuestion: options.improveQuestion,
|
|
1488
|
+
improveAnswer: options.improveAnswer,
|
|
1489
|
+
improveNote: options.improveNote,
|
|
1490
|
+
category: options.category,
|
|
1491
|
+
clearImproved: options.clearImproved,
|
|
1492
|
+
prepareStale: options.prepareStale,
|
|
1493
|
+
printPrepareCommand: options.printPrepareCommand,
|
|
1494
|
+
dryRun: options.dryRun,
|
|
1495
|
+
});
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
1498
|
+
process.exit(1);
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
|
|
690
1502
|
program.parse();
|