laya-browser / code /apps /browser_suite_om2w.py
cklxx's picture
v19s: WebChain real-site trajectories, format v5, webgym x7 + DAgger, harness fixes; replaces v17s
454b3e6 verified
Raw History Blame Contribute Delete
4.15 kB
"""Online-Mind2Web on live sites: the jev agent + laya on the tasks whose domain never appears in training.
python apps/browser_suite_om2w.py [out.jsonl=finetune/out/om2w_run.jsonl]
env: OM2W_LEVELS=easy,medium,hard OM2W_LIMIT=0 OM2W_MAX_STEPS=30 SHARD=i/n
(+ the usual jev switches: JEV_VERIFY_DONE / JEV_ESCALATE / JEV_PLANNER ...)
Resumable: tasks already in the output are skipped. Every trajectory keeps what a judge needs (the action history with
URLs and typed text, the final URL / title / visible text); judge them with finetune/judge_om2w.py.
"""
import json, os, sys, time, urllib.parse
sys.path.insert(0, "/home/ckl/projects/S/jev-ultrafast")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import browser_suite # noqa: F401 (jev env: CDP, laya server, text model)
from jev_ultrafast import Agent
L = "/home/ckl/projects/S/laya/finetune/out"
def base(host):
return ".".join((host or "").split(".")[-2:])
def held_out_tasks():
tasks = json.load(open(f"{L}/online_mind2web.json"))
trained = {base(d) for d in json.load(open(f"{L}/train_domains.json"))}
# tasks whose completion would act on a real third party (message a seller, file a government request, send a gift
# card) are not run on live sites; JEV_SAFE=1 additionally hides pay / order / send / submit-request controls
side_effects = ("contact the cheapest", "Submit a request for vehicle registration", "send Christene")
# OM2W_SITES (finetune/probe_sites.py output): only sites that load for our headless browser; the rest show an
# anti-bot wall or "access denied" and would measure the wall, not the agent
sites = json.load(open(os.environ["OM2W_SITES"])) if os.environ.get("OM2W_SITES") else None
return [t for t in tasks if base(urllib.parse.urlparse(t["website"]).hostname) not in trained
and not any(x in t["confirmed_task"] for x in side_effects) and (sites is None or sites.get(t["website"], {}).get("ok"))]
def run(task, max_steps):
t0 = time.time(); status, hist, final, extra = "error", [], {}, {}
try:
with Agent(task["website"], task["confirmed_task"]) as agent:
for state in agent.run():
status = state["status"]
if len(state["history"]) >= max_steps:
break
s = agent.state
hist = [{k: h.get(k) for k in ("action", "kind", "text", "url", "page_changed")} for h in s["history"]]
p = s["page"]
final = {"url": p["url"], "title": p["title"], "text": p["text"][:4000]}
extra = {k: s.get(k, 0) for k in ("escalations", "done_rejections", "select_overrides")}
status = s["status"]
except Exception as e:
status = f"error:{type(e).__name__}:{str(e)[:80]}"
return {"task_id": task["task_id"], "level": task["level"], "website": task["website"], "task": task["confirmed_task"],
"status": status, "steps": len(hist), "wall": round(time.time() - t0, 1), "history": hist, "final": final, **extra}
def main():
out = sys.argv[1] if len(sys.argv) > 1 else f"{L}/om2w_run.jsonl"
levels = os.environ.get("OM2W_LEVELS", "easy,medium,hard").split(",")
tasks = [t for t in held_out_tasks() if t["level"] in levels]
if os.environ.get("SHARD"):
i, n = map(int, os.environ["SHARD"].split("/")); tasks = tasks[i::n]
if int(os.environ.get("OM2W_LIMIT", "0")):
tasks = tasks[:int(os.environ["OM2W_LIMIT"])]
done = {json.loads(l)["task_id"] for l in open(out)} if os.path.exists(out) else set()
max_steps = int(os.environ.get("OM2W_MAX_STEPS", "30"))
print(f"== {len(tasks)} held-out tasks ({len(done)} already run)", flush=True)
with open(out, "a") as f:
for t in tasks:
if t["task_id"] in done:
continue
r = run(t, max_steps)
f.write(json.dumps(r, ensure_ascii=False) + "\n"); f.flush()
print(f" {r['level']:6s} {r['status'][:24]:24s} steps={r['steps']:2d} esc={r.get('escalations', 0)} {r['wall']:5.1f}s {t['confirmed_task'][:80]}", flush=True)
if __name__ == "__main__":
main()