Spaces:
Runtime error
Runtime error
| # Grader - evaluates a completed episode and returns a score in [0.0, 1.0]. | |
| # Scoring formula: Base score is a weighted combination of: | |
| # - Resolution bonus (50 % weight) | |
| # - Efficiency bonus (30 % weight) - fewer steps = higher score | |
| # - Quality bonus (20 % weight) - average normalised reward | |
| # All components are clipped to [0, 1] before weighting. | |
| # Final score is also clipped to [0.0, 1.0]. | |
| from __future__ import annotations | |
| from typing import List, Dict, Any | |
| _RESOLUTION_WEIGHT = 0.50 | |
| _EFFICIENCY_WEIGHT = 0.30 | |
| _QUALITY_WEIGHT = 0.20 | |
| _MAX_STEPS_SIMPLE = 15 | |
| _MAX_STEPS_MULTI = 12 | |
| _MAX_STEPS_CRITICAL = 10 | |
| # Reference reward range for normalisation (empirically determined) | |
| _REWARD_MIN = -200.0 | |
| _REWARD_MAX = 200.0 | |
| def evaluate_episode( | |
| episode_logs: List[Dict[str, Any]], | |
| max_steps: int = _MAX_STEPS_SIMPLE, | |
| ) -> Dict[str, Any]: | |
| """ | |
| Parameters | |
| ---------- | |
| episode_logs : list of step dicts containing at minimum | |
| {"reward": float, "info": {"resolved": bool, ...}} | |
| max_steps : maximum allowed steps for this task (used in efficiency calc) | |
| Returns | |
| ------- | |
| dict with keys: score (float 0-1), resolved (bool), steps (int), | |
| total_reward (float), efficiency (float), quality (float) | |
| """ | |
| if not episode_logs: | |
| return {"score": 0.0, "resolved": False, "steps": 0, | |
| "total_reward": 0.0, "efficiency": 0.0, "quality": 0.0} | |
| steps = len(episode_logs) | |
| total_reward = sum(entry["reward"] for entry in episode_logs) | |
| resolved = any(entry["info"].get("resolved", False) for entry in episode_logs) | |
| # Component 1: resolution (binary, but partial credit for progress) | |
| if resolved: | |
| resolution_score = 1.0 | |
| else: | |
| max_progress = max( | |
| (entry["info"].get("progress", 0) for entry in episode_logs), | |
| default=0, | |
| ) | |
| # Guess fix sequence length ≈ 2 for all tasks | |
| resolution_score = min(max_progress / 2.0, 0.49) | |
| # Component 2: efficiency (resolved faster → higher score) | |
| if resolved: | |
| efficiency_score = max(0.0, 1.0 - (steps - 1) / max(max_steps - 1, 1)) | |
| else: | |
| efficiency_score = 0.0 | |
| # Component 3: quality (normalised average reward) | |
| avg_reward = total_reward / steps | |
| norm_reward = (avg_reward - _REWARD_MIN) / (_REWARD_MAX - _REWARD_MIN) | |
| quality_score = max(0.0, min(norm_reward, 1.0)) | |
| # Weighted combination | |
| score = ( | |
| _RESOLUTION_WEIGHT * resolution_score + | |
| _EFFICIENCY_WEIGHT * efficiency_score + | |
| _QUALITY_WEIGHT * quality_score | |
| ) | |
| score = round(max(0.0, min(score, 1.0)), 4) | |
| return { | |
| "score": score, | |
| "resolved": resolved, | |
| "steps": steps, | |
| "total_reward": round(total_reward, 3), | |
| "efficiency": round(efficiency_score, 4), | |
| "quality": round(quality_score, 4), | |
| } | |
| if __name__ == "__main__": | |
| import sys | |
| import importlib | |
| task_configs = [ | |
| ("simple", "tasks.task_simple", _MAX_STEPS_SIMPLE), | |
| ("multi_service", "tasks.task_multi_service", _MAX_STEPS_MULTI), | |
| ("critical", "tasks.task_critical", _MAX_STEPS_CRITICAL), | |
| ] | |
| from agent.baseline import act | |
| all_passed = True | |
| for task_name, module_path, max_steps in task_configs: | |
| mod = importlib.import_module(module_path) | |
| env = mod.create_env() | |
| state = env.reset() | |
| logs = [] | |
| done = False | |
| while not done: | |
| action = act(state) | |
| next_state, reward, done, info = env.step(action) | |
| logs.append({"reward": reward, "info": info}) | |
| state = next_state | |
| result = evaluate_episode(logs, max_steps=max_steps) | |
| ok = 0.0 <= result["score"] <= 1.0 | |
| all_passed = all_passed and ok | |
| status = "PASS" if ok else "FAIL" | |
| print( | |
| f"[GRADER] task={task_name:<15} score={result['score']:.4f} " | |
| f"resolved={str(result['resolved']).lower():<5} " | |
| f"steps={result['steps']:<3} status={status}" | |
| ) | |
| sys.exit(0 if all_passed else 1) |