Spaces:
Sleeping
Sleeping
| import json | |
| from pathlib import Path | |
| import sys | |
| REPO_ROOT = Path(__file__).resolve().parents[1] | |
| if str(REPO_ROOT) not in sys.path: | |
| sys.path.insert(0, str(REPO_ROOT)) | |
| from releaseops_arena.baselines import naive_baseline, rule_baseline | |
| from releaseops_arena.tool_env import ReleaseOpsToolEnv | |
| def load_jsonl(path: Path) -> list[dict]: | |
| if not path.exists(): | |
| return [] | |
| with open(path, "r", encoding="utf-8") as handle: | |
| return [json.loads(line) for line in handle if line.strip()] | |
| def summarize_rollout(env, reward, initial_budget): | |
| reason = env.state.get("terminal_reason") | |
| return { | |
| "reward": reward, | |
| "safe_ship": 1 if reason == "safe_ship" else 0, | |
| "unsafe_ship": 1 if reason == "unsafe_ship" else 0, | |
| "missed_deadline": 1 if reason == "missed_deadline" else 0, | |
| "false_blocks": env.metrics.get("false_blocks", 0), | |
| "true_blocks": env.metrics.get("true_blocks", 0), | |
| "invalid_actions": env.metrics.get("invalid_actions", 0), | |
| "budget_spent": initial_budget - env.state["review_budget_remaining"], | |
| } | |
| def aggregate(rows): | |
| if not rows: | |
| return { | |
| "avg_reward": 0.0, | |
| "safe_ship_rate": 0.0, | |
| "unsafe_ship_rate": 0.0, | |
| "missed_deadline_rate": 0.0, | |
| "avg_false_blocks": 0.0, | |
| "avg_true_blocks": 0.0, | |
| "avg_invalid_actions": 0.0, | |
| "avg_budget_spent": 0.0, | |
| } | |
| return { | |
| "avg_reward": round(sum(row["reward"] for row in rows) / len(rows), 3), | |
| "safe_ship_rate": round(sum(row["safe_ship"] for row in rows) / len(rows), 3), | |
| "unsafe_ship_rate": round(sum(row["unsafe_ship"] for row in rows) / len(rows), 3), | |
| "missed_deadline_rate": round(sum(row["missed_deadline"] for row in rows) / len(rows), 3), | |
| "avg_false_blocks": round(sum(row["false_blocks"] for row in rows) / len(rows), 3), | |
| "avg_true_blocks": round(sum(row["true_blocks"] for row in rows) / len(rows), 3), | |
| "avg_invalid_actions": round(sum(row["invalid_actions"] for row in rows) / len(rows), 3), | |
| "avg_budget_spent": round(sum(row["budget_spent"] for row in rows) / len(rows), 3), | |
| } | |
| def run_slice(rows: list[dict]): | |
| naive_rows = [] | |
| rule_rows = [] | |
| phase_aware_rule_rows = [] | |
| for kwargs in rows: | |
| env_naive = ReleaseOpsToolEnv() | |
| env_naive.reset(**kwargs) | |
| naive_initial_budget = env_naive.state["review_budget_remaining"] | |
| naive_reward = naive_baseline(env_naive) | |
| naive_rows.append(summarize_rollout(env_naive, naive_reward, naive_initial_budget)) | |
| env_rule = ReleaseOpsToolEnv() | |
| env_rule.reset(**kwargs) | |
| rule_initial_budget = env_rule.state["review_budget_remaining"] | |
| rule_reward = rule_baseline(env_rule) | |
| rule_rows.append(summarize_rollout(env_rule, rule_reward, rule_initial_budget)) | |
| # Imported here to keep baseline selection explicit in evaluation output. | |
| from releaseops_arena.baselines import phase_aware_rule_baseline | |
| env_phase = ReleaseOpsToolEnv() | |
| env_phase.reset(**kwargs) | |
| phase_initial_budget = env_phase.state["review_budget_remaining"] | |
| phase_reward = phase_aware_rule_baseline(env_phase) | |
| phase_aware_rule_rows.append( | |
| summarize_rollout(env_phase, phase_reward, phase_initial_budget) | |
| ) | |
| return { | |
| "count": len(rows), | |
| "naive": aggregate(naive_rows), | |
| "rule": aggregate(rule_rows), | |
| "phase_aware_rule": aggregate(phase_aware_rule_rows), | |
| } | |
| def load_eval_slices(): | |
| seen_path = Path("training/data/eval_seen.jsonl") | |
| unseen_path = Path("training/data/eval_unseen.jsonl") | |
| seen_rows = load_jsonl(seen_path) | |
| unseen_rows = load_jsonl(unseen_path) | |
| if seen_rows or unseen_rows: | |
| return seen_rows, unseen_rows | |
| # Backward-compatible fallback: split eval.jsonl by family. | |
| all_rows = load_jsonl(Path("training/data/eval.jsonl")) | |
| unseen_family = "release_manager_ship_before_evidence" | |
| seen_rows = [row for row in all_rows if row.get("family") != unseen_family] | |
| unseen_rows = [row for row in all_rows if row.get("family") == unseen_family] | |
| return seen_rows, unseen_rows | |
| def run_eval(): | |
| print("Evaluating baselines on seen and unseen slices...") | |
| seen_rows, unseen_rows = load_eval_slices() | |
| all_rows = seen_rows + unseen_rows | |
| seen_results = run_slice(seen_rows) | |
| unseen_results = run_slice(unseen_rows) | |
| overall_results = run_slice(all_rows) | |
| results = { | |
| "seen": seen_results, | |
| "unseen": unseen_results, | |
| "overall": overall_results, | |
| # Legacy keys retained for scripts that expect top-level aggregates. | |
| "naive": overall_results["naive"], | |
| "rule": overall_results["rule"], | |
| "naive_avg": overall_results["naive"]["avg_reward"], | |
| "rule_avg": overall_results["rule"]["avg_reward"], | |
| } | |
| print("Seen slice:") | |
| print(json.dumps(seen_results, indent=2)) | |
| print("Unseen slice:") | |
| print(json.dumps(unseen_results, indent=2)) | |
| print("Overall:") | |
| print(json.dumps(overall_results, indent=2)) | |
| output_path = Path("outputs/eval_results.json") | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| with open(output_path, "w", encoding="utf-8") as handle: | |
| json.dump(results, handle, indent=2) | |
| print(f"Wrote evaluation results to {output_path}") | |
| if __name__ == "__main__": | |
| run_eval() | |