|
3 | 3 | CLI for running RLM benchmarks. |
4 | 4 |
|
5 | 5 | Usage: |
6 | | - # Run benchmarks |
7 | | - python -m benchmarks.cli run --benchmark oolong --num-samples 10 |
| 6 | + # Run benchmarks with model spec (backend:model format) |
| 7 | + python -m benchmarks.cli run --benchmark niah --models openai:gpt-4o |
| 8 | + python -m benchmarks.cli run --benchmark niah --models openai:gpt-4o anthropic:claude-sonnet-4-20250514 |
| 9 | +
|
| 10 | + # Run with methods comparison |
8 | 11 | python -m benchmarks.cli run --benchmark niah --methods rlm direct |
9 | 12 |
|
10 | 13 | # Query stored results |
11 | 14 | python -m benchmarks.cli history --benchmark oolong-toy_dnd --limit 10 |
12 | 15 | python -m benchmarks.cli compare --benchmark niah-100k --group-by method |
13 | | -
|
14 | | - # Legacy (no subcommand defaults to 'run') |
15 | | - python -m benchmarks.cli --benchmark oolong --num-samples 10 |
16 | 16 | """ |
17 | 17 |
|
18 | 18 | import argparse |
19 | 19 | import json |
20 | 20 | import sys |
| 21 | +from dataclasses import dataclass |
21 | 22 | from datetime import datetime |
22 | 23 |
|
23 | 24 | from benchmarks.base import BenchmarkResult |
|
28 | 29 | from benchmarks.tasks.oolong import OolongBenchmark, OolongPairsBenchmark |
29 | 30 |
|
30 | 31 |
|
| 32 | +@dataclass |
| 33 | +class ModelSpec: |
| 34 | + """Parsed model specification.""" |
| 35 | + |
| 36 | + backend: str |
| 37 | + model: str |
| 38 | + |
| 39 | + def __str__(self) -> str: |
| 40 | + return f"{self.backend}:{self.model}" |
| 41 | + |
| 42 | + |
| 43 | +def parse_model_spec(spec: str) -> ModelSpec: |
| 44 | + """Parse a backend:model specification string. |
| 45 | +
|
| 46 | + Formats supported: |
| 47 | + - "backend:model" -> ModelSpec(backend, model) |
| 48 | + - "model" -> ModelSpec("openai", model) # default backend |
| 49 | +
|
| 50 | + Examples: |
| 51 | + - "openai:gpt-4o" -> ModelSpec("openai", "gpt-4o") |
| 52 | + - "anthropic:claude-sonnet-4-20250514" -> ModelSpec("anthropic", "claude-sonnet-4-20250514") |
| 53 | + - "gpt-4o" -> ModelSpec("openai", "gpt-4o") |
| 54 | + """ |
| 55 | + if ":" in spec: |
| 56 | + parts = spec.split(":", 1) |
| 57 | + return ModelSpec(backend=parts[0], model=parts[1]) |
| 58 | + else: |
| 59 | + # Default to openai backend |
| 60 | + return ModelSpec(backend="openai", model=spec) |
| 61 | + |
| 62 | + |
31 | 63 | def get_benchmark(args: argparse.Namespace): |
32 | 64 | """Instantiate benchmark from CLI arguments.""" |
33 | 65 | name = args.benchmark.lower() |
@@ -278,17 +310,25 @@ def main(): |
278 | 310 | default=None, |
279 | 311 | help="Output file path for results (JSON)", |
280 | 312 | ) |
| 313 | + run_parser.add_argument( |
| 314 | + "--models", |
| 315 | + nargs="+", |
| 316 | + default=None, |
| 317 | + help="Model specs as backend:model (e.g., openai:gpt-4o anthropic:claude-sonnet-4-20250514). " |
| 318 | + "Can specify multiple for comparison. Default: openai/gpt-4o-mini", |
| 319 | + ) |
| 320 | + # Legacy args for backwards compatibility |
281 | 321 | run_parser.add_argument( |
282 | 322 | "--backend", |
283 | 323 | type=str, |
284 | | - default="openai", |
285 | | - help="LLM backend (default: openai)", |
| 324 | + default=None, |
| 325 | + help="(Legacy) LLM backend. Prefer --models backend:model syntax.", |
286 | 326 | ) |
287 | 327 | run_parser.add_argument( |
288 | 328 | "--model", |
289 | 329 | type=str, |
290 | | - default="gpt-5-mini", |
291 | | - help="Model name (default: gpt-5-mini)", |
| 330 | + default=None, |
| 331 | + help="(Legacy) Model name. Prefer --models backend:model syntax.", |
292 | 332 | ) |
293 | 333 | run_parser.add_argument( |
294 | 334 | "--environment", |
@@ -389,62 +429,81 @@ def main(): |
389 | 429 | return 1 |
390 | 430 |
|
391 | 431 |
|
| 432 | +def get_model_specs(args: argparse.Namespace) -> list[ModelSpec]: |
| 433 | + """Get model specifications from args, handling legacy and new formats.""" |
| 434 | + if args.models: |
| 435 | + # New format: --models backend:model [backend:model ...] |
| 436 | + return [parse_model_spec(spec) for spec in args.models] |
| 437 | + elif args.backend or args.model: |
| 438 | + # Legacy format: --backend X --model Y |
| 439 | + backend = args.backend or "openai" |
| 440 | + model = args.model or "gpt-4o-mini" |
| 441 | + return [ModelSpec(backend=backend, model=model)] |
| 442 | + else: |
| 443 | + # Default |
| 444 | + return [ModelSpec(backend="openai", model="gpt-4o-mini")] |
| 445 | + |
| 446 | + |
392 | 447 | def cmd_run(args: argparse.Namespace) -> int: |
393 | 448 | """Run benchmarks (main command).""" |
394 | | - runner = BenchmarkRunner( |
395 | | - backend=args.backend, |
396 | | - model=args.model, |
397 | | - environment=args.environment, |
398 | | - max_iterations=args.max_iterations, |
399 | | - verbose=args.verbose, |
400 | | - log_dir=args.log_dir, |
401 | | - max_workers=args.max_workers, |
402 | | - progress=args.progress, |
403 | | - ) |
| 449 | + model_specs = get_model_specs(args) |
404 | 450 |
|
405 | 451 | if args.benchmark == "all": |
406 | 452 | benchmarks = get_all_benchmarks(args) |
407 | 453 | else: |
408 | 454 | benchmarks = [get_benchmark(args)] |
409 | 455 |
|
410 | 456 | all_results = {} |
| 457 | + store = ResultsStore(args.results_dir) |
411 | 458 |
|
412 | | - for benchmark in benchmarks: |
413 | | - print(f"\n{'=' * 60}") |
414 | | - print(f"Running: {benchmark.name}") |
415 | | - print(f"Description: {benchmark.description}") |
416 | | - print(f"{'=' * 60}") |
417 | | - |
418 | | - results = compare_methods( |
419 | | - benchmark=benchmark, |
420 | | - runner=runner, |
421 | | - methods=args.methods, |
422 | | - num_samples=args.num_samples, |
423 | | - seed=args.seed, |
| 459 | + for model_spec in model_specs: |
| 460 | + print(f"\n{'=' * 70}") |
| 461 | + print(f"Model: {model_spec}") |
| 462 | + print(f"{'=' * 70}") |
| 463 | + |
| 464 | + runner = BenchmarkRunner( |
| 465 | + backend=model_spec.backend, |
| 466 | + model=model_spec.model, |
| 467 | + environment=args.environment, |
| 468 | + max_iterations=args.max_iterations, |
| 469 | + verbose=args.verbose, |
| 470 | + log_dir=args.log_dir, |
| 471 | + max_workers=args.max_workers, |
| 472 | + progress=args.progress, |
424 | 473 | ) |
425 | 474 |
|
426 | | - for method, result in results.items(): |
427 | | - key = f"{benchmark.name}/{method}" |
428 | | - all_results[key] = result |
| 475 | + for benchmark in benchmarks: |
| 476 | + print(f"\nRunning: {benchmark.name}") |
| 477 | + print(f"Description: {benchmark.description}") |
| 478 | + |
| 479 | + results = compare_methods( |
| 480 | + benchmark=benchmark, |
| 481 | + runner=runner, |
| 482 | + methods=args.methods, |
| 483 | + num_samples=args.num_samples, |
| 484 | + seed=args.seed, |
| 485 | + ) |
| 486 | + |
| 487 | + for method, result in results.items(): |
| 488 | + key = f"{model_spec}/{benchmark.name}/{method}" |
| 489 | + all_results[key] = result |
| 490 | + |
| 491 | + # Save to results store |
| 492 | + config = ExperimentConfig( |
| 493 | + backend=model_spec.backend, |
| 494 | + model=model_spec.model, |
| 495 | + environment=args.environment, |
| 496 | + max_iterations=args.max_iterations, |
| 497 | + num_samples=args.num_samples, |
| 498 | + seed=args.seed, |
| 499 | + method=method, |
| 500 | + ) |
| 501 | + exp_id = store.save(result, config) |
| 502 | + print(f" Saved as experiment {exp_id}") |
429 | 503 |
|
430 | 504 | # Print summary |
431 | 505 | print_summary(all_results) |
432 | 506 |
|
433 | | - # Save to results store |
434 | | - store = ResultsStore(args.results_dir) |
435 | | - config = ExperimentConfig( |
436 | | - backend=args.backend, |
437 | | - model=args.model, |
438 | | - environment=args.environment, |
439 | | - max_iterations=args.max_iterations, |
440 | | - num_samples=args.num_samples, |
441 | | - seed=args.seed, |
442 | | - ) |
443 | | - |
444 | | - for key, result in all_results.items(): |
445 | | - exp_id = store.save(result, config) |
446 | | - print(f"Saved {key} as experiment {exp_id}") |
447 | | - |
448 | 507 | if args.output: |
449 | 508 | save_results(all_results, args.output) |
450 | 509 |
|
|
0 commit comments