Georgefifth commited on
Commit
a9f1b52
·
verified ·
1 Parent(s): 5fb8014

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +28 -6
  2. app.py +86 -0
  3. requirements.txt +6 -0
README.md CHANGED
@@ -1,13 +1,35 @@
1
  ---
2
  title: Tiny Browser Planner
3
- emoji: 📈
4
- colorFrom: pink
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.18.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Tiny Browser Planner
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 4.36.1
8
+ python_version: 3.12.0
9
  app_file: app.py
10
  pinned: false
11
  ---
12
 
13
+ # Tiny Browser Planner
14
+
15
+ A 1B model that plans browser actions — fine-tuned MiniCPM5-1B with LoRA.
16
+
17
+ **The core finding:** A 1B model can explain the correct browser action before it can reliably choose it.
18
+
19
+ - [Model on HF](https://huggingface.co/Georgefifth/tiny-browser-planner-reason)
20
+ - [Dataset on HF](https://huggingface.co/datasets/Georgefifth/tiny-browser-planner-reason-dataset)
21
+
22
+ ## Experiments
23
+
24
+ ### v1 → v4
25
+ Data scaling didn't help. 200 targeted hard examples outperformed 2000 generic ones.
26
+
27
+ ### Ablation: Action Space Paradox
28
+ Adding `back` to the action space created a powerful tool — but the model over-generalized it, using `back` for everything.
29
+
30
+ ### Reason-First
31
+ With just 40 reasoning examples (7.8s training), the model went from 4/12 → 10/12. The reasoning head eliminates heuristic shortcutting.
32
+
33
+ ## Usage
34
+
35
+ Enter a task and browsing history. The model shows its reasoning, then picks the next action.
app.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re, time, json, os, shutil, torch, gradio as gr
2
+ import tempfile
3
+ from transformers import AutoModelForCausalLM, AutoTokenizer
4
+ from peft import PeftModel
5
+ from huggingface_hub import snapshot_download
6
+
7
+ BASE_ID = "openbmb/MiniCPM5-1B"
8
+ ADAPTER_ID = "Georgefifth/tiny-browser-planner-reason"
9
+
10
+ print("Loading model (this may take a minute)...")
11
+ start = time.time()
12
+
13
+ # Download adapter and create clean config
14
+ adapter_dir = os.path.join(tempfile.gettempdir(), "adapter")
15
+ from huggingface_hub import snapshot_download
16
+ snapshot_download(repo_id="Georgefifth/tiny-browser-planner-reason", local_dir=adapter_dir)
17
+ with open(os.path.join(adapter_dir, "adapter_config.json")) as f:
18
+ raw_cfg = json.load(f)
19
+
20
+ KEEP = {"r","lora_alpha","lora_dropout","target_modules","bias","task_type","peft_type","inference_mode"}
21
+ clean_cfg = {k: v for k, v in json.load(open(os.path.join(adapter_dir, "adapter_config.json"))).items() if k in {"r","lora_alpha","lora_dropout","target_modules","bias","task_type","peft_type","inference_mode"}}
22
+
23
+ clean_dir = os.path.join(tempfile.gettempdir(), "clean_adapter")
24
+ os.makedirs(clean_dir, exist_ok=True)
25
+ import shutil
26
+ for fname in os.listdir(adapter_dir):
27
+ src = os.path.join(adapter_dir, fname)
28
+ dst = os.path.join(clean_dir, fname)
29
+ if os.path.isfile(src):
30
+ if fname == "adapter_config.json":
31
+ with open(dst, "w") as f:
32
+ json.dump({"r":16,"lora_alpha":16,"lora_dropout":0,"target_modules":["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],"bias":"none","task_type":"CAUSAL_LM","peft_type":"LORA","inference_mode":True}, f)
33
+ else:
34
+ shutil.copy2(src, dst)
35
+
36
+ model = AutoModelForCausalLM.from_pretrained("openbmb/MiniCPM5-1B", torch_dtype=torch.float16, trust_remote_code=True)
37
+ model = PeftModel.from_pretrained(model, clean_dir)
38
+ tokenizer = AutoTokenizer.from_pretrained("openbmb/MiniCPM5-1B", trust_remote_code=True)
39
+ if tokenizer.pad_token is None:
40
+ tokenizer.pad_token = tokenizer.eos_token
41
+ print(f"Ready! ({time.time()-start:.0f}s)")
42
+
43
+ ACTIONS = ["search", "open_page", "extract", "refine_search", "back", "finish"]
44
+
45
+ def predict(task, history_text):
46
+ if not task or not task.strip():
47
+ return "Error: task is empty", ""
48
+ history = [l.strip() for l in history_text.strip().split("\n") if l.strip()]
49
+ hist_str = "\n".join(history)
50
+ msgs = [
51
+ {"role": "system", "content": "You are a browser planner. First reason about the situation, then output the next action."},
52
+ {"role": "user", "content": f"Task: {task}\n\nHistory:\n{hist_str}\n\nWhat is the next action?"},
53
+ ]
54
+ prompt = tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
55
+ inputs = tokenizer(prompt, return_tensors="pt")
56
+ input_len = inputs["input_ids"].shape[1]
57
+ inputs.pop("token_type_ids", None)
58
+ outs = model.generate(**inputs, max_new_tokens=64, temperature=0.01, do_sample=False,
59
+ pad_token_id=tokenizer.eos_token_id)
60
+ output = tokenizer.decode(outs[0][input_len:], skip_special_tokens=True).strip()
61
+ reason_m = re.search(r"Reason:\s*(.+?)(?:\n|$)", output)
62
+ action_m = re.search(r"Action:\s*(\S+)", output)
63
+ reason = reason_m.group(1).strip() if reason_m else "?"
64
+ action = action_m.group(1).strip().lower() if action_m else "?"
65
+ if action not in ["search", "open_page", "extract", "refine_search", "back", "finish"]:
66
+ action = f"{action} (unknown)"
67
+ return reason, action
68
+
69
+ with gr.Blocks(title="Tiny Browser Planner", theme=gr.themes.Soft()) as demo:
70
+ gr.Markdown("""
71
+ # Tiny Browser Planner — Reason-First
72
+ MiniCPM5-1B + LoRA | Actions: `search`, `open_page`, `extract`, `refine_search`, `back`, `finish`
73
+ """)
74
+ with gr.Row():
75
+ with gr.Column(scale=2):
76
+ task = gr.Textbox(label="Task", placeholder="e.g. Find Apple stock price")
77
+ history = gr.Textbox(label="History (one action per line)", lines=5,
78
+ placeholder="[search] Search completed.\n[open_page] Page content here...")
79
+ btn = gr.Button("Predict", variant="primary")
80
+ with gr.Column(scale=1):
81
+ reason = gr.Textbox(label="Reason", lines=3, interactive=False)
82
+ action = gr.Textbox(label="Next Action", lines=1, interactive=False)
83
+ btn.click(fn=predict, inputs=[task, history], outputs=[reason, action])
84
+
85
+ if __name__ == "__main__":
86
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ transformers>=4.30.0
2
+ torch>=2.0.0
3
+ peft>=0.6.0
4
+ accelerate>=0.24.0
5
+ safetensors>=0.4.0
6
+ huggingface_hub>=0.20.0,<0.25.0