Spaces:
Sleeping
Sleeping
fix(eval): mathematically bound all precision/recall metrics via laplace smoothing to naturally yield strict (0, 1) scores without artificial clipping
f8a6ce7 | """ | |
| evaluate.py – Reproducible baseline evaluation for SilentFailureDetector. | |
| Usage (CLI): | |
| python -m src.eval.evaluate --agent rule_based --data data/seed_dataset.jsonl | |
| Usage (import): | |
| from src.eval.evaluate import evaluate_rule_based | |
| result = evaluate_rule_based("data/seed_dataset.jsonl", task_name="easy") | |
| """ | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from typing import Literal | |
| from src.agents.rule_based_agent import RuleBasedAgent | |
| from src.env import SilentFailureDetectorEnv | |
| from src.grader import compute_confusion, compute_metrics, compute_reward | |
| from src.models import SilentFailureAction | |
| TaskName = Literal["easy", "medium", "hard"] | |
| def evaluate_rule_based( | |
| dataset_path: str | Path = "data/seed_dataset.jsonl", | |
| batch_size: int = 32, | |
| episodes: int = 5, | |
| task_name: TaskName = "easy", | |
| threshold: float = 0.4, | |
| ) -> dict: | |
| """Run the rule-based agent for `episodes` episodes and return averaged metrics. | |
| Returns: | |
| { | |
| "task": str, | |
| "episodes": int, | |
| "reward_total": float, # averaged grader score across episodes | |
| "confusion": dict, # summed confusion matrix | |
| "metrics": dict, # metrics computed from summed confusion | |
| } | |
| """ | |
| env = SilentFailureDetectorEnv( | |
| dataset_path=dataset_path, | |
| batch_size=batch_size, | |
| seed=42, | |
| ) | |
| env.set_task(task_name) | |
| agent = RuleBasedAgent(threshold=threshold) | |
| all_y_true: list[int] = [] | |
| all_y_pred: list[int] = [] | |
| episode_scores: list[float] = [] | |
| for ep in range(episodes): | |
| obs = env.reset(seed=42 + ep) | |
| done = False | |
| while not done: | |
| action_val = agent.act(obs) | |
| obs = env.step(SilentFailureAction(action=action_val)) | |
| done = obs.done | |
| result = env.grader_score() | |
| episode_scores.append(result.get("score", 0.0)) | |
| all_y_true.extend(env.y_true) | |
| all_y_pred.extend(env.y_pred) | |
| confusion = compute_confusion(all_y_true, all_y_pred) | |
| metrics = compute_metrics(confusion) | |
| avg_score = sum(episode_scores) / len(episode_scores) if episode_scores else 0.001 | |
| return { | |
| "task": task_name, | |
| "episodes": episodes, | |
| "reward_total": round(avg_score, 4), | |
| "confusion": confusion, | |
| "metrics": {k: round(v, 4) for k, v in metrics.items()}, | |
| } | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Evaluate SilentFailureDetector baseline") | |
| parser.add_argument("--agent", choices=["rule_based"], default="rule_based") | |
| parser.add_argument("--data", default="data/seed_dataset.jsonl") | |
| parser.add_argument("--episodes", type=int, default=5) | |
| parser.add_argument("--batch-size", type=int, default=32) | |
| parser.add_argument("--output", default="artifacts/baseline_metrics.json") | |
| args = parser.parse_args() | |
| print(f"Evaluating {args.agent} agent across all 3 tasks...") | |
| results: dict[str, dict] = {} | |
| for task in ("easy", "medium", "hard"): | |
| print(f"\n Task: {task.upper()}") | |
| r = evaluate_rule_based( | |
| dataset_path=args.data, | |
| batch_size=args.batch_size, | |
| episodes=args.episodes, | |
| task_name=task, # type: ignore[arg-type] | |
| ) | |
| results[task] = r | |
| m = r["metrics"] | |
| print( | |
| f" score={r['reward_total']:.4f} " | |
| f"recall={m['recall']:.2f} " | |
| f"specificity={m['specificity']:.2f} " | |
| f"f1={m['f1']:.2f}" | |
| ) | |
| # Save for dashboard | |
| out_path = Path(args.output) | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| # dashboard.py reads the "easy" task result by default | |
| easy_result = results["easy"] | |
| out_path.write_text( | |
| json.dumps( | |
| { | |
| "metrics": easy_result["metrics"], | |
| "reward_total": easy_result["reward_total"], | |
| "all_tasks": results, | |
| }, | |
| indent=2, | |
| ), | |
| encoding="utf-8", | |
| ) | |
| print(f"\nSaved to {out_path}") | |
| if __name__ == "__main__": | |
| main() |