Spaces:
Running
Running
File size: 6,918 Bytes
22de727 | 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 | """Generate the paper's data, step by step: MiniWebGen's trajectories, the training episodes and pool, the generated test
tasks of the held-out sites, the task-centric comparison data, and the release.
python scripts/generate_data.py # list the steps and print every command (nothing runs)
python scripts/generate_data.py --run --steps generate,replay,augment
python scripts/generate_data.py --run # everything, in order
Steps (each is resumable: the underlying commands skip what already exists):
split the train/test site split, fixed before anything is generated (data/datagen/site_split.json, tracked)
sitemap MiniWebGen's privileged site maps: crawl the training sites, find each primitive's controls, probe the request
each control sends (data/datagen/sitemap/)
guides one guide per primitive, distilled from the human demonstration spans on training sites
(data/datagen/guides/, tracked)
generate the paper's generation runs (scripts/paper.yaml, data.runs), each with the arguments it recorded in
data/datagen/runs/<run>/run.json: sample -> suggest -> execute -> backend gate -> kept trajectories
replay every kept trajectory replayed through the agent harness into training rows (data/webmix/replay_v4/), the
base model writing each step's memory: 4 shards, then one pass that retries failures
augment the variants: recovery (an injected, corrected mistake; 35% of trajectories) and chain (two trajectories
of one site as one episode)
pool the paper's training pool: the frozen id lists scripts/pools/pool3k_plain_ids.txt (3,128 trajectories) and
pool3k_dirs.txt (4,614 episodes); `python scripts/reproduce.py train pooled_v4` builds its folder
valset the 115 generated single-primitive test tasks of the held-out sites (src/miniwebgen/scripts/make_valset.py)
taskgen the task-centric comparison data: proposed goal-level tasks on the training sites, attempted by the base
model, judged, and selected to the same step budget (src/miniwebgen/taskgen.py)
release the consolidated, split-labelled, anonymized data folder (scripts/build_release.py)
The LLM stages need Gemini credentials in .env; replay, taskgen's attempts and the release's grading need a served base
model (src/miniwebagent/scripts/serve.sh, port 8400). MiniWebGen's servers use ports 8300-8320 only.
"""
import argparse
import ast
import json
import os
import subprocess
import sys
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
RUNS = ROOT / "data" / "datagen" / "runs"
PY = "python"
RUN_FLAGS = {"n": "--n", "workers": "--workers", "servers": "--servers", "attempts": "--attempts", "seed": "--seed",
"propose_site_cap": "--propose-site-cap", "per_site_cap": "--per-site-cap", "target": "--target",
"overshoot": "--overshoot", "mid_chain_share": "--mid-chain-share", "preset_share": "--preset-share",
"thoughts": "--thoughts", "stop_after": "--stop-after"}
def generation_run(run_id, template):
"""The command a generation run recorded (run.json), or the registry's template for a run not on this machine."""
f = RUNS / run_id / "run.json"
if not f.exists():
return template.replace("<id>", run_id)
a = json.loads(f.read_text())
macros = a.get("macros")
macros = ast.literal_eval(macros) if isinstance(macros, str) else macros
argv = [PY, "-m", "miniwebgen", "run", "--run-id", run_id]
if macros:
argv += ["--macros", *macros]
for k, flag in RUN_FLAGS.items():
if a.get(k) is not None and not (k == "thoughts" and a[k] == "none"):
argv += [flag, str(a[k])]
if a.get("ports"):
argv += ["--ports", *[str(p) for p in a["ports"]]]
if a.get("no_judge"):
argv.append("--no-judge")
return " ".join(argv)
def steps():
reg = yaml.safe_load((ROOT / "scripts" / "paper.yaml").read_text())["data"]
replay = reg["replay"].split("#")[0].strip()
return [
("split", [f"{PY} -m miniwebgen split"]),
("sitemap", [f"{PY} -m miniwebgen sitemap --workers 4"]),
("guides", [f"{PY} -m miniwebgen guide"]),
("generate", [generation_run(r, reg["generate"]) for r in reg["runs"]]),
("replay", [" & ".join(replay.replace("k/4", f"{k}/4") + f" --ports {8310 + 2 * k},{8311 + 2 * k}" for k in range(4))
+ " & wait", # 4 shards side by side, each with its own servers
replay.replace(" --shard k/4", "")]), # then once more: retries the failures
("augment", [c.split("#")[0].strip() + " --memory-model qwen35-4b" for c in reg["augment"]]),
("pool", ["wc -l scripts/pools/pool3k_plain_ids.txt scripts/pools/pool3k_dirs.txt",
f"{PY} scripts/reproduce.py train pooled_v4"]),
("valset", [f"{PY} src/miniwebgen/scripts/make_valset.py"]),
("taskgen", [f"{PY} -m miniwebgen.taskgen propose --out data/webmix/taskgen/tasks.jsonl --per-site 140",
f"{PY} -m miniwebagent.evaluate --tasks data/webmix/taskgen/tasks.jsonl --arm base --record-rows "
f"--max-steps 30 --timeout 900 --workers 12 --ports 8300,8301 --out data/webmix/taskgen/runs/v1",
"cp data/webmix/taskgen/tasks.jsonl data/webmix/taskgen/runs/v1/tasks.jsonl",
f"{PY} -m miniwebgen.taskgen judge data/webmix/taskgen/runs/v1 --workers 16",
f"{PY} -m miniwebgen.taskgen rows data/webmix/taskgen/runs/v1 --out data/webmix/taskgen/rows_27k"]),
("release", [f"{PY} scripts/build_release.py"]),
]
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--steps", default=None, help="comma-separated steps (default: all, in order)")
ap.add_argument("--run", action="store_true", help="run the commands (default: print them)")
a = ap.parse_args()
all_steps = steps()
names = [n for n, _ in all_steps]
want = a.steps.split(",") if a.steps else names
unknown = set(want) - set(names)
if unknown:
sys.exit(f"unknown steps {sorted(unknown)}; steps: {', '.join(names)}")
env = {**os.environ, "PYTHONPATH": os.pathsep.join(filter(None, [str(ROOT / "src"), os.environ.get("PYTHONPATH")]))}
for name, cmds in all_steps:
if name not in want:
continue
print(f"\n## {name}", flush=True)
for c in cmds:
print(f" {c}", flush=True)
if a.run:
subprocess.run(c.replace(f"{PY} ", f"{sys.executable} ", -1) if c.startswith(PY) else c,
shell=True, cwd=ROOT, env=env, check=True)
if __name__ == "__main__":
main()
|