Feature Extraction
Transformers
Safetensors
Laya
English
multilingual
laya_browser
custom_code
system-1
browser-agent
web-navigation
decision-model
mmbert
mind2web
tilelang
Instructions to use cklxx/laya-browser with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use cklxx/laya-browser with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="cklxx/laya-browser", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("cklxx/laya-browser", trust_remote_code=True, device_map="auto") - Laya
How to use cklxx/laya-browser with Laya:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Download code/finetune/build_items.py from cklxx/laya-browser: direct link, hf CLI and curl.
- Browser
- Download file 9.41 kB
-
https://huggingface.co/cklxx/laya-browser/resolve/main/code/finetune/build_items.py
- Command line
-
hf download hf://cklxx/laya-browser/code/finetune/build_items.py
-
curl -L -o build_items.py https://huggingface.co/cklxx/laya-browser/resolve/main/code/finetune/build_items.py
9.41 kB
| """cases.jsonl + pages.jsonl -> tokenized training items (train split) and eval cases (held-out pages). | |
| python finetune/build_items.py out/pages.jsonl out/cases.jsonl out/ | |
| """ | |
| import json, random, os, sys | |
| from array import array | |
| import torch | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from common_ft import build_request, gold_for, goal_done_question | |
| from transformers import AutoTokenizer | |
| from laya.common import QTYPES, build_sequence, render_options | |
| MAX_LEN, HEAD_MAX_LEN = int(os.environ.get('LAYA_MAXLEN', '1024')), int(os.environ.get('LAYA_HEAD', '512')) | |
| EVAL_EVERY = 5 # pages with index % 5 == 0 are held out | |
| MAX_TARGETS = int(os.environ.get('MAX_TARGETS', '40')) | |
| FINAL_P = float(os.environ.get('FINAL_P', '0.2')) # share of target items built like the server's final round | |
| # NOUL=1: also emit the yes/no question "is the whole goal visibly done on this page?" (common_ft.GOAL_DONE). | |
| # yes = states whose gold is DONE; no = every mid-trajectory state (incl. webgym's results-shown-but-filter-missing | |
| # states). NOUL_NEG_P keeps the negatives at roughly 60:40 (no-heavy, to counter the DONE-too-early bias). | |
| NOUL = os.environ.get('NOUL') == '1' | |
| NOUL_NEG_P = float(os.environ.get('NOUL_NEG_P', '0.3')) | |
| def main(): | |
| pages_f, cases_f, out = sys.argv[1:4] | |
| pages = [json.loads(l) for l in open(pages_f)] | |
| cases = [json.loads(l) for l in open(cases_f)] | |
| for extra in sys.argv[4:]: | |
| cases += [json.loads(l) for l in open(extra)] | |
| snap = os.environ.get("LAYA_BASE") | |
| tok = AutoTokenizer.from_pretrained(os.path.join(snap, "tokenizer")) | |
| items, n_skip, ev = [], 0, [] | |
| import hashlib | |
| STOPS = [" Stop when it is open.", " Stop once that page is visible.", " Then stop.", " Finish when it has loaded.", ""] | |
| # evaluation hygiene: never train on a suite task's start page (suite A and B start URLs), whatever the source | |
| import re as _re | |
| def _norm(u): return (u or "").split("#")[0].rstrip("/").replace("://www.", "://") | |
| suite_urls = set() | |
| for f in ("apps/browser_suite.py", "apps/browser_suite_b.py"): | |
| if os.path.exists(f): | |
| suite_urls |= {_norm(u) for u in _re.findall(r'\("[a-z0-9-]+", "(https?://[^"]+)"', open(f).read())} | |
| n_suite = 0 | |
| if os.environ.get("LAYA_FMT") == "v6": | |
| # v6 history carries each action's result = the page the NEXT step starts on (same trajectory, one more step) | |
| traj = lambda c: (c.get("source"), c.get("task_id") or c.get("goal"), c.get("website")) | |
| at = {} | |
| for c in cases: | |
| pg = c.get("page_obj") or (pages[c["page"]] if c.get("page", -1) >= 0 else {}) | |
| at.setdefault((traj(c), len(c.get("history") or [])), (pg.get("url") or c.get("url"), pg.get("title") or c.get("title"))) | |
| filled = 0 | |
| for c in cases: | |
| for i, h in enumerate(c.get("history") or []): | |
| if not h.get("url") and (traj(c), i + 1) in at: | |
| h["url"], h["title"] = at[(traj(c), i + 1)]; filled += 1 | |
| print("v6: history steps given their result page:", filled, flush=True) | |
| keep = float(os.environ.get("CASE_KEEP", "1")) # CASE_KEEP=p: build from a random p of the cases (smaller, faster) | |
| if keep < 1: | |
| cases = [c for c in cases if int(hashlib.md5((c["goal"] + str(len(c.get("history") or []))).encode()).hexdigest()[:8], 16) / 0xffffffff < keep] | |
| print("CASE_KEEP", keep, "->", len(cases), "cases", flush=True) | |
| for c in cases: | |
| if _norm(c.get("url") or (c.get("page_obj") or {}).get("url")) in suite_urls or (c.get("page", -1) >= 0 and _norm(pages[c["page"]]["url"]) in suite_urls): | |
| n_suite += 1; continue | |
| if c.get("source") == "dagger": | |
| continue | |
| if c["kind"] == "done" and "page_obj" not in c: | |
| continue # template DONE cases leak phrasing; real ones come from make_done_cases.py | |
| page = c.get("page_obj") or pages[c["page"]] | |
| h = int(hashlib.md5(c["goal"].encode()).hexdigest(), 16) | |
| is_sub = str(c.get("skill", "")).endswith("@sub") or c.get("skill") == "subgoal_done" | |
| goal = c["goal"] + ("" if is_sub or c.get("source") == "webgym" else STOPS[h % len(STOPS)]) | |
| c = {**c, "goal": goal} | |
| state, questions, targets, controls = build_request(page, goal, c.get("history", [])) | |
| if c.get("noul_only"): | |
| # a state where a failed run wrongly stopped: only the completion question, answered "not done" | |
| if NOUL and not ((hashlib.md5(c["website"].encode()).digest()[0] % EVAL_EVERY == 0)): | |
| nq = goal_done_question(goal) | |
| qq = {"t": "noul", "ins": json.dumps(nq["instructions"]), "crit": None} | |
| seq, markers = build_sequence(tok, state, qq, MAX_LEN, HEAD_MAX_LEN) | |
| if len(markers) == 2: | |
| items.extend([{"ids": array("i", seq), "markers": markers, "qtype": QTYPES["noul"], "target": [1.0, 0.0], "label": 0, | |
| "qid": "goal_done", "gold_op": "NOT_DONE", "src": c.get("source")}] * 2) | |
| elif NOUL: | |
| ev.append({**c, "gold_index": None}) | |
| continue | |
| gop, gidx = gold_for(c, targets, controls) | |
| if gop is None: | |
| n_skip += 1; continue | |
| held = (hashlib.md5(c["website"].encode()).digest()[0] % EVAL_EVERY == 0) if c.get("source") in ("mind2web", "nnetnav", "webchain", "gobrowse") else (c["page"] % EVAL_EVERY == 0) | |
| if held: | |
| ev.append({**c, "gold_index": gidx}); continue | |
| golds = {"operation": gop} | |
| if gidx is not None: | |
| golds[gop.lower() + "_target"] = gidx | |
| for qid, gold in golds.items(): | |
| q = questions[qid]; keys = list(q["criteria"]) | |
| rs = random.Random(h + len(keys) + len(qid)) | |
| if qid != "operation" and len(keys) > 8 and rs.random() < FINAL_P: | |
| width = rs.randint(2, 6) # the server's final round: only the chunk winners (2-6 options) | |
| else: | |
| width = MAX_TARGETS | |
| if qid != "operation" and len(keys) > width: | |
| # inference splits wide choices into chunks of <= MAX_TARGETS (systemone_server MAXOPT); train on the same | |
| # widths, so option labels are never truncated to a few tokens: gold + a random sample, page order kept | |
| keep = set(rs.sample([k for k in keys if k != gold], width - 1)) | {gold} | |
| keys = [k for k in keys if k in keep] | |
| q = {**q, "criteria": {k: q["criteria"][k] for k in keys}} | |
| target = [1.0 if k == gold else 0.0 for k in keys] | |
| qq = {"t": "choice", "ins": json.dumps(q["instructions"]), "crit": q["criteria"]} | |
| seq, markers = build_sequence(tok, state, qq, MAX_LEN, HEAD_MAX_LEN) | |
| if len(markers) != len(render_options(qq)): | |
| n_skip += 1; continue | |
| item = {"ids": array("i", seq), "markers": markers, "qtype": QTYPES["choice"], "target": target, "label": keys.index(gold), "qid": qid, "gold_op": gop, "src": c.get("source")} | |
| # class balance: CLICK dominates the operation question, so repeat the rare operations | |
| reps = {"DONE": 4, "TYPE_TEXT": 3, "SELECT": 3, "PRESS_ENTER": 4, "SCROLL_DOWN": 2}.get(gop, 1) if qid == "operation" else 1 | |
| if c.get("source") == "dagger": | |
| reps *= 5 | |
| if c.get("source") == "webgym" or str(c.get("skill", "")).endswith("@sub"): | |
| reps = 1 # webgym already pairs every DONE with its "keep going" twin; no op-level oversampling | |
| if c.get("source") == "rollout": | |
| reps *= 3 # scripted scroll / search-submit / select trajectories: the skills the model lacked # on-policy teacher corrections from real tasks: few but exactly where the policy fails | |
| items.extend([item] * reps) | |
| if NOUL: | |
| yes = gop == "DONE" | |
| rn = random.Random(h + 7) | |
| if yes or rn.random() < NOUL_NEG_P: | |
| nq = goal_done_question(goal) | |
| qq = {"t": "noul", "ins": json.dumps(nq["instructions"]), "crit": None} | |
| seq, markers = build_sequence(tok, state, qq, MAX_LEN, HEAD_MAX_LEN) | |
| if len(markers) == 2: | |
| items.append({"ids": array("i", seq), "markers": markers, "qtype": QTYPES["noul"], "target": [0.0, 1.0] if yes else [1.0, 0.0], | |
| "label": int(yes), "qid": "goal_done", "gold_op": gop, "src": c.get("source")}) | |
| torch.save(items, os.path.join(out, "train_items.pt")) | |
| with open(os.path.join(out, "eval_cases.jsonl"), "w") as f: | |
| for c in ev: f.write(json.dumps(c, ensure_ascii=False) + "\n") | |
| print(f"dropped {n_suite} cases on suite start pages; dagger cases are never used") | |
| lens = [len(i["ids"]) for i in items] | |
| import collections | |
| print("goal_done (noul) items:", collections.Counter(i["label"] for i in items if i["qid"] == "goal_done")) | |
| print("operation label counts:", dict(collections.Counter(i["gold_op"] for i in items if i["qid"] == "operation"))) | |
| print(f"train items {len(items)} (op {sum(i['qid']=='operation' for i in items)}, target {sum(i['qid']!='operation' for i in items)}), " | |
| f"eval cases {len(ev)}, skipped {n_skip}, seq len mean {sum(lens)/len(lens):.0f} max {max(lens)}") | |
| if __name__ == "__main__": | |
| main() | |