@@ -2617,6 +2617,8 @@ def create_parser():
26172617 eval_parser .add_argument ('-o' , '--output' , help = 'Output file for results (JSON)' )
26182618 eval_parser .add_argument ('--difficulty' , choices = ['easy' , 'medium' , 'hard' ],
26192619 help = 'Filter test cases by difficulty' )
2620+ eval_parser .add_argument ('--max-cases' , type = int , default = None ,
2621+ help = 'Only run the first N cases (quick subsample)' )
26202622 eval_parser .add_argument ('--no-animation' , action = 'store_true' , default = argparse .SUPPRESS ,
26212623 help = 'Disable the live progress bar (plain output)' )
26222624
@@ -2628,11 +2630,18 @@ def create_parser():
26282630 'for a bare id (e.g. '
26292631 'groq:llama-3.1-8b-instant,gpt-5-nano).' )
26302632 compare_parser .add_argument ('--suite' , required = True ,
2631- help = 'Test suite name' )
2633+ help = 'Built-in suite name (math, tool_use, '
2634+ 'reasoning, safety, conversation) OR a path '
2635+ 'to your own .jsonl/.json test cases' )
26322636 compare_parser .add_argument ('--scoring' , choices = ['exact_match' , 'contains' , 'regex' , 'semantic_similarity' , 'llm_judge' ],
26332637 default = 'contains' , help = 'Scoring mode (default: contains)' )
26342638 compare_parser .add_argument ('--threshold' , type = float , default = 0.5 ,
26352639 help = 'Pass threshold (default: 0.5)' )
2640+ compare_parser .add_argument ('--max-cases' , type = int , default = None ,
2641+ help = 'Only run the first N cases (quick bake-off '
2642+ 'on a big suite)' )
2643+ compare_parser .add_argument ('--difficulty' , choices = ['easy' , 'medium' , 'hard' ],
2644+ help = 'Filter test cases by difficulty' )
26362645 compare_parser .add_argument ('-o' , '--output' , help = 'Output file for results (JSON or Markdown)' )
26372646 compare_parser .add_argument ('--preset' , choices = _preset_choices ,
26382647 help = 'Use a preset agent configuration' )
@@ -3419,9 +3428,50 @@ def _handle_batch_command(args, cli) -> int:
34193428 logging .getLogger (__name__ ).debug ("Batch agent close() failed" , exc_info = True )
34203429
34213430
3431+ def _resolve_eval_suite (suite_arg : str , difficulty = None , max_cases = None ):
3432+ """Resolve a ``--suite`` argument to a ``TestSuite``.
3433+
3434+ Accepts a built-in suite name **or** a path to a ``.jsonl`` / ``.json`` file
3435+ of your own test cases (each an object with ``query``/``expected_output`` and
3436+ optional ``difficulty``/``tags``), so a bake-off can run on your own data —
3437+ not just the bundled suites. Optionally filters by ``difficulty`` and trims to
3438+ the first ``max_cases``. Raises ``KeyError`` (with the list of valid names)
3439+ for an unknown name, or ``FileNotFoundError`` / ``ValueError`` for a bad file.
3440+ """
3441+ from effgen .eval import get_suite
3442+ from effgen .eval .suites import TestSuite
3443+
3444+ p = Path (suite_arg )
3445+ if p .suffix .lower () in (".jsonl" , ".json" ) or p .exists ():
3446+ if not p .exists ():
3447+ raise FileNotFoundError (f"Test-case file not found: { suite_arg } " )
3448+ from effgen .eval .evaluator import TestCase
3449+ raw = p .read_text (encoding = "utf-8" )
3450+ records = []
3451+ if p .suffix .lower () == ".json" :
3452+ data = json .loads (raw )
3453+ records = data if isinstance (data , list ) else [data ]
3454+ else :
3455+ records = [json .loads (line ) for line in raw .splitlines () if line .strip ()]
3456+ cases = [TestCase .from_dict (r ) for r in records ]
3457+ if not cases :
3458+ raise ValueError (f"No test cases found in { suite_arg } " )
3459+ suite = TestSuite (test_cases = cases )
3460+ suite .name = p .stem
3461+ else :
3462+ suite = get_suite (suite_arg )
3463+
3464+ if difficulty :
3465+ from effgen .eval .evaluator import Difficulty
3466+ suite .test_cases = suite .filter (difficulty = Difficulty (difficulty ))
3467+ if max_cases is not None and max_cases > 0 :
3468+ suite .test_cases = suite .test_cases [:max_cases ]
3469+ return suite
3470+
3471+
34223472def _handle_eval_command (args , cli ) -> int :
34233473 """Handle 'effgen eval' subcommand."""
3424- from effgen .eval import AgentEvaluator , RegressionTracker , get_suite , list_suites
3474+ from effgen .eval import AgentEvaluator , RegressionTracker , list_suites
34253475 from effgen .eval .evaluator import ScoringMode
34263476
34273477 suite_name = args .suite
@@ -3430,6 +3480,7 @@ def _handle_eval_command(args, cli) -> int:
34303480 scoring = ScoringMode (args .scoring )
34313481 threshold = args .threshold
34323482 difficulty = getattr (args , 'difficulty' , None )
3483+ max_cases = getattr (args , 'max_cases' , None )
34333484
34343485 try :
34353486 # List suites if requested
@@ -3439,13 +3490,13 @@ def _handle_eval_command(args, cli) -> int:
34393490 cli .print (f" { name :16s} — { desc } " )
34403491 return 0
34413492
3442- suite = get_suite (suite_name )
3493+ suite = _resolve_eval_suite (suite_name , difficulty = difficulty , max_cases = max_cases )
34433494
3444- # Filter by difficulty if specified
3495+ # Report any narrowing applied
34453496 if difficulty :
3446- from effgen .eval .evaluator import Difficulty
3447- suite .test_cases = suite .filter (difficulty = Difficulty (difficulty ))
34483497 cli .print (f"Filtered to { len (suite .test_cases )} { difficulty } test cases" )
3498+ if max_cases :
3499+ cli .print (f"Limited to first { len (suite .test_cases )} cases" )
34493500
34503501 cli .print (f"Loading model { model_name } ..." )
34513502
@@ -3533,22 +3584,29 @@ def _handle_eval_command(args, cli) -> int:
35333584
35343585def _handle_compare_command (args , cli ) -> int :
35353586 """Handle 'effgen compare' subcommand."""
3536- from effgen .eval import ModelComparison , get_suite
3587+ from effgen .eval import ModelComparison
35373588 from effgen .eval .evaluator import ScoringMode
35383589
35393590 model_names = [m .strip () for m in args .models .split (',' )]
35403591 suite_name = args .suite
35413592 scoring = ScoringMode (args .scoring )
35423593 threshold = args .threshold
35433594 preset_name = getattr (args , 'preset' , None )
3595+ difficulty = getattr (args , 'difficulty' , None )
3596+ max_cases = getattr (args , 'max_cases' , None )
35443597
35453598 # Unknown suite is a user error, not a crash — report cleanly (no traceback)
3546- # and exit 2 with the list of valid suites.
3599+ # and exit 2 with the list of valid suites. A bad data file is reported the
3600+ # same way.
35473601 try :
3548- suite = get_suite (suite_name )
3549- except (KeyError , ValueError ) :
3602+ suite = _resolve_eval_suite (suite_name , difficulty = difficulty , max_cases = max_cases )
3603+ except (KeyError , ValueError , FileNotFoundError ) as exc :
35503604 from effgen .eval import list_suites
3551- cli .print (f"Unknown suite '{ suite_name } '. Available: { ', ' .join (list_suites ())} ." )
3605+ cli .print (
3606+ f"Could not load suite '{ suite_name } ' ({ exc } ). "
3607+ f"Use a built-in suite ({ ', ' .join (list_suites ())} ) "
3608+ "or a path to a .jsonl/.json file of test cases."
3609+ )
35523610 return 2
35533611
35543612 agents : dict = {}
0 commit comments