DesignGym / cli.py
AdithyaSK's picture
AdithyaSK HF Staff
Serve Crello corpus aafdb0e86816 from the mounted bucket
23df34a verified
Raw History Blame Contribute Delete
13.4 kB
"""`design-canvas` command line.
design-canvas prepare [--train 200 --validation 60 --test 100] build the task pack
design-canvas serve [--port 8000] [--workers N] OpenEnv server + editor (N processes, one URL)
design-canvas mcp stdio MCP server for Claude & co.
design-canvas export-sft --split train --out sft.jsonl expert trajectories
design-canvas export-hf --cua artifacts/cua/cua.jsonl --out artifacts/hf/cua cua-v1 dataset (Hub-ready)
design-canvas smoke offline end-to-end check
design-canvas corpus build --out DIR index all of FineEnvs/crello-bucket
design-canvas corpus publish --out DIR upload the index next to the data
design-canvas corpus stats what the served corpus holds
design-canvas evalset build --out FILE freeze the stratified evaluation set
design-canvas evalset verify FILE check a frozen set against the corpus
design-canvas evalset export FILE --out DIR write it as an offline task pack
design-canvas evalset subset FILE --name N --size K [--within lite] nest a smaller subset in the set
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
from pathlib import Path
def _pack_arg(p: argparse.ArgumentParser):
p.add_argument("--pack", default=os.environ.get("DESIGN_CANVAS_PACK"),
help="task pack directory (default ~/.cache/design_canvas/pack)")
def cmd_prepare(args) -> int:
from huggingface_hub import hf_hub_download
from .core.pack import CRELLO_REPO, CRELLO_REVISION, DEFAULT_PACK, build_pack
out = Path(args.pack or DEFAULT_PACK).expanduser()
def dl(name):
return hf_hub_download(CRELLO_REPO, name, repo_type="dataset", revision=CRELLO_REVISION)
shards = {"train": "data/train-{:05d}-of-00031.parquet",
"validation": "data/validation-{:05d}-of-00003.parquet",
"test": "data/test-{:05d}-of-00004.parquet"}
per = {"train": args.train, "validation": args.validation, "test": args.test}
sources = {}
for split, pattern in shards.items():
if per[split] <= 0:
continue
# Each shard holds ~600 templates; about 60% pass the filters.
need = max(1, min(args.max_shards, 1 + per[split] // 350))
sources[split] = [dl(pattern.format(i)) for i in range(need)]
print(f"{split}: {len(sources[split])} shard(s)")
t = time.time()
manifest = build_pack(out, sources, per, dl("resources/fonts.pickle"),
max_elements=args.max_elements)
print(json.dumps(manifest, indent=2))
print(f"pack ready at {out} in {time.time() - t:.0f}s")
return 0
def cmd_serve(args) -> int:
if args.pack:
os.environ["DESIGN_CANVAS_PACK"] = str(Path(args.pack).expanduser())
os.environ["PORT"] = str(args.port)
from .server.app import main
print(f"DesignGym on http://{args.host}:{args.port} "
f"(editor /editor/, playground /web/, MCP /mcp)")
main(host=args.host, port=args.port, workers=args.workers)
return 0
def cmd_mcp(args) -> int:
if args.pack:
os.environ["DESIGN_CANVAS_PACK"] = str(Path(args.pack).expanduser())
from .server.stdio_mcp import run
run(editor_port=args.editor_port)
return 0
def cmd_export_sft(args) -> int:
from .core.pack import load_pack
from .train_data import export
pack = load_pack(args.pack)
n = export(pack, args.split, Path(args.out), surfaces=args.surfaces.split(","),
modes=args.modes.split(","), limit=args.limit)
print(f"wrote {n} trajectories to {args.out}")
return 0
def cmd_export_cua(args) -> int:
from .core.pack import load_pack
from .cua import export
summary = export(load_pack(args.pack), args.split, Path(args.out), limit=args.limit, start=args.start,
modes=tuple(args.modes.split(",")), seed=args.seed, workers=args.workers,
server=args.server, max_elements=args.max_elements, image_format=args.image_format)
print(json.dumps(summary, indent=2))
return 0
def cmd_export_hf(args) -> int:
from .cua_format import Window, export_hf
summary = export_hf(Path(args.cua), Path(args.out), image_format=args.image_format, min_reward=args.min_reward,
window=Window(args.max_images, args.keep_images), shard_mb=args.shard_mb,
with_windows=args.windows)
print(json.dumps(summary, indent=2))
return 0
def cmd_smoke(args) -> int:
from .smoke import run
return run(args.pack)
def _corpus(args):
from .core.corpus import CorpusPack
manifest = args.corpus or os.environ.get("DESIGN_CANVAS_CORPUS")
if not manifest:
raise SystemExit("set --corpus or DESIGN_CANVAS_CORPUS to a corpus manifest")
return CorpusPack(manifest)
def cmd_corpus(args) -> int:
from .core import corpus
if args.action == "build":
source = corpus.BucketSource(args.bucket, args.source_root)
m = corpus.build_index(args.out, source, max_elements=args.max_elements, workers=args.workers)
print(json.dumps({k: m[k] for k in ("snapshot_id", "splits", "rejected", "difficulty")}, indent=2))
elif args.action == "publish":
print(corpus.publish_index(args.out, args.bucket))
elif args.action == "warm":
pack = _corpus(args)
out = pack.warm(args.splits.split(","), workers=min(args.workers, 8),
progress=lambda g, n, d: print(f" {g}/{n} row groups, {d} designs", flush=True))
out["failed"] = out["failed"][:20]
print(json.dumps(out, indent=2))
return 1 if out["failed"] else 0
else:
pack = _corpus(args)
print(json.dumps({**pack.stats(), "splits": pack.manifest["splits"],
"rejected": pack.manifest["rejected"]}, indent=2))
return 0
def cmd_evalset(args) -> int:
from .core import evalset as ev
pack = _corpus(args)
if args.action == "build":
record = ev.build(pack, split=args.split, size=args.size, lite=args.lite, seed=args.seed,
validate=not args.no_validate)
ev.save(record, args.out)
print(json.dumps({"evalset_id": record["evalset_id"], **record["summary"],
"lite": ev.summarize(record, "lite"), "failures": record["failures"]}, indent=2))
return 1 if record["failures"] else 0
record = ev.load(args.file, pack.snapshot_id)
ev.check_against(record, pack)
if args.action == "calibrate":
from .core.difficulty import calibrate
rewards: dict[str, list[float]] = {}
for trace in args.trace:
for line in Path(trace).read_text().splitlines():
r = json.loads(line)
if r.get("evalset_task") and not r.get("error"):
rewards.setdefault(r["evalset_task"], []).append(r["reward"] or 0.0)
ids = {e["task_id"] for e in record["tasks"]}
rows = [r for r in pack.rows(record["split"]) if r["task_id"] in ids]
print(json.dumps(calibrate(rows, rewards, pack.manifest["difficulty"]["weights"]), indent=2))
return 0
if args.action == "export":
print(json.dumps(ev.export_pack(pack, record, args.out), indent=2))
elif args.action == "subset":
ev.add_subset(record, args.name, args.size, args.within)
ev.save(record, args.file)
print(json.dumps({"evalset_id": record["evalset_id"], "subset": args.name,
**ev.summarize(record, args.name), "task_ids": record["subsets"][args.name]}, indent=2))
else:
print(json.dumps({"evalset_id": record["evalset_id"], "ok": True, **record["summary"]}, indent=2))
return 0
def main(argv=None) -> int:
parser = argparse.ArgumentParser(prog="design-canvas", description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("prepare", help="build the task pack from Crello")
_pack_arg(p)
p.add_argument("--train", type=int, default=200)
p.add_argument("--validation", type=int, default=60)
p.add_argument("--test", type=int, default=100)
p.add_argument("--max-elements", type=int, default=20)
p.add_argument("--max-shards", type=int, default=4)
p.set_defaults(fn=cmd_prepare)
p = sub.add_parser("serve", help="run the OpenEnv server with the editor")
_pack_arg(p)
p.add_argument("--host", default="127.0.0.1")
p.add_argument("--port", type=int, default=int(os.environ.get("PORT", "8007")))
p.add_argument("--workers", type=int, default=int(os.environ.get("DESIGN_CANVAS_WORKERS", "1")),
help="server processes behind one URL (a session-aware router); 1 = a single process")
p.set_defaults(fn=cmd_serve)
p = sub.add_parser("mcp", help="stdio MCP server (e.g. claude mcp add design-canvas -- ...)")
_pack_arg(p)
p.add_argument("--editor-port", type=int, default=8007,
help="also serve the live editor on this port (0 to disable)")
p.set_defaults(fn=cmd_mcp)
p = sub.add_parser("export-sft", help="write expert trajectories as chat JSONL")
_pack_arg(p)
p.add_argument("--split", default="train")
p.add_argument("--out", default="sft.jsonl")
p.add_argument("--surfaces", default="tools,html")
p.add_argument("--modes", default="reference,description")
p.add_argument("--limit", type=int, default=None)
p.set_defaults(fn=cmd_export_sft)
p = sub.add_parser("export-cua", help="record verified computer-use trajectories (screenshots + actions)")
_pack_arg(p)
p.add_argument("--split", default="train")
p.add_argument("--out", default="artifacts/cua")
p.add_argument("--limit", type=int, default=20)
p.add_argument("--start", type=int, default=0)
p.add_argument("--modes", default="reference,description")
p.add_argument("--seed", type=int, default=0)
p.add_argument("--workers", type=int, default=2)
p.add_argument("--max-elements", type=int, default=12)
p.add_argument("--image-format", default="webp", choices=["webp", "png", "jpeg"])
p.add_argument("--server", default=None, help="a running server with DESIGN_CANVAS_DEBUG=1; default in-process")
p.set_defaults(fn=cmd_export_cua)
p = sub.add_parser("export-hf", help="write recorded trajectories as a cua-v1 Hugging Face dataset folder")
p.add_argument("--cua", required=True, help="the recorder's cua.jsonl (images next to it)")
p.add_argument("--out", required=True)
p.add_argument("--image-format", default="keep", choices=["keep", "png", "jpeg"],
help="keep = the recorder's format (WebP), smallest")
p.add_argument("--min-reward", type=float, default=0.98)
p.add_argument("--max-images", type=int, default=8)
p.add_argument("--keep-images", type=int, default=3)
p.add_argument("--shard-mb", type=int, default=500)
p.add_argument("--windows", action="store_true", help="also store the SFT windows view (duplicates screenshots)")
p.set_defaults(fn=cmd_export_hf)
p = sub.add_parser("smoke", help="offline end-to-end check")
_pack_arg(p)
p.set_defaults(fn=cmd_smoke)
p = sub.add_parser("corpus", help="index, publish or inspect the bucket-backed corpus")
p.add_argument("action", choices=["build", "publish", "stats", "warm"])
p.add_argument("--out", default="artifacts/corpus-index", help="index directory (build, publish)")
p.add_argument("--bucket", default="FineEnvs/crello-bucket")
p.add_argument("--source-root", default=None, help="local copy or mount of the bucket")
p.add_argument("--max-elements", type=int, default=30)
p.add_argument("--workers", type=int, default=8)
p.add_argument("--corpus", default=None, help="corpus manifest (stats, warm)")
p.add_argument("--splits", default="train,validation,test", help="splits to download ahead of time (warm)")
p.set_defaults(fn=cmd_corpus)
p = sub.add_parser("evalset", help="build, verify or export the frozen evaluation set")
p.add_argument("action", choices=["build", "verify", "export", "calibrate", "subset"])
p.add_argument("file", nargs="?", help="frozen set (verify, export, subset)")
p.add_argument("--name", help="subset name (subset)")
p.add_argument("--within", default="lite", help="subset to nest inside (subset)")
p.add_argument("--out", help="output file (build) or pack directory (export)")
p.add_argument("--corpus", default=None, help="corpus manifest (default DESIGN_CANVAS_CORPUS)")
p.add_argument("--split", default="test")
p.add_argument("--size", type=int, default=300, help="set size (build) or subset size (subset)")
p.add_argument("--lite", type=int, default=60)
p.add_argument("--seed", type=int, default=42)
p.add_argument("--no-validate", action="store_true")
p.add_argument("--trace", action="append", default=[], help="rollout trace.jsonl (calibrate)")
p.set_defaults(fn=cmd_evalset)
args = parser.parse_args(argv)
return args.fn(args)
if __name__ == "__main__":
sys.exit(main())