ClarkBear commited on
Commit
1ab77a7
·
verified ·
1 Parent(s): b885a4d

Upload folder using huggingface_hub

Browse files
examples/README.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Examples
2
+
3
+ Run the merged model with Transformers:
4
+
5
+ ```bash
6
+ python examples/run_transformers.py \
7
+ --model-id ClarkBear/gemma4-e2b-mobile-actions-200 \
8
+ --prompt "Turn on the flashlight"
9
+ ```
10
+
11
+ Use a local checkout:
12
+
13
+ ```bash
14
+ python examples/run_transformers.py \
15
+ --model-id . \
16
+ --prompt "Open wifi settings"
17
+ ```
18
+
19
+ The script prints the raw generation and the first parsed tool call.
20
+
examples/prompts.jsonl ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {"prompt": "Turn on the flashlight"}
2
+ {"prompt": "Turn off the flashlight"}
3
+ {"prompt": "Open wifi settings"}
4
+ {"prompt": "Show me Central Park on the map"}
5
+ {"prompt": "Send an email to Alex saying I am running late"}
6
+ {"prompt": "Create a calendar event tomorrow at 3pm called dentist appointment"}
7
+
examples/run_transformers.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run the merged Gemma 4 Mobile Actions model with Transformers."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ from typing import Any
10
+
11
+ import torch
12
+ from transformers import AutoModelForCausalLM, AutoProcessor, AutoTokenizer, pipeline
13
+
14
+
15
+ TOOL_CALL_RE = re.compile(
16
+ r"<\|tool_call\>call:(\w+)\{(.*?)\}<(?:tool_call|tool)\|>",
17
+ re.DOTALL,
18
+ )
19
+ STRING_ARG_RE = re.compile(r"(\w+):<\|\"\|>(.*?)<\|\"\|>", re.DOTALL)
20
+ PLAIN_ARG_RE = re.compile(r"(\w+):(None|True|False|-?\d+(?:\.\d+)?)")
21
+ QUOTED_VALUE_RE = re.compile(r":<\|\"\|>.*?<\|\"\|>", re.DOTALL)
22
+
23
+
24
+ TOOLS: list[dict[str, Any]] = [
25
+ {
26
+ "type": "function",
27
+ "function": {
28
+ "name": "turn_on_flashlight",
29
+ "description": "Turns on the device flashlight.",
30
+ "parameters": {"type": "object", "properties": {}, "required": []},
31
+ },
32
+ },
33
+ {
34
+ "type": "function",
35
+ "function": {
36
+ "name": "turn_off_flashlight",
37
+ "description": "Turns off the device flashlight.",
38
+ "parameters": {"type": "object", "properties": {}, "required": []},
39
+ },
40
+ },
41
+ {
42
+ "type": "function",
43
+ "function": {
44
+ "name": "open_wifi_settings",
45
+ "description": "Opens the device Wi-Fi settings screen.",
46
+ "parameters": {"type": "object", "properties": {}, "required": []},
47
+ },
48
+ },
49
+ {
50
+ "type": "function",
51
+ "function": {
52
+ "name": "show_map",
53
+ "description": "Shows a location on the map.",
54
+ "parameters": {
55
+ "type": "object",
56
+ "properties": {"query": {"type": "string"}},
57
+ "required": ["query"],
58
+ },
59
+ },
60
+ },
61
+ {
62
+ "type": "function",
63
+ "function": {
64
+ "name": "send_email",
65
+ "description": "Composes an email.",
66
+ "parameters": {
67
+ "type": "object",
68
+ "properties": {
69
+ "recipient": {"type": "string"},
70
+ "subject": {"type": "string"},
71
+ "body": {"type": "string"},
72
+ },
73
+ "required": ["recipient", "body"],
74
+ },
75
+ },
76
+ },
77
+ {
78
+ "type": "function",
79
+ "function": {
80
+ "name": "create_calendar_event",
81
+ "description": "Creates a calendar event.",
82
+ "parameters": {
83
+ "type": "object",
84
+ "properties": {
85
+ "title": {"type": "string"},
86
+ "start_datetime": {"type": "string"},
87
+ "end_datetime": {"type": "string"},
88
+ },
89
+ "required": ["title", "start_datetime"],
90
+ },
91
+ },
92
+ },
93
+ ]
94
+
95
+
96
+ def parse_args() -> argparse.Namespace:
97
+ parser = argparse.ArgumentParser(description=__doc__)
98
+ parser.add_argument("--model-id", default="ClarkBear/gemma4-e2b-mobile-actions-200")
99
+ parser.add_argument("--prompt", required=True)
100
+ parser.add_argument("--system", default="You are a mobile assistant that calls tools.")
101
+ parser.add_argument("--max-new-tokens", type=int, default=160)
102
+ parser.add_argument("--dtype", choices=["auto", "bfloat16", "float16", "float32"], default="auto")
103
+ return parser.parse_args()
104
+
105
+
106
+ def load_processor(model_id: str):
107
+ try:
108
+ return AutoProcessor.from_pretrained(model_id)
109
+ except Exception:
110
+ return AutoTokenizer.from_pretrained(model_id)
111
+
112
+
113
+ def tokenizer_from_processor(processor):
114
+ return getattr(processor, "tokenizer", processor)
115
+
116
+
117
+ def torch_dtype(name: str) -> torch.dtype:
118
+ if name == "float16":
119
+ return torch.float16
120
+ if name == "float32":
121
+ return torch.float32
122
+ return torch.bfloat16
123
+
124
+
125
+ def device_map():
126
+ if torch.cuda.is_available():
127
+ return "auto"
128
+ if torch.backends.mps.is_available() and torch.backends.mps.is_built():
129
+ return {"": "mps"}
130
+ return {"": "cpu"}
131
+
132
+
133
+ def apply_template(processor, system: str, user_prompt: str) -> str:
134
+ messages = [
135
+ {"role": "system", "content": system},
136
+ {"role": "user", "content": user_prompt},
137
+ ]
138
+ attempts = [
139
+ {"tools": TOOLS, "add_generation_prompt": True, "enable_thinking": False},
140
+ {"tools": TOOLS, "add_generation_prompt": True},
141
+ {"add_generation_prompt": True, "enable_thinking": False},
142
+ {"add_generation_prompt": True},
143
+ ]
144
+ last_error: Exception | None = None
145
+ for kwargs in attempts:
146
+ try:
147
+ return processor.apply_chat_template(messages, tokenize=False, **kwargs)
148
+ except TypeError as exc:
149
+ last_error = exc
150
+ raise RuntimeError(f"Could not apply chat template: {last_error}")
151
+
152
+
153
+ def parse_scalar(value: str) -> Any:
154
+ if value == "None":
155
+ return None
156
+ if value == "True":
157
+ return True
158
+ if value == "False":
159
+ return False
160
+ if "." in value:
161
+ return float(value)
162
+ return int(value)
163
+
164
+
165
+ def parse_tool_call(text: str) -> dict[str, Any] | None:
166
+ match = TOOL_CALL_RE.search(text)
167
+ if not match:
168
+ return None
169
+ name, body = match.group(1), match.group(2)
170
+ args: dict[str, Any] = {}
171
+ for key, value in STRING_ARG_RE.findall(body):
172
+ args[key] = value
173
+ body_without_strings = QUOTED_VALUE_RE.sub("", body)
174
+ for key, value in PLAIN_ARG_RE.findall(body_without_strings):
175
+ args.setdefault(key, parse_scalar(value))
176
+ return {"name": name, "args": args, "raw": match.group(0)}
177
+
178
+
179
+ def main() -> None:
180
+ args = parse_args()
181
+ processor = load_processor(args.model_id)
182
+ tokenizer = tokenizer_from_processor(processor)
183
+ prompt = apply_template(processor, args.system, args.prompt)
184
+
185
+ model = AutoModelForCausalLM.from_pretrained(
186
+ args.model_id,
187
+ dtype=torch_dtype(args.dtype),
188
+ device_map=device_map(),
189
+ )
190
+ generator = pipeline(
191
+ "text-generation",
192
+ model=model,
193
+ tokenizer=tokenizer,
194
+ clean_up_tokenization_spaces=False,
195
+ )
196
+ output = generator(
197
+ prompt,
198
+ max_new_tokens=args.max_new_tokens,
199
+ do_sample=False,
200
+ )[0]["generated_text"]
201
+ generated = output[len(prompt) :]
202
+
203
+ print("=== Generated ===")
204
+ print(generated.strip())
205
+ print("\n=== Parsed Tool Call ===")
206
+ print(json.dumps(parse_tool_call(generated), indent=2, ensure_ascii=False))
207
+
208
+
209
+ if __name__ == "__main__":
210
+ main()
211
+