Spaces:
Sleeping
Sleeping
File size: 7,378 Bytes
e5e36db | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | """
run_evaluation.py
=================
Runs inference.py once per scenario across all difficulties and aggregates results.
Usage:
python run_evaluation.py
Environment variables forwarded to each inference.py subprocess:
API_BASE_URL, MODEL_NAME, HF_TOKEN, OPENAI_API_KEY, MAX_STEPS
Each subprocess is run as:
DIFFICULTY=<d> SCENARIO_ID=<id> python inference.py
"""
from __future__ import annotations
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Optional
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
DIFFICULTIES = ["easy", "medium", "hard"]
TASKS_DIR = Path(__file__).parent / "tasks"
PYTHON = sys.executable # same interpreter that runs this script
INFERENCE_SCRIPT = Path(__file__).parent / "inference.py"
TIMEOUT = 180 # seconds per episode (3 min ceiling)
# Env vars to forward to subprocesses (all LLM + infra config)
FORWARD_VARS = [
"API_BASE_URL", "MODEL_NAME",
"HF_TOKEN", "OPENAI_API_KEY",
"MAX_STEPS", "REDIS_URL",
]
# ---------------------------------------------------------------------------
# Load scenarios
# ---------------------------------------------------------------------------
def load_scenario_ids(difficulty: str) -> list[str]:
path = TASKS_DIR / difficulty / "scenarios.json"
with open(path, "r", encoding="utf-8") as f:
scenarios = json.load(f)
return [s["id"] for s in scenarios]
# ---------------------------------------------------------------------------
# Parse [END] line
# ---------------------------------------------------------------------------
_END_RE = re.compile(
r"\[END\]\s+"
r"success=(?P<success>\w+)\s+"
r"steps=(?P<steps>\d+)\s+"
r"score=(?P<score>[\d.]+)"
)
def parse_end_line(output: str) -> Optional[dict]:
"""Extract score/steps/success from [END] line in subprocess stdout."""
for line in output.splitlines():
m = _END_RE.search(line)
if m:
return {
"score": float(m.group("score")),
"steps": int(m.group("steps")),
"success": m.group("success").lower() == "true",
}
return None
# ---------------------------------------------------------------------------
# Run one episode
# ---------------------------------------------------------------------------
def run_episode(difficulty: str, scenario_id: str) -> Optional[dict]:
"""
Invoke inference.py as a subprocess with DIFFICULTY and SCENARIO_ID set.
Returns parsed result dict or None on failure.
"""
print(f" Running {scenario_id}...", flush=True)
env = {**os.environ}
env["DIFFICULTY"] = difficulty
env["SCENARIO_ID"] = scenario_id
try:
proc = subprocess.run(
[PYTHON, str(INFERENCE_SCRIPT)],
env=env,
capture_output=True,
text=True,
timeout=TIMEOUT,
)
except subprocess.TimeoutExpired:
print(f" TIMEOUT after {TIMEOUT}s — skipping", flush=True)
return None
except Exception as exc:
print(f" ERROR launching subprocess: {exc}", flush=True)
return None
# Print [START]/[STEP]/[END] lines live (already captured, echo them)
for line in proc.stdout.splitlines():
if line.startswith(("[START]", "[STEP]", "[END]")):
print(f" {line}", flush=True)
if proc.returncode != 0:
print(f" FAILED (exit {proc.returncode})", flush=True)
if proc.stderr:
# Show last error line only to keep output clean
last_err = [l for l in proc.stderr.splitlines() if l.strip()]
if last_err:
print(f" stderr: {last_err[-1]}", flush=True)
return None
result = parse_end_line(proc.stdout)
if result is None:
print(f" WARNING: could not parse [END] line", flush=True)
return result
# ---------------------------------------------------------------------------
# Main evaluation loop
# ---------------------------------------------------------------------------
def run_evaluation() -> dict:
results: dict[str, list[dict]] = {d: [] for d in DIFFICULTIES}
for difficulty in DIFFICULTIES:
print(f"\n{'='*40}", flush=True)
print(f"Difficulty: {difficulty.upper()}", flush=True)
print(f"{'='*40}", flush=True)
ids = load_scenario_ids(difficulty)
for scenario_id in ids:
ep = run_episode(difficulty, scenario_id)
if ep is not None:
ep["scenario_id"] = scenario_id
results[difficulty].append(ep)
else:
results[difficulty].append({
"scenario_id": scenario_id,
"score": 0.0,
"steps": 0,
"success": False,
"error": True,
})
return results
# ---------------------------------------------------------------------------
# Metrics + summary
# ---------------------------------------------------------------------------
def compute_metrics(episodes: list[dict]) -> dict:
if not episodes:
return {"avg_score": 0.0, "avg_steps": 0.0, "success_rate": 0.0, "n": 0}
n = len(episodes)
avg_score = sum(e["score"] for e in episodes) / n
avg_steps = sum(e["steps"] for e in episodes) / n
success_rate = sum(1 for e in episodes if e["success"]) / n * 100
return {"avg_score": avg_score, "avg_steps": avg_steps, "success_rate": success_rate, "n": n}
def print_summary(results: dict[str, list[dict]]) -> None:
metrics = {d: compute_metrics(results[d]) for d in DIFFICULTIES}
print("\n" + "=" * 34)
print(" FINAL RESULTS")
print("=" * 34)
for difficulty in DIFFICULTIES:
m = metrics[difficulty]
print(f"\n{difficulty}:")
print(f" avg_score: {m['avg_score']:.2f}")
print(f" avg_steps: {m['avg_steps']:.1f}")
print(f" success_rate: {m['success_rate']:.0f}% ({sum(e['success'] for e in results[difficulty])}/{m['n']})")
# Difficulty tier validation
easy_s = metrics["easy"]["avg_score"]
medium_s = metrics["medium"]["avg_score"]
hard_s = metrics["hard"]["avg_score"]
print()
if easy_s > medium_s > hard_s:
print("Difficulty progression validated ✓ (easy > medium > hard)")
else:
print(
"WARNING: Difficulty tiers not well separated "
f"(easy={easy_s:.3f}, medium={medium_s:.3f}, hard={hard_s:.3f})"
)
print()
# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------
if __name__ == "__main__":
print("Starting evaluation — 15 episodes across easy / medium / hard")
print(f"Model : {os.getenv('MODEL_NAME', 'gpt-4o')}")
print(f"API : {os.getenv('API_BASE_URL', 'https://api.openai.com/v1')}")
print(f"Timeout per episode: {TIMEOUT}s")
results = run_evaluation()
print_summary(results)
|