Download scripts/compare_cua.py from FineEnvs/DesignGym: direct link, hf CLI and curl.
- Browser
- Download file 4.33 kB
-
https://huggingface.co/spaces/FineEnvs/DesignGym/resolve/main/scripts/compare_cua.py
- Command line
-
hf download hf://spaces/FineEnvs/DesignGym/scripts/compare_cua.py
-
curl -L -o compare_cua.py https://huggingface.co/spaces/FineEnvs/DesignGym/resolve/main/scripts/compare_cua.py
4.33 kB
| """Compare recorded CUA trajectories with real model rollouts on the GUI surface. | |
| python scripts/compare_cua.py --cua artifacts/cua/cua.jsonl --trace artifacts/cua-compare/trace.jsonl | |
| For each side it reports how the episode was driven: turns, calls per turn, | |
| the gesture mix, how numbers got into the editor (typed into fields vs dragged), | |
| scrolls, undos, thought length, and reward. The point is to see whether the | |
| recorded behaviour looks like what a capable model actually does, per layout. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import statistics | |
| from collections import Counter | |
| from pathlib import Path | |
| def _profile(turns: list[dict]) -> dict: | |
| """`turns`: [{"text": str, "calls": [{"name", "args"}]}] -> behaviour profile.""" | |
| calls = [c for t in turns for c in t["calls"]] | |
| names = Counter(c["name"] for c in calls) | |
| typed = [c for c in calls if c["name"] == "type"] | |
| numeric = sum(1 for c in typed if str(c["args"].get("text", "")).replace(".", "").replace("-", "").isdigit()) | |
| keys = Counter(str(c["args"].get("keys")) for c in calls if c["name"] == "key") | |
| words = [len(t["text"].split()) for t in turns if t.get("text")] | |
| return { | |
| "turns": len(turns), | |
| "calls": len(calls), | |
| "calls_per_turn": round(len(calls) / max(1, len(turns)), 2), | |
| "mix": {k: round(v / max(1, len(calls)), 3) for k, v in names.most_common()}, | |
| "typed_numbers": numeric, | |
| "typed_text": len(typed) - numeric, | |
| "drags": names.get("left_click_drag", 0), | |
| "scrolls": names.get("scroll", 0), | |
| "undo": keys.get("ctrl+z", 0) + keys.get("cmd+z", 0), | |
| "screenshots_requested": names.get("screenshot", 0), | |
| "thought_words_median": statistics.median(words) if words else 0, | |
| "turns_with_thought": round(len(words) / max(1, len(turns)), 2), | |
| } | |
| def from_cua(traj: dict) -> dict: | |
| turns = [] | |
| for m in traj["messages"]: | |
| if m["role"] == "assistant": | |
| turns.append({"text": m.get("content") or "", "calls": [ | |
| {"name": c["function"]["name"], "args": json.loads(c["function"]["arguments"])} | |
| for c in m.get("tool_calls", [])]}) | |
| meta = traj["meta"] | |
| return {"who": "recorder", "skin": meta["skin"], "layout": meta.get("layout"), "task_id": meta["task_id"], | |
| "reward": meta["reward"], **_profile(turns)} | |
| def from_trace(rec: dict) -> dict: | |
| turns = [{"text": t.get("text", ""), "calls": t.get("calls", [])} for t in rec.get("turns", [])] | |
| return {"who": rec["model"], "skin": rec.get("skin"), "layout": "default", "task_id": rec.get("task_id"), | |
| "reward": rec.get("reward"), **_profile(turns)} | |
| def main(argv=None) -> int: | |
| ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) | |
| ap.add_argument("--cua", action="append", default=[], help="cua.jsonl, or a directory of trajectory .json files") | |
| ap.add_argument("--trace", action="append", default=[], help="rollout trace.jsonl") | |
| ap.add_argument("--out", default=None) | |
| args = ap.parse_args(argv) | |
| rows = [] | |
| for src in args.cua: | |
| p = Path(src) | |
| items = ([json.loads(line) for line in p.read_text().splitlines() if line.strip()] if p.is_file() | |
| else [json.loads(f.read_text()) for f in sorted(p.glob("*.json"))]) | |
| rows += [from_cua(t) for t in items] | |
| for src in args.trace: | |
| rows += [from_trace(json.loads(line)) for line in Path(src).read_text().splitlines() | |
| if line.strip() and json.loads(line).get("surface") == "gui"] | |
| cols = ["who", "skin", "reward", "turns", "calls", "calls_per_turn", "typed_numbers", "typed_text", "drags", | |
| "scrolls", "undo", "screenshots_requested", "thought_words_median"] | |
| print("| " + " | ".join(cols) + " | top gestures |") | |
| print("|" + "---|" * (len(cols) + 1)) | |
| for r in sorted(rows, key=lambda r: (r["skin"] or "", r["who"])): | |
| top = ", ".join(f"{k} {v:.0%}" for k, v in list(r["mix"].items())[:4]) | |
| vals = [f"{r[c]:.3f}" if isinstance(r[c], float) and c == "reward" else str(r[c]) for c in cols] | |
| print("| " + " | ".join(vals) + f" | {top} |") | |
| if args.out: | |
| Path(args.out).write_text(json.dumps(rows, indent=1)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |