Bc-AI commited on
Commit
a98eb91
·
verified ·
1 Parent(s): bb7541a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1048 -93
app.py CHANGED
@@ -2,167 +2,1122 @@ import gradio as gr
2
  from gradio import Server
3
  import spaces
4
  import torch
 
5
  from transformers import (
6
  AutoTokenizer,
7
  AutoModelForCausalLM,
8
- BitsAndBytesConfig, # ← added
 
9
  )
10
 
11
- # ─────────────────────────────────────────────
12
- # 1. MODEL SETUP
13
- # ─────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  MODEL_ID = "Qwen/Qwen3.8-27B"
15
 
16
- # ── 4-bit quantisation config ──────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  bnb_config = BitsAndBytesConfig(
18
- load_in_4bit=True, # enable 4-bit loading
19
- bnb_4bit_quant_type="nf4", # NormalFloat4 — best quality/size trade-off
20
- bnb_4bit_compute_dtype=torch.bfloat16, # compute in bf16 for speed & stability
21
- bnb_4bit_use_double_quant=True, # nested quantisation → saves ~0.4 bits/param
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  )
23
 
24
- print(f"Loading tokenizer: {MODEL_ID}")
25
- tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- print(f"Loading model in 4-bit: {MODEL_ID}")
28
  model = AutoModelForCausalLM.from_pretrained(
29
  MODEL_ID,
30
- quantization_config=bnb_config, # ← replaces torch_dtype=float16
31
- device_map="auto", # ZeroGPU manages CUDA device
 
 
 
32
  trust_remote_code=True,
33
  )
 
34
  model.eval()
35
 
36
- # ─────────────────────────────────────────────
37
- # 2. INFERENCE FUNCTION (ZeroGPU decorated)
38
- # ─────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  @spaces.GPU(duration=120)
40
  def generate(
41
- prompt: str,
42
- system_prompt: str = "You are a helpful assistant created by Smilyai-Labs. You are called G1-Preview.",
 
 
43
  max_new_tokens: int = 512,
 
44
  temperature: float = 0.7,
 
45
  top_p: float = 0.9,
 
46
  do_sample: bool = True,
47
- ) -> str:
 
 
 
 
 
 
48
  """
49
- Generate a text response from Mira-1-Large.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- Args:
52
- prompt: The user message / prompt to send to the model.
53
- system_prompt: System-level instruction for the model.
54
- max_new_tokens: Maximum number of tokens to generate.
55
- temperature: Sampling temperature (higher = more creative).
56
- top_p: Nucleus sampling probability mass.
57
- do_sample: Whether to use sampling (True) or greedy decoding (False).
 
58
 
59
- Returns:
60
- The model's text response as a string.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  """
62
- messages = [
63
- {"role": "system", "content": system_prompt},
64
- {"role": "user", "content": prompt},
65
- ]
66
 
67
- text = tokenizer.apply_chat_template(
68
- messages,
69
- tokenize=False,
70
- add_generation_prompt=True,
 
 
 
 
 
71
  )
72
 
73
- inputs = tokenizer(text, return_tensors="pt").to(model.device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- with torch.no_grad():
76
- output_ids = model.generate(
77
- **inputs,
78
  max_new_tokens=max_new_tokens,
 
79
  temperature=temperature,
 
80
  top_p=top_p,
 
81
  do_sample=do_sample,
82
- pad_token_id=tokenizer.eos_token_id,
83
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- new_tokens = output_ids[0][inputs["input_ids"].shape[1]:]
86
- response = tokenizer.decode(new_tokens, skip_special_tokens=True)
87
  return response
88
 
89
 
90
- # ─────────────────────────────────────────────
91
- # 3. STREAMING INFERENCE (SSE / token-by-token)
92
- # ─────────────────────────────────────────────
 
93
  @spaces.GPU(duration=120)
94
  def generate_stream(
95
- prompt: str,
96
- system_prompt: str = "You are a helpful assistant.",
 
 
97
  max_new_tokens: int = 512,
 
98
  temperature: float = 0.7,
 
99
  top_p: float = 0.9,
100
- ) -> str:
101
- """
102
- Stream a text response token-by-token from Mira-1-Large via SSE.
103
 
104
- Args:
105
- prompt: The user message / prompt.
106
- system_prompt: System-level instruction for the model.
107
- max_new_tokens: Maximum number of tokens to generate.
108
- temperature: Sampling temperature.
109
- top_p: Nucleus sampling probability mass.
110
 
111
- Yields:
112
- Partial response strings, growing with each new token.
113
  """
114
- from transformers import TextIteratorStreamer
115
- from threading import Thread
116
 
117
- messages = [
118
- {"role": "system", "content": system_prompt},
119
- {"role": "user", "content": prompt},
120
- ]
121
- text = tokenizer.apply_chat_template(
122
- messages, tokenize=False, add_generation_prompt=True
 
 
 
 
 
 
123
  )
124
- inputs = tokenizer(text, return_tensors="pt").to(model.device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  streamer = TextIteratorStreamer(
127
- tokenizer, skip_prompt=True, skip_special_tokens=True
 
 
 
 
128
  )
129
 
130
- gen_kwargs = dict(
131
- **inputs,
132
- max_new_tokens=max_new_tokens,
133
- temperature=temperature,
134
- top_p=top_p,
135
- do_sample=True,
136
- streamer=streamer,
137
- pad_token_id=tokenizer.eos_token_id,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  )
139
 
140
- thread = Thread(target=model.generate, kwargs=gen_kwargs)
141
  thread.start()
142
 
 
 
 
 
143
  partial = ""
 
144
  for new_text in streamer:
 
145
  partial += new_text
146
- yield partial
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- # ───────────────────────────────���─────────────
150
- # 4. gr.Server — REST API + OPTIONAL SWAGGER UI
151
- # ─────────────────────────────────────────────
152
  app = Server(
153
- title="Mira-1-Large API",
154
- summary="ZeroGPU-backed REST API for Smilyai-labs/Mira-1-large (Qwen arch)",
155
- version="1.0.0",
 
 
 
 
 
156
  )
157
 
158
- app.api(generate, name="generate")
159
- app.api(generate_stream, name="generate_stream")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  @app.get("/health")
162
  def health():
163
- return {"status": "ok", "model": MODEL_ID}
164
 
165
- # ─────────────────────────────────────────────
166
- # 5. LAUNCH
167
- # ─────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  app.launch()
 
2
  from gradio import Server
3
  import spaces
4
  import torch
5
+
6
  from transformers import (
7
  AutoTokenizer,
8
  AutoModelForCausalLM,
9
+ BitsAndBytesConfig,
10
+ TextIteratorStreamer,
11
  )
12
 
13
+ from threading import Thread
14
+
15
+
16
+ # ============================================================
17
+ # G1-PREVIEW API
18
+ # Smilyai-Labs
19
+ #
20
+ # Backwards compatible with the original prompt API.
21
+ #
22
+ # OLD FORMAT:
23
+ # {
24
+ # "prompt": "Hello"
25
+ # }
26
+ #
27
+ # -> thinking OFF by default
28
+ #
29
+ #
30
+ # NEW FORMAT:
31
+ # {
32
+ # "messages": [
33
+ # {"role": "user", "content": "Hello"}
34
+ # ]
35
+ # }
36
+ #
37
+ # -> xhigh reasoning by default
38
+ #
39
+ #
40
+ # Explicit reasoning_effort always overrides the default.
41
+ # ============================================================
42
+
43
+
44
+ # ============================================================
45
+ # 1. CONFIGURATION
46
+ # ============================================================
47
+
48
  MODEL_ID = "Qwen/Qwen3.8-27B"
49
 
50
+ VERSION = "2.1.0"
51
+
52
+ DEFAULT_SYSTEM_PROMPT = (
53
+ "You are a helpful assistant created by Smilyai-Labs. "
54
+ "You are called G1-Preview."
55
+ )
56
+
57
+
58
+ # ============================================================
59
+ # 2. REASONING MODES
60
+ # ============================================================
61
+
62
+ REASONING_MODES = {
63
+ # Maximum reasoning
64
+ "xhigh": {
65
+ "enable_thinking": True,
66
+ "temperature": 1.0,
67
+ "top_p": 0.95,
68
+ "max_token_multiplier": 1.00,
69
+ },
70
+
71
+ # Balanced reasoning
72
+ "medium": {
73
+ "enable_thinking": True,
74
+ "temperature": 0.9,
75
+ "top_p": 0.95,
76
+ "max_token_multiplier": 0.65,
77
+ },
78
+
79
+ # Fast reasoning
80
+ "low": {
81
+ "enable_thinking": True,
82
+ "temperature": 0.8,
83
+ "top_p": 0.90,
84
+ "max_token_multiplier": 0.35,
85
+ },
86
+
87
+ # Direct / non-thinking
88
+ "none": {
89
+ "enable_thinking": False,
90
+ "temperature": 0.7,
91
+ "top_p": 0.80,
92
+ "max_token_multiplier": 1.00,
93
+ },
94
+ }
95
+
96
+
97
+ # ============================================================
98
+ # 3. REASONING ALIASES
99
+ # ============================================================
100
+
101
+ REASONING_ALIASES = {
102
+ "high": "xhigh",
103
+ "maximum": "xhigh",
104
+ "max": "xhigh",
105
+
106
+ "mid": "medium",
107
+ "balanced": "medium",
108
+
109
+ "minimal": "low",
110
+
111
+ "off": "none",
112
+ "false": "none",
113
+ "disabled": "none",
114
+ "disable": "none",
115
+
116
+ "non-thinking": "none",
117
+ "non_thinking": "none",
118
+
119
+ "instruct": "none",
120
+ }
121
+
122
+
123
+ def normalize_reasoning_effort(reasoning_effort):
124
+ """
125
+ Normalize the requested reasoning mode.
126
+
127
+ Supported canonical values:
128
+
129
+ xhigh
130
+ medium
131
+ low
132
+ none
133
+ """
134
+
135
+ if reasoning_effort is None:
136
+ return None
137
+
138
+ effort = str(
139
+ reasoning_effort
140
+ ).strip().lower()
141
+
142
+ effort = REASONING_ALIASES.get(
143
+ effort,
144
+ effort,
145
+ )
146
+
147
+ if effort not in REASONING_MODES:
148
+ raise ValueError(
149
+ "Invalid reasoning_effort "
150
+ f"'{reasoning_effort}'. "
151
+
152
+ "Supported values: "
153
+ + ", ".join(REASONING_MODES.keys())
154
+ )
155
+
156
+ return effort
157
+
158
+
159
+ # ============================================================
160
+ # 4. QUANTIZATION
161
+ # ============================================================
162
+
163
  bnb_config = BitsAndBytesConfig(
164
+ load_in_4bit=True,
165
+
166
+ bnb_4bit_quant_type="nf4",
167
+
168
+ bnb_4bit_compute_dtype=torch.bfloat16,
169
+
170
+ bnb_4bit_use_double_quant=True,
171
+ )
172
+
173
+
174
+ # ============================================================
175
+ # 5. LOAD TOKENIZER
176
+ # ============================================================
177
+
178
+ print("=" * 70)
179
+ print("Loading G1-Preview")
180
+ print("=" * 70)
181
+
182
+ print(
183
+ f"Tokenizer: {MODEL_ID}"
184
  )
185
 
186
+ tokenizer = AutoTokenizer.from_pretrained(
187
+ MODEL_ID,
188
+ trust_remote_code=True,
189
+ )
190
+
191
+
192
+ # ============================================================
193
+ # 6. LOAD MODEL
194
+ # ============================================================
195
+
196
+ print(
197
+ f"Model: {MODEL_ID}"
198
+ )
199
+
200
+ print(
201
+ "Loading in 4-bit..."
202
+ )
203
 
 
204
  model = AutoModelForCausalLM.from_pretrained(
205
  MODEL_ID,
206
+
207
+ quantization_config=bnb_config,
208
+
209
+ device_map="auto",
210
+
211
  trust_remote_code=True,
212
  )
213
+
214
  model.eval()
215
 
216
+ print(
217
+ "Model loaded successfully."
218
+ )
219
+
220
+
221
+ # ============================================================
222
+ # 7. DETERMINE DEFAULT REASONING MODE
223
+ # ============================================================
224
+
225
+ def get_default_reasoning_effort(
226
+ messages,
227
+ ):
228
+ """
229
+ IMPORTANT BACKWARDS-COMPATIBILITY RULE:
230
+
231
+ Old prompt API:
232
+ thinking OFF
233
+
234
+ New messages API:
235
+ xhigh thinking
236
+ """
237
+
238
+ if messages is None:
239
+ return "none"
240
+
241
+ return "xhigh"
242
+
243
+
244
+ def resolve_reasoning_effort(
245
+ reasoning_effort,
246
+ messages,
247
+ ):
248
+ """
249
+ Explicit reasoning_effort always wins.
250
+
251
+ If omitted:
252
+
253
+ old prompt API -> none
254
+
255
+ messages API -> xhigh
256
+ """
257
+
258
+ if reasoning_effort is None:
259
+
260
+ reasoning_effort = (
261
+ get_default_reasoning_effort(
262
+ messages
263
+ )
264
+ )
265
+
266
+ reasoning_effort = (
267
+ normalize_reasoning_effort(
268
+ reasoning_effort
269
+ )
270
+ )
271
+
272
+ return reasoning_effort
273
+
274
+
275
+ # ============================================================
276
+ # 8. BUILD MESSAGES
277
+ # ============================================================
278
+
279
+ def build_messages(
280
+ prompt=None,
281
+ messages=None,
282
+ system_prompt=DEFAULT_SYSTEM_PROMPT,
283
+ ):
284
+ """
285
+ Supports both the legacy and modern formats.
286
+ """
287
+
288
+ # --------------------------------------------------------
289
+ # Modern messages format
290
+ # --------------------------------------------------------
291
+
292
+ if messages is not None:
293
+
294
+ if not isinstance(
295
+ messages,
296
+ list,
297
+ ):
298
+ raise ValueError(
299
+ "'messages' must be a list."
300
+ )
301
+
302
+ final_messages = []
303
+
304
+ for message in messages:
305
+
306
+ if not isinstance(
307
+ message,
308
+ dict,
309
+ ):
310
+ raise ValueError(
311
+ "Every message must be "
312
+ "an object."
313
+ )
314
+
315
+ role = message.get(
316
+ "role"
317
+ )
318
+
319
+ content = message.get(
320
+ "content"
321
+ )
322
+
323
+ if role is None:
324
+ raise ValueError(
325
+ "Every message requires "
326
+ "a 'role'."
327
+ )
328
+
329
+ if content is None:
330
+ content = ""
331
+
332
+ final_messages.append(
333
+ {
334
+ "role": role,
335
+ "content": content,
336
+ }
337
+ )
338
+
339
+ # ----------------------------------------------------
340
+ # Add default system prompt if needed
341
+ # ----------------------------------------------------
342
+
343
+ has_system = any(
344
+ message.get("role") == "system"
345
+ for message in final_messages
346
+ )
347
+
348
+ if not has_system:
349
+
350
+ final_messages.insert(
351
+ 0,
352
+ {
353
+ "role": "system",
354
+ "content": system_prompt,
355
+ },
356
+ )
357
+
358
+ return final_messages
359
+
360
+ # --------------------------------------------------------
361
+ # Legacy prompt format
362
+ # --------------------------------------------------------
363
+
364
+ return [
365
+ {
366
+ "role": "system",
367
+ "content": system_prompt,
368
+ },
369
+
370
+ {
371
+ "role": "user",
372
+ "content": prompt or "",
373
+ },
374
+ ]
375
+
376
+
377
+ # ============================================================
378
+ # 9. APPLY CHAT TEMPLATE
379
+ # ============================================================
380
+
381
+ def apply_chat_template(
382
+ messages,
383
+ reasoning_effort,
384
+ ):
385
+ """
386
+ Apply the model's chat template.
387
+
388
+ For Qwen-style models, enable_thinking controls
389
+ whether reasoning mode is enabled.
390
+ """
391
+
392
+ effort = normalize_reasoning_effort(
393
+ reasoning_effort
394
+ )
395
+
396
+ enable_thinking = (
397
+ REASONING_MODES[
398
+ effort
399
+ ]["enable_thinking"]
400
+ )
401
+
402
+ try:
403
+
404
+ # Preferred path
405
+ return tokenizer.apply_chat_template(
406
+ messages,
407
+
408
+ tokenize=False,
409
+
410
+ add_generation_prompt=True,
411
+
412
+ enable_thinking=enable_thinking,
413
+ )
414
+
415
+ except TypeError:
416
+
417
+ # Compatibility fallback
418
+ return tokenizer.apply_chat_template(
419
+ messages,
420
+
421
+ tokenize=False,
422
+
423
+ add_generation_prompt=True,
424
+ )
425
+
426
+
427
+ # ============================================================
428
+ # 10. CLEAN THINKING
429
+ # ============================================================
430
+
431
+ def remove_thinking(
432
+ response,
433
+ ):
434
+ """
435
+ Remove <think>...</think> from a response.
436
+ """
437
+
438
+ if not response:
439
+ return response
440
+
441
+ # Complete thinking section
442
+ if "<think>" in response:
443
+
444
+ if "</think>" in response:
445
+
446
+ response = response.split(
447
+ "</think>",
448
+ 1,
449
+ )[1]
450
+
451
+ else:
452
+
453
+ # Incomplete thinking section
454
+ response = response.split(
455
+ "<think>",
456
+ 1,
457
+ )[0]
458
+
459
+ return response.strip()
460
+
461
+
462
+ def clean_response(
463
+ response,
464
+ preserve_thinking,
465
+ ):
466
+ """
467
+ Return either the complete model output or only
468
+ the final answer.
469
+ """
470
+
471
+ if preserve_thinking:
472
+ return response
473
+
474
+ return remove_thinking(
475
+ response
476
+ )
477
+
478
+
479
+ # ============================================================
480
+ # 11. GENERATION SETTINGS
481
+ # ============================================================
482
+
483
+ def get_generation_settings(
484
+ reasoning_effort,
485
+ max_new_tokens,
486
+ temperature,
487
+ top_p,
488
+ do_sample,
489
+ ):
490
+ """
491
+ Create generation settings for the selected
492
+ reasoning mode.
493
+ """
494
+
495
+ effort = normalize_reasoning_effort(
496
+ reasoning_effort
497
+ )
498
+
499
+ config = REASONING_MODES[
500
+ effort
501
+ ]
502
+
503
+ # --------------------------------------------------------
504
+ # Token budget
505
+ # --------------------------------------------------------
506
+
507
+ effective_max_tokens = int(
508
+ max_new_tokens
509
+ * config["max_token_multiplier"]
510
+ )
511
+
512
+ # Never let the multiplier reduce this below 64
513
+ effective_max_tokens = max(
514
+ 64,
515
+ effective_max_tokens,
516
+ )
517
+
518
+ # --------------------------------------------------------
519
+ # Sampling
520
+ # --------------------------------------------------------
521
+
522
+ if effort == "none":
523
+
524
+ effective_temperature = (
525
+ temperature
526
+ )
527
+
528
+ effective_top_p = (
529
+ top_p
530
+ )
531
+
532
+ else:
533
+
534
+ effective_temperature = (
535
+ config["temperature"]
536
+ )
537
+
538
+ effective_top_p = (
539
+ config["top_p"]
540
+ )
541
+
542
+ settings = {
543
+ "max_new_tokens": effective_max_tokens,
544
+
545
+ "do_sample": do_sample,
546
+ }
547
+
548
+ # --------------------------------------------------------
549
+ # Only include sampling parameters when sampling
550
+ # is actually enabled.
551
+ # --------------------------------------------------------
552
+
553
+ if do_sample:
554
+
555
+ settings[
556
+ "temperature"
557
+ ] = effective_temperature
558
+
559
+ settings[
560
+ "top_p"
561
+ ] = effective_top_p
562
+
563
+ return settings
564
+
565
+
566
+ # ============================================================
567
+ # 12. GENERATE
568
+ # ============================================================
569
+
570
  @spaces.GPU(duration=120)
571
  def generate(
572
+ prompt: str = "",
573
+
574
+ system_prompt: str = DEFAULT_SYSTEM_PROMPT,
575
+
576
  max_new_tokens: int = 512,
577
+
578
  temperature: float = 0.7,
579
+
580
  top_p: float = 0.9,
581
+
582
  do_sample: bool = True,
583
+
584
+ reasoning_effort=None,
585
+
586
+ preserve_thinking: bool = False,
587
+
588
+ messages=None,
589
+ ):
590
  """
591
+ Main generation endpoint.
592
+
593
+ ------------------------------------------------------------
594
+ OLD FORMAT
595
+ ------------------------------------------------------------
596
+
597
+ {
598
+ "prompt": "Hello"
599
+ }
600
+
601
+ Default:
602
+ reasoning_effort = none
603
+
604
+
605
+ ------------------------------------------------------------
606
+ NEW FORMAT
607
+ ------------------------------------------------------------
608
 
609
+ {
610
+ "messages": [
611
+ {
612
+ "role": "user",
613
+ "content": "Hello"
614
+ }
615
+ ]
616
+ }
617
 
618
+ Default:
619
+ reasoning_effort = xhigh
620
+
621
+
622
+ ------------------------------------------------------------
623
+ EXPLICIT REASONING
624
+ ------------------------------------------------------------
625
+
626
+ {
627
+ "prompt": "Solve this.",
628
+ "reasoning_effort": "xhigh"
629
+ }
630
+
631
+
632
+ ------------------------------------------------------------
633
+ THINKING VISIBILITY
634
+ ------------------------------------------------------------
635
+
636
+ preserve_thinking=true
637
+
638
+ returns:
639
+
640
+ <think>
641
+ ...
642
+ </think>
643
+
644
+ final answer
645
+
646
+
647
+ preserve_thinking=false
648
+
649
+ returns:
650
+
651
+ final answer
652
  """
 
 
 
 
653
 
654
+ # --------------------------------------------------------
655
+ # Resolve reasoning mode
656
+ # --------------------------------------------------------
657
+
658
+ reasoning_effort = (
659
+ resolve_reasoning_effort(
660
+ reasoning_effort,
661
+ messages,
662
+ )
663
  )
664
 
665
+ # --------------------------------------------------------
666
+ # Build messages
667
+ # --------------------------------------------------------
668
+
669
+ final_messages = build_messages(
670
+ prompt=prompt,
671
+
672
+ messages=messages,
673
+
674
+ system_prompt=system_prompt,
675
+ )
676
+
677
+ # --------------------------------------------------------
678
+ # Apply template
679
+ # --------------------------------------------------------
680
+
681
+ text = apply_chat_template(
682
+ final_messages,
683
+
684
+ reasoning_effort,
685
+ )
686
+
687
+ # --------------------------------------------------------
688
+ # Tokenize
689
+ # --------------------------------------------------------
690
+
691
+ inputs = tokenizer(
692
+ text,
693
+
694
+ return_tensors="pt",
695
+ )
696
+
697
+ # --------------------------------------------------------
698
+ # Move inputs to model device
699
+ # --------------------------------------------------------
700
+
701
+ inputs = {
702
+ key: value.to(model.device)
703
+
704
+ for key, value in inputs.items()
705
+ }
706
+
707
+ # --------------------------------------------------------
708
+ # Generation settings
709
+ # --------------------------------------------------------
710
+
711
+ generation_settings = (
712
+ get_generation_settings(
713
+ reasoning_effort=reasoning_effort,
714
 
 
 
 
715
  max_new_tokens=max_new_tokens,
716
+
717
  temperature=temperature,
718
+
719
  top_p=top_p,
720
+
721
  do_sample=do_sample,
 
722
  )
723
+ )
724
+
725
+ generation_settings[
726
+ "pad_token_id"
727
+ ] = tokenizer.eos_token_id
728
+
729
+ # --------------------------------------------------------
730
+ # Generate
731
+ # --------------------------------------------------------
732
+
733
+ with torch.inference_mode():
734
+
735
+ output_ids = model.generate(
736
+ **inputs,
737
+
738
+ **generation_settings,
739
+ )
740
+
741
+ # --------------------------------------------------------
742
+ # Remove input tokens
743
+ # --------------------------------------------------------
744
+
745
+ input_length = (
746
+ inputs["input_ids"].shape[1]
747
+ )
748
+
749
+ new_tokens = output_ids[
750
+ 0,
751
+ input_length:
752
+ ]
753
+
754
+ # --------------------------------------------------------
755
+ # Decode
756
+ # --------------------------------------------------------
757
+
758
+ response = tokenizer.decode(
759
+ new_tokens,
760
+
761
+ skip_special_tokens=True,
762
+ )
763
+
764
+ # --------------------------------------------------------
765
+ # Hide thinking if requested
766
+ # --------------------------------------------------------
767
+
768
+ response = clean_response(
769
+ response,
770
+
771
+ preserve_thinking=preserve_thinking,
772
+ )
773
 
 
 
774
  return response
775
 
776
 
777
+ # ============================================================
778
+ # 13. STREAMING GENERATION
779
+ # ============================================================
780
+
781
  @spaces.GPU(duration=120)
782
  def generate_stream(
783
+ prompt: str = "",
784
+
785
+ system_prompt: str = DEFAULT_SYSTEM_PROMPT,
786
+
787
  max_new_tokens: int = 512,
788
+
789
  temperature: float = 0.7,
790
+
791
  top_p: float = 0.9,
 
 
 
792
 
793
+ do_sample: bool = True,
794
+
795
+ reasoning_effort=None,
796
+
797
+ preserve_thinking: bool = False,
 
798
 
799
+ messages=None,
800
+ ):
801
  """
802
+ Streaming generation endpoint.
 
803
 
804
+ Uses exactly the same reasoning rules as generate().
805
+ """
806
+
807
+ # --------------------------------------------------------
808
+ # Resolve reasoning
809
+ # --------------------------------------------------------
810
+
811
+ reasoning_effort = (
812
+ resolve_reasoning_effort(
813
+ reasoning_effort,
814
+ messages,
815
+ )
816
  )
817
+
818
+ # --------------------------------------------------------
819
+ # Build messages
820
+ # --------------------------------------------------------
821
+
822
+ final_messages = build_messages(
823
+ prompt=prompt,
824
+
825
+ messages=messages,
826
+
827
+ system_prompt=system_prompt,
828
+ )
829
+
830
+ # --------------------------------------------------------
831
+ # Apply template
832
+ # --------------------------------------------------------
833
+
834
+ text = apply_chat_template(
835
+ final_messages,
836
+
837
+ reasoning_effort,
838
+ )
839
+
840
+ # --------------------------------------------------------
841
+ # Tokenize
842
+ # --------------------------------------------------------
843
+
844
+ inputs = tokenizer(
845
+ text,
846
+
847
+ return_tensors="pt",
848
+ )
849
+
850
+ inputs = {
851
+ key: value.to(model.device)
852
+
853
+ for key, value in inputs.items()
854
+ }
855
+
856
+ # --------------------------------------------------------
857
+ # Generation settings
858
+ # --------------------------------------------------------
859
+
860
+ generation_settings = (
861
+ get_generation_settings(
862
+ reasoning_effort=reasoning_effort,
863
+
864
+ max_new_tokens=max_new_tokens,
865
+
866
+ temperature=temperature,
867
+
868
+ top_p=top_p,
869
+
870
+ do_sample=do_sample,
871
+ )
872
+ )
873
+
874
+ # --------------------------------------------------------
875
+ # Streamer
876
+ # --------------------------------------------------------
877
 
878
  streamer = TextIteratorStreamer(
879
+ tokenizer,
880
+
881
+ skip_prompt=True,
882
+
883
+ skip_special_tokens=True,
884
  )
885
 
886
+ generation_settings.update(
887
+ {
888
+ "streamer": streamer,
889
+
890
+ "pad_token_id": (
891
+ tokenizer.eos_token_id
892
+ ),
893
+ }
894
+ )
895
+
896
+ # --------------------------------------------------------
897
+ # Background generation
898
+ # --------------------------------------------------------
899
+
900
+ thread = Thread(
901
+ target=model.generate,
902
+
903
+ kwargs={
904
+ **inputs,
905
+
906
+ **generation_settings,
907
+ },
908
  )
909
 
 
910
  thread.start()
911
 
912
+ # --------------------------------------------------------
913
+ # Stream
914
+ # --------------------------------------------------------
915
+
916
  partial = ""
917
+
918
  for new_text in streamer:
919
+
920
  partial += new_text
 
921
 
922
+ if preserve_thinking:
923
+
924
+ yield partial
925
+
926
+ else:
927
+
928
+ yield remove_thinking(
929
+ partial
930
+ )
931
+
932
+
933
+ # ============================================================
934
+ # 14. OPENAI-STYLE CHAT
935
+ # ============================================================
936
+
937
+ @spaces.GPU(duration=120)
938
+ def chat(
939
+ messages,
940
+
941
+ model: str = MODEL_ID,
942
+
943
+ max_tokens: int = 512,
944
+
945
+ temperature: float = 0.7,
946
+
947
+ top_p: float = 0.9,
948
+
949
+ reasoning_effort=None,
950
+
951
+ preserve_thinking: bool = False,
952
+ ):
953
+ """
954
+ OpenAI-style chat endpoint.
955
+
956
+ Since this uses the modern messages format,
957
+ omitted reasoning_effort defaults to xhigh.
958
+ """
959
+
960
+ response = generate(
961
+ messages=messages,
962
+
963
+ max_new_tokens=max_tokens,
964
+
965
+ temperature=temperature,
966
+
967
+ top_p=top_p,
968
+
969
+ reasoning_effort=reasoning_effort,
970
+
971
+ preserve_thinking=preserve_thinking,
972
+ )
973
+
974
+ return {
975
+ "id": "g1-preview-response",
976
+
977
+ "object": "chat.completion",
978
+
979
+ "model": model,
980
+
981
+ "choices": [
982
+ {
983
+ "index": 0,
984
+
985
+ "message": {
986
+ "role": "assistant",
987
+
988
+ "content": response,
989
+ },
990
+
991
+ "finish_reason": "stop",
992
+ }
993
+ ],
994
+ }
995
+
996
+
997
+ # ============================================================
998
+ # 15. SERVER
999
+ # ============================================================
1000
 
 
 
 
1001
  app = Server(
1002
+ title="G1-Preview API",
1003
+
1004
+ summary=(
1005
+ "ZeroGPU-backed API for "
1006
+ "Smilyai-Labs G1-Preview"
1007
+ ),
1008
+
1009
+ version=VERSION,
1010
  )
1011
 
1012
+
1013
+ # ============================================================
1014
+ # 16. REGISTER API ENDPOINTS
1015
+ # ============================================================
1016
+
1017
+ # Legacy + modern generation
1018
+ app.api(
1019
+ generate,
1020
+
1021
+ name="generate",
1022
+ )
1023
+
1024
+
1025
+ # Streaming generation
1026
+ app.api(
1027
+ generate_stream,
1028
+
1029
+ name="generate_stream",
1030
+ )
1031
+
1032
+
1033
+ # OpenAI-style chat
1034
+ app.api(
1035
+ chat,
1036
+
1037
+ name="chat",
1038
+ )
1039
+
1040
+
1041
+ # ============================================================
1042
+ # 17. HEALTH CHECK
1043
+ # ============================================================
1044
 
1045
  @app.get("/health")
1046
  def health():
 
1047
 
1048
+ return {
1049
+ "status": "ok",
1050
+
1051
+ "model": MODEL_ID,
1052
+
1053
+ "version": VERSION,
1054
+
1055
+ "reasoning": {
1056
+ "supported": True,
1057
+
1058
+ "modes": [
1059
+ "xhigh",
1060
+ "medium",
1061
+ "low",
1062
+ "none",
1063
+ ],
1064
+
1065
+ "new_format_default": "xhigh",
1066
+
1067
+ "legacy_format_default": "none",
1068
+ },
1069
+
1070
+ "compatibility": {
1071
+ "legacy_prompt": True,
1072
+
1073
+ "messages": True,
1074
+
1075
+ "streaming": True,
1076
+
1077
+ "openai_style_chat": True,
1078
+ },
1079
+ }
1080
+
1081
+
1082
+ # ============================================================
1083
+ # 18. START SERVER
1084
+ # ============================================================
1085
+
1086
+ print()
1087
+ print("=" * 70)
1088
+ print("G1-PREVIEW API READY")
1089
+ print("=" * 70)
1090
+
1091
+ print(
1092
+ f"Model: {MODEL_ID}"
1093
+ )
1094
+
1095
+ print(
1096
+ f"Version: {VERSION}"
1097
+ )
1098
+
1099
+ print(
1100
+ "Reasoning modes: "
1101
+ "xhigh / medium / low / none"
1102
+ )
1103
+
1104
+ print(
1105
+ "Legacy prompt default: NONE"
1106
+ )
1107
+
1108
+ print(
1109
+ "Messages default: XHIGH"
1110
+ )
1111
+
1112
+ print(
1113
+ "Streaming: ENABLED"
1114
+ )
1115
+
1116
+ print(
1117
+ "OpenAI-style chat: ENABLED"
1118
+ )
1119
+
1120
+ print("=" * 70)
1121
+ print()
1122
+
1123
  app.launch()