Run rubric-based evaluation over completed writing results. Args: runs_dir: Directory containing run result JSON files. benchmark_path: Path to benchmark_all.jsonl. eval_dir: Directory to save evaluation results. judge_model: Model to use for judging. for
(
runs_dir: str,
benchmark_path: str,
eval_dir: str = "evals",
judge_model: str = "claude-sonnet-4-5",
force: bool = False,
max_parallel: int = 1,
)
| 167 | |
| 168 | |
| 169 | def evaluate( |
| 170 | runs_dir: str, |
| 171 | benchmark_path: str, |
| 172 | eval_dir: str = "evals", |
| 173 | judge_model: str = "claude-sonnet-4-5", |
| 174 | force: bool = False, |
| 175 | max_parallel: int = 1, |
| 176 | ) -> dict: |
| 177 | """Run rubric-based evaluation over completed writing results. |
| 178 | |
| 179 | Args: |
| 180 | runs_dir: Directory containing run result JSON files. |
| 181 | benchmark_path: Path to benchmark_all.jsonl. |
| 182 | eval_dir: Directory to save evaluation results. |
| 183 | judge_model: Model to use for judging. |
| 184 | force: If True, re-evaluate even if cached results exist. |
| 185 | max_parallel: Max parallel evaluation threads (default: 1, sequential). |
| 186 | """ |
| 187 | runs_path = Path(runs_dir) |
| 188 | output_dir = Path(eval_dir) |
| 189 | output_dir.mkdir(parents=True, exist_ok=True) |
| 190 | |
| 191 | benchmark = load_benchmark_queries(Path(benchmark_path)) |
| 192 | |
| 193 | # Support both directory layouts: |
| 194 | # New: runs/{index}/result.json (per-instance directories) |
| 195 | # Old: runs/{index}.json (flat layout, backward compat) |
| 196 | json_files = sorted(runs_path.glob("*/result.json")) |
| 197 | if not json_files: |
| 198 | json_files = sorted(runs_path.glob("*.json")) |
| 199 | if not json_files: |
| 200 | print(f"No result JSON files in {runs_path}") |
| 201 | return {} |
| 202 | |
| 203 | client = LLMClient() |
| 204 | |
| 205 | # Build (json_path, eval_path) pairs |
| 206 | eval_tasks = [] |
| 207 | for json_path in json_files: |
| 208 | if json_path.name == "result.json": |
| 209 | eval_key = json_path.parent.name |
| 210 | else: |
| 211 | eval_key = json_path.stem |
| 212 | eval_path = output_dir / f"{eval_key}_eval.json" |
| 213 | eval_tasks.append((json_path, eval_path)) |
| 214 | |
| 215 | all_scores: List[dict] = [] |
| 216 | |
| 217 | if max_parallel > 1: |
| 218 | print( |
| 219 | f"Evaluating {len(eval_tasks)} results with max {max_parallel} parallel judges" |
| 220 | ) |
| 221 | with ThreadPoolExecutor(max_workers=max_parallel) as executor: |
| 222 | future_to_key = { |
| 223 | executor.submit( |
| 224 | _evaluate_single, |
| 225 | json_path, |
| 226 | eval_path, |
no test coverage detected