| |
| """Summarize a verifiers traces.jsonl run without hiding errored episodes. |
| |
| The scheduled-task score is the conservative primary estimate. A second score over |
| reward-bearing traces helps diagnose transient infrastructure failures, but is never |
| labelled as the run's result. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import math |
| import tomllib |
| from collections import Counter |
| from pathlib import Path |
|
|
|
|
| def wilson(successes: int, total: int, z: float = 1.959963984540054) -> tuple[float, float]: |
| if total == 0: |
| return 0.0, 0.0 |
| p = successes / total |
| denominator = 1.0 + z * z / total |
| center = (p + z * z / (2 * total)) / denominator |
| radius = z * math.sqrt(p * (1 - p) / total + z * z / (4 * total * total)) / denominator |
| return max(0.0, center - radius), min(1.0, center + radius) |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser() |
| parser.add_argument("run", type=Path) |
| args = parser.parse_args() |
| run_dir = args.run if args.run.is_dir() else args.run.parent |
| path = run_dir / "traces.jsonl" if args.run.is_dir() else args.run |
|
|
| expected = None |
| config_path = run_dir / "config.toml" |
| if config_path.is_file(): |
| config = tomllib.loads(config_path.read_text()) |
| num_tasks = config.get("num_tasks") |
| num_rollouts = config.get("num_rollouts", 1) |
| if isinstance(num_tasks, int) and isinstance(num_rollouts, int): |
| expected = num_tasks * num_rollouts |
|
|
| records: list[dict] = [] |
| traces: list[dict] = [] |
| for line in path.read_text().splitlines(): |
| record = json.loads(line) |
| records.append(record) |
| traces.extend(record.get("traces", [])) |
|
|
| def has_reward(trace: dict) -> bool: |
| return isinstance((trace.get("rewards") or {}).get("solved"), dict) |
|
|
| def solved(trace: dict) -> bool: |
| return float(((trace.get("rewards") or {}).get("solved") or {}).get("score", 0)) > 0 |
|
|
| clean = [ |
| trace |
| for trace in traces |
| if trace.get("is_completed") and trace.get("ok") and not trace.get("errors") |
| ] |
| scored = [trace for trace in traces if has_reward(trace)] |
| errored = [trace for trace in traces if trace.get("errors") or not trace.get("ok")] |
| successes = sum(solved(trace) for trace in traces) |
| scheduled = max(len(traces), expected or 0) |
| scheduled_low, scheduled_high = wilson(successes, scheduled) |
| scored_low, scored_high = wilson(successes, len(scored)) |
| stop_counts = Counter(t.get("stop_condition", "unknown") for t in traces) |
| prompt_tokens = 0 |
| completion_tokens = 0 |
| model_calls = 0 |
| completion_tokens_per_call: list[int] = [] |
| calls_per_episode: list[int] = [] |
| for trace in traces: |
| calls_per_episode.append(len(trace.get("calls", []))) |
| for call in trace.get("calls", []): |
| usage = call.get("usage") or {} |
| prompt_tokens += int(usage.get("prompt_tokens", 0)) |
| call_completion = int(usage.get("completion_tokens", 0)) |
| completion_tokens += call_completion |
| completion_tokens_per_call.append(call_completion) |
| model_calls += 1 |
|
|
| def percentile(values: list[int], quantile: float) -> int | None: |
| if not values: |
| return None |
| ordered = sorted(values) |
| return ordered[round((len(ordered) - 1) * quantile)] |
|
|
| result = { |
| "trace_file": str(path), |
| "scheduled_episodes": scheduled, |
| "observed_trace_episodes": len(traces), |
| "missing_trace_episodes": scheduled - len(traces), |
| "clean_completed_episodes": len(clean), |
| "reward_bearing_episodes": len(scored), |
| "errored_or_unscored_episodes": scheduled - len(scored), |
| "successes": successes, |
| "score_scheduled": successes / scheduled if scheduled else None, |
| "ci95_wilson_scheduled": [scheduled_low, scheduled_high] if scheduled else None, |
| "diagnostic_score_reward_bearing": successes / len(scored) if scored else None, |
| "diagnostic_ci95_reward_bearing": [scored_low, scored_high] if scored else None, |
| "stop_conditions": dict(sorted(stop_counts.items())), |
| "model_calls": model_calls, |
| "calls_per_episode_mean": sum(calls_per_episode) / len(calls_per_episode) |
| if calls_per_episode |
| else None, |
| "prompt_tokens": prompt_tokens, |
| "completion_tokens": completion_tokens, |
| "completion_tokens_per_call_mean": completion_tokens / model_calls |
| if model_calls |
| else None, |
| "completion_tokens_per_call_p50": percentile(completion_tokens_per_call, 0.50), |
| "completion_tokens_per_call_p95": percentile(completion_tokens_per_call, 0.95), |
| "calls_hitting_4096_token_cap": sum( |
| value >= 4096 for value in completion_tokens_per_call |
| ), |
| "error_types": dict( |
| Counter( |
| str(error.get("type") or error.get("name") or "unknown") |
| for trace in errored |
| for error in (trace.get("errors") or []) |
| ) |
| ), |
| "episode_retry_or_env_error_types": dict( |
| Counter( |
| str(error.get("type") or error.get("name") or "unknown") |
| for record in records |
| for error in (record.get("errors") or []) |
| ) |
| ), |
| } |
| print(json.dumps(result, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|