#!/usr/bin/env python3 """Aggregate A/B results. Usage: report.py [model_short ...]""" import json import glob import os import statistics import sys BASE = os.environ.get("ABDEFER_RESULTS", os.path.join(os.path.dirname(os.path.abspath(__file__)), "results")) models = sys.argv[1:] or sorted( d for d in os.listdir(BASE) if os.path.isdir(os.path.join(BASE, d)) and d != "smoke") def load(model): recs = [] for p in glob.glob(f"{BASE}/{model}/*.json"): if p.endswith(".transcript.json"): continue with open(p, encoding="utf-8") as f: recs.append(json.load(f)) return recs def fmt(v, nd=1): return "-" if v is None else (f"{v:.{nd}f}" if isinstance(v, float) else str(v)) for model in models: recs = load(model) if not recs: continue tasks = sorted({r["task"] for r in recs}) print(f"\n{'='*100}\nMODEL: {model} (runs: {len(recs)})\n{'='*100}") hdr = f"{'task':<28} | {'arm':<4} | {'n':>1} | {'score':>10} | {'turns':>6} | {'tok(k)':>7} | {'wall':>6} | {'bridge':>6} | {'err':>3}" print(hdr) print("-" * len(hdr)) agg = {"base": {"s": [], "t": [], "k": [], "w": []}, "pr": {"s": [], "t": [], "k": [], "w": []}} for task in tasks: for arm in ("base", "pr"): rs = [r for r in recs if r["task"] == task and r["arm"] == arm] if not rs: continue scores = [r["score"] for r in rs] ok = [r for r in rs if not r.get("error")] turns = [r["api_turns"] for r in ok if r.get("api_turns")] toks = [r["total_tokens"] for r in ok if r.get("total_tokens")] walls = [r["wall_s"] for r in ok if r.get("wall_s")] bridges = [r.get("bridge_calls") or 0 for r in ok] nerr = sum(1 for r in rs if r.get("error")) smean = statistics.mean(scores) sspread = f"{smean:.2f} [{min(scores):.1f}-{max(scores):.1f}]" print(f"{task:<28} | {arm:<4} | {len(rs)} | {sspread:>10} | " f"{fmt(statistics.mean(turns) if turns else None):>6} | " f"{fmt(statistics.mean(toks)/1000 if toks else None):>7} | " f"{fmt(statistics.mean(walls) if walls else None):>6} | " f"{fmt(statistics.mean(bridges) if bridges else None):>6} | {nerr:>3}") agg[arm]["s"].append(smean) if turns: agg[arm]["t"].append(statistics.mean(turns)) if toks: agg[arm]["k"].append(statistics.mean(toks)) if walls: agg[arm]["w"].append(statistics.mean(walls)) print("-" * len(hdr)) for arm in ("base", "pr"): a = agg[arm] if a["s"]: print(f"{'MEAN-OF-TASK-MEANS':<28} | {arm:<4} | | {statistics.mean(a['s']):>10.3f} | " f"{fmt(statistics.mean(a['t']) if a['t'] else None):>6} | " f"{fmt(statistics.mean(a['k'])/1000 if a['k'] else None):>7} | " f"{fmt(statistics.mean(a['w']) if a['w'] else None):>6} |") noise = [r for r in recs if r.get("raw_xml_noise")] errs = [r for r in recs if r.get("error")] if noise: print(f"raw-XML noise runs: {len(noise)} -> " + ", ".join(f"{r['arm']}/{r['task']}/r{r['rep']}" for r in noise)) if errs: print(f"errored runs: {len(errs)} -> " + ", ".join(f"{r['arm']}/{r['task']}/r{r['rep']}: {r['error'][:60]}" for r in errs))