varsshancoder commited on
Commit
f576972
·
verified ·
1 Parent(s): baf7003

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +413 -145
app.py CHANGED
@@ -33,18 +33,34 @@ CUSTOM_API_KEY = os.getenv("CUSTOM_API_KEY")
33
  if not CUSTOM_API_KEY:
34
  raise RuntimeError(
35
  "CUSTOM_API_KEY is not configured. "
36
- "Add CUSTOM_API_KEY in Hugging Face Space "
37
- "Settings > Secrets."
38
  )
39
 
40
 
41
  # ============================================================
42
- # APP
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  # ============================================================
44
 
45
  app = FastAPI(
46
  title=APP_NAME,
47
- version="3.0.0",
48
  )
49
 
50
  app.add_middleware(
@@ -91,12 +107,16 @@ def verify_api_key(
91
  # MODEL
92
  # ============================================================
93
 
94
- print("=" * 65)
95
  print("Starting Varsh LLM")
96
- print("=" * 65)
97
 
98
  print(f"Model repository: {MODEL_REPO}")
99
  print(f"Model file: {MODEL_FILE}")
 
 
 
 
100
  print("Checking/downloading model...")
101
 
102
  model_path = hf_hub_download(
@@ -110,10 +130,13 @@ print("Loading Qwen 2.5 Coder 7B...")
110
  llm = Llama(
111
  model_path=model_path,
112
 
113
- # 2 vCPU / 16 GB RAM Space
114
- n_ctx=4096,
 
115
  n_threads=2,
116
  n_threads_batch=2,
 
 
117
  n_batch=256,
118
 
119
  use_mmap=True,
@@ -124,19 +147,21 @@ llm = Llama(
124
 
125
  print("Model loaded successfully.")
126
 
127
- print("=" * 65)
128
  print("VARSH LLM API READY")
129
- print("=" * 65)
130
  print(f"Listening on: {HOST}:{PORT}")
131
  print("Root endpoint: /")
132
  print("Models endpoint: /v1/models")
133
  print("Chat endpoint: /v1/chat/completions")
134
  print("Authentication: Bearer API key")
135
  print("Streaming: enabled")
136
- print("=" * 65)
 
137
 
138
 
139
- # llama.cpp should not receive simultaneous generations
 
140
  generation_lock = asyncio.Lock()
141
 
142
 
@@ -145,42 +170,13 @@ generation_lock = asyncio.Lock()
145
  # ============================================================
146
 
147
  def content_to_text(content: Any) -> str:
148
- """
149
- Convert OpenAI/Claude-style content into a plain string.
150
-
151
- Examples:
152
-
153
- "hello"
154
-
155
- becomes:
156
-
157
- "hello"
158
-
159
- And:
160
-
161
- [
162
- {
163
- "type": "text",
164
- "text": "hello"
165
- }
166
- ]
167
-
168
- becomes:
169
-
170
- "hello"
171
-
172
- Image/audio blocks are ignored because this model is
173
- text-only.
174
- """
175
 
176
  if content is None:
177
  return ""
178
 
179
- # Normal string
180
  if isinstance(content, str):
181
  return content
182
 
183
- # OpenAI content array
184
  if isinstance(content, list):
185
 
186
  parts = []
@@ -196,40 +192,39 @@ def content_to_text(content: Any) -> str:
196
 
197
  item_type = item.get("type")
198
 
199
- # Standard text block
200
  if item_type == "text":
201
- text = item.get("text")
 
202
 
203
  if isinstance(text, str):
204
  parts.append(text)
205
 
206
  continue
207
 
208
- # Some clients may omit type
209
- if isinstance(item.get("text"), str):
210
- parts.append(item["text"])
211
-
212
- continue
213
-
214
- # Ignore image/audio content.
215
- if item_type in (
216
- "image",
217
- "image_url",
218
- "input_image",
219
- "audio",
220
- "input_audio",
221
  ):
222
- continue
 
 
223
 
224
  return "\n".join(parts)
225
 
226
- # Dict content
227
  if isinstance(content, dict):
228
 
229
- if isinstance(content.get("text"), str):
 
 
 
230
  return content["text"]
231
 
232
- if isinstance(content.get("content"), str):
 
 
 
233
  return content["content"]
234
 
235
  return json.dumps(
@@ -245,10 +240,6 @@ def content_to_text(content: Any) -> str:
245
  # ============================================================
246
 
247
  def normalize_messages(messages: Any) -> list:
248
- """
249
- Convert all incoming messages into llama.cpp-safe
250
- text-only messages.
251
- """
252
 
253
  if not isinstance(messages, list):
254
  raise HTTPException(
@@ -278,34 +269,28 @@ def normalize_messages(messages: Any) -> list:
278
  )
279
  )
280
 
281
- # IMPORTANT:
282
- # llama.cpp must receive string content.
283
- normalized_message = {
284
  "role": role,
285
  "content": content,
286
  }
287
 
288
- # Preserve name when present.
289
  if isinstance(
290
  message.get("name"),
291
  str,
292
  ):
293
- normalized_message["name"] = (
294
  message["name"]
295
  )
296
 
297
- # Preserve tool call ID if present.
298
  if isinstance(
299
  message.get("tool_call_id"),
300
  str,
301
  ):
302
- normalized_message["tool_call_id"] = (
303
  message["tool_call_id"]
304
  )
305
 
306
- result.append(
307
- normalized_message
308
- )
309
 
310
  if not result:
311
  raise HTTPException(
@@ -317,39 +302,304 @@ def normalize_messages(messages: Any) -> list:
317
 
318
 
319
  # ============================================================
320
- # PARAMETERS
321
  # ============================================================
322
 
323
- def safe_float(
324
- value: Any,
325
- default: float,
326
- ) -> float:
 
327
 
328
  try:
329
- return float(value)
330
- except Exception:
331
- return default
332
 
 
 
 
333
 
334
- def safe_int(
335
- value: Any,
336
- default: int,
337
- ) -> int:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
- try:
340
- return int(value)
341
  except Exception:
342
- return default
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
 
345
  # ============================================================
346
- # ID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  # ============================================================
348
 
349
  def make_id():
350
  return f"chatcmpl-{uuid.uuid4().hex}"
351
 
352
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  # ============================================================
354
  # ROOT
355
  # ============================================================
@@ -359,12 +609,13 @@ async def root():
359
 
360
  return {
361
  "status": "healthy",
362
- "service": "Varsh LLM",
363
  "security": "enabled",
364
  "model": MODEL_ID,
365
  "endpoint": "/v1/chat/completions",
366
  "models_endpoint": "/v1/models",
367
  "streaming": True,
 
368
  }
369
 
370
 
@@ -378,6 +629,7 @@ async def health():
378
  return {
379
  "status": "ok",
380
  "model": MODEL_ID,
 
381
  }
382
 
383
 
@@ -405,7 +657,7 @@ async def models():
405
 
406
 
407
  # ============================================================
408
- # CHAT COMPLETIONS
409
  # ============================================================
410
 
411
  @app.post(
@@ -417,7 +669,7 @@ async def chat_completions(
417
  ):
418
 
419
  # --------------------------------------------------------
420
- # Parse JSON
421
  # --------------------------------------------------------
422
 
423
  try:
@@ -455,10 +707,7 @@ async def chat_completions(
455
  MODEL_ID,
456
  )
457
 
458
- if requested_model in (
459
- MODEL_ID,
460
- f"varsh/{MODEL_ID}",
461
- ):
462
  requested_model = MODEL_ID
463
 
464
  # --------------------------------------------------------
@@ -496,7 +745,6 @@ async def chat_completions(
496
  )
497
  )
498
 
499
- # Reasonable limits for your CPU Space.
500
  temperature = max(
501
  0.0,
502
  min(
@@ -513,18 +761,48 @@ async def chat_completions(
513
  ),
514
  )
515
 
516
- max_tokens = max(
517
- 1,
518
- min(
519
- max_tokens,
520
- 4096,
521
- ),
522
- )
523
-
524
  # --------------------------------------------------------
525
- # STREAMING
526
  # --------------------------------------------------------
527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
  if stream:
529
 
530
  chat_id = make_id()
@@ -533,13 +811,7 @@ async def chat_completions(
533
  async def stream_generator():
534
 
535
  # ------------------------------------------------
536
- # FIRST EVENT
537
- #
538
- # Send this BEFORE model generation.
539
- #
540
- # This is important for Claude Code because the
541
- # proxy/client must receive a real SSE event before
542
- # waiting for the CPU model.
543
  # ------------------------------------------------
544
 
545
  first_chunk = {
@@ -570,18 +842,16 @@ async def chat_completions(
570
 
571
  try:
572
 
573
- # --------------------------------------------
574
- # Generate
575
- # --------------------------------------------
576
-
577
  async with generation_lock:
578
 
579
- result = llm.create_chat_completion(
580
- messages=messages,
581
- temperature=temperature,
582
- top_p=top_p,
583
- max_tokens=max_tokens,
584
- stream=True,
 
 
585
  )
586
 
587
  for chunk in result:
@@ -609,11 +879,6 @@ async def chat_completions(
609
  if content is None:
610
  content = ""
611
 
612
- # ------------------------------------
613
- # Every streamed event must be valid
614
- # SSE JSON.
615
- # ------------------------------------
616
-
617
  output_chunk = {
618
  "id": chat_id,
619
  "object": "chat.completion.chunk",
@@ -641,9 +906,9 @@ async def chat_completions(
641
  + "\n\n"
642
  )
643
 
644
- # --------------------------------------------
645
- # Final chunk
646
- # --------------------------------------------
647
 
648
  final_chunk = {
649
  "id": chat_id,
@@ -673,7 +938,7 @@ async def chat_completions(
673
  except Exception as exc:
674
 
675
  print(
676
- "STREAM GENERATION ERROR:",
677
  repr(exc),
678
  )
679
 
@@ -687,9 +952,10 @@ async def chat_completions(
687
  "index": 0,
688
  "delta": {
689
  "content": (
690
- f"\n[Generation error: "
691
- f"{str(exc)}]"
692
- ),
 
693
  },
694
  "finish_reason": "stop",
695
  }
@@ -718,19 +984,21 @@ async def chat_completions(
718
  )
719
 
720
  # ========================================================
721
- # NON-STREAMING
722
  # ========================================================
723
 
724
  try:
725
 
726
  async with generation_lock:
727
 
728
- result = llm.create_chat_completion(
729
- messages=messages,
730
- temperature=temperature,
731
- top_p=top_p,
732
- max_tokens=max_tokens,
733
- stream=False,
 
 
734
  )
735
 
736
  choices = result.get(
@@ -786,7 +1054,7 @@ async def chat_completions(
786
  "usage": {
787
  "prompt_tokens": usage.get(
788
  "prompt_tokens",
789
- 0,
790
  ),
791
  "completion_tokens": usage.get(
792
  "completion_tokens",
@@ -794,7 +1062,7 @@ async def chat_completions(
794
  ),
795
  "total_tokens": usage.get(
796
  "total_tokens",
797
- 0,
798
  ),
799
  },
800
  }
 
33
  if not CUSTOM_API_KEY:
34
  raise RuntimeError(
35
  "CUSTOM_API_KEY is not configured. "
36
+ "Add CUSTOM_API_KEY in Hugging Face Space Settings > Secrets."
 
37
  )
38
 
39
 
40
  # ============================================================
41
+ # CONTEXT SETTINGS
42
+ # ============================================================
43
+
44
+ # 16 GB RAM + 7B Q4 CPU deployment.
45
+ # 8192 is a safer target than 16384.
46
+ CONTEXT_SIZE = 8192
47
+
48
+ # Reserve room for the model's answer.
49
+ RESERVED_OUTPUT_TOKENS = 1024
50
+
51
+ # Maximum amount of input context we allow.
52
+ MAX_INPUT_TOKENS = (
53
+ CONTEXT_SIZE - RESERVED_OUTPUT_TOKENS
54
+ )
55
+
56
+
57
+ # ============================================================
58
+ # FASTAPI
59
  # ============================================================
60
 
61
  app = FastAPI(
62
  title=APP_NAME,
63
+ version="4.0.0",
64
  )
65
 
66
  app.add_middleware(
 
107
  # MODEL
108
  # ============================================================
109
 
110
+ print("=" * 70)
111
  print("Starting Varsh LLM")
112
+ print("=" * 70)
113
 
114
  print(f"Model repository: {MODEL_REPO}")
115
  print(f"Model file: {MODEL_FILE}")
116
+ print(f"Context size: {CONTEXT_SIZE}")
117
+ print(f"Max input: {MAX_INPUT_TOKENS}")
118
+ print(f"Reserved output: {RESERVED_OUTPUT_TOKENS}")
119
+
120
  print("Checking/downloading model...")
121
 
122
  model_path = hf_hub_download(
 
130
  llm = Llama(
131
  model_path=model_path,
132
 
133
+ # CPU deployment
134
+ n_ctx=CONTEXT_SIZE,
135
+
136
  n_threads=2,
137
  n_threads_batch=2,
138
+
139
+ # Moderate batch for 2 vCPU.
140
  n_batch=256,
141
 
142
  use_mmap=True,
 
147
 
148
  print("Model loaded successfully.")
149
 
150
+ print("=" * 70)
151
  print("VARSH LLM API READY")
152
+ print("=" * 70)
153
  print(f"Listening on: {HOST}:{PORT}")
154
  print("Root endpoint: /")
155
  print("Models endpoint: /v1/models")
156
  print("Chat endpoint: /v1/chat/completions")
157
  print("Authentication: Bearer API key")
158
  print("Streaming: enabled")
159
+ print(f"Context: {CONTEXT_SIZE}")
160
+ print("=" * 70)
161
 
162
 
163
+ # llama.cpp generation is serialized because this is
164
+ # a 2-vCPU CPU deployment.
165
  generation_lock = asyncio.Lock()
166
 
167
 
 
170
  # ============================================================
171
 
172
  def content_to_text(content: Any) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  if content is None:
175
  return ""
176
 
 
177
  if isinstance(content, str):
178
  return content
179
 
 
180
  if isinstance(content, list):
181
 
182
  parts = []
 
192
 
193
  item_type = item.get("type")
194
 
 
195
  if item_type == "text":
196
+
197
+ text = item.get("text", "")
198
 
199
  if isinstance(text, str):
200
  parts.append(text)
201
 
202
  continue
203
 
204
+ # Some clients use:
205
+ # {"text": "..."}
206
+ if isinstance(
207
+ item.get("text"),
208
+ str,
 
 
 
 
 
 
 
 
209
  ):
210
+ parts.append(
211
+ item["text"]
212
+ )
213
 
214
  return "\n".join(parts)
215
 
 
216
  if isinstance(content, dict):
217
 
218
+ if isinstance(
219
+ content.get("text"),
220
+ str,
221
+ ):
222
  return content["text"]
223
 
224
+ if isinstance(
225
+ content.get("content"),
226
+ str,
227
+ ):
228
  return content["content"]
229
 
230
  return json.dumps(
 
240
  # ============================================================
241
 
242
  def normalize_messages(messages: Any) -> list:
 
 
 
 
243
 
244
  if not isinstance(messages, list):
245
  raise HTTPException(
 
269
  )
270
  )
271
 
272
+ normalized = {
 
 
273
  "role": role,
274
  "content": content,
275
  }
276
 
 
277
  if isinstance(
278
  message.get("name"),
279
  str,
280
  ):
281
+ normalized["name"] = (
282
  message["name"]
283
  )
284
 
 
285
  if isinstance(
286
  message.get("tool_call_id"),
287
  str,
288
  ):
289
+ normalized["tool_call_id"] = (
290
  message["tool_call_id"]
291
  )
292
 
293
+ result.append(normalized)
 
 
294
 
295
  if not result:
296
  raise HTTPException(
 
302
 
303
 
304
  # ============================================================
305
+ # TOKEN COUNTING
306
  # ============================================================
307
 
308
+ def count_message_tokens(messages: list) -> int:
309
+ """
310
+ Use llama.cpp's actual tokenizer instead of estimating
311
+ tokens using character length.
312
+ """
313
 
314
  try:
 
 
 
315
 
316
+ # llama.cpp's chat template will ultimately create
317
+ # a prompt. We use a conservative approximation here.
318
+ total = 0
319
 
320
+ for message in messages:
321
+
322
+ role = message.get(
323
+ "role",
324
+ "",
325
+ )
326
+
327
+ content = message.get(
328
+ "content",
329
+ "",
330
+ )
331
+
332
+ text = (
333
+ f"<|im_start|>{role}\n"
334
+ f"{content}"
335
+ f"<|im_end|>\n"
336
+ )
337
+
338
+ tokens = llm.tokenize(
339
+ text.encode(
340
+ "utf-8"
341
+ ),
342
+ add_bos=False,
343
+ )
344
+
345
+ total += len(tokens)
346
+
347
+ # Assistant generation marker.
348
+ assistant_tokens = llm.tokenize(
349
+ b"<|im_start|>assistant\n",
350
+ add_bos=False,
351
+ )
352
+
353
+ total += len(
354
+ assistant_tokens
355
+ )
356
+
357
+ return total
358
 
 
 
359
  except Exception:
360
+
361
+ # Safe fallback.
362
+ characters = sum(
363
+ len(
364
+ str(
365
+ m.get(
366
+ "content",
367
+ "",
368
+ )
369
+ )
370
+ )
371
+ for m in messages
372
+ )
373
+
374
+ return max(
375
+ 1,
376
+ characters // 4,
377
+ )
378
 
379
 
380
  # ============================================================
381
+ # CONTEXT MANAGEMENT
382
+ # ============================================================
383
+
384
+ def fit_messages_to_context(
385
+ messages: list,
386
+ max_tokens: int,
387
+ ) -> tuple[list, int]:
388
+
389
+ if not messages:
390
+ return messages, 0
391
+
392
+ current_tokens = count_message_tokens(
393
+ messages
394
+ )
395
+
396
+ if current_tokens <= max_tokens:
397
+ return messages, current_tokens
398
+
399
+ print(
400
+ f"Context too large: "
401
+ f"{current_tokens} tokens > "
402
+ f"{max_tokens}. "
403
+ f"Trimming old messages."
404
+ )
405
+
406
+ # --------------------------------------------------------
407
+ # Preserve system messages.
408
+ # --------------------------------------------------------
409
+
410
+ system_messages = [
411
+ m
412
+ for m in messages
413
+ if m.get("role") == "system"
414
+ ]
415
+
416
+ non_system_messages = [
417
+ m
418
+ for m in messages
419
+ if m.get("role") != "system"
420
+ ]
421
+
422
+ # --------------------------------------------------------
423
+ # Keep newest messages first.
424
+ # --------------------------------------------------------
425
+
426
+ kept = list(system_messages)
427
+
428
+ # Add newest conversation messages backwards.
429
+ selected = []
430
+
431
+ for message in reversed(
432
+ non_system_messages
433
+ ):
434
+
435
+ candidate = (
436
+ system_messages
437
+ + list(reversed(
438
+ selected + [message]
439
+ ))
440
+ )
441
+
442
+ tokens = count_message_tokens(
443
+ candidate
444
+ )
445
+
446
+ if tokens <= max_tokens:
447
+ selected.append(message)
448
+ else:
449
+ break
450
+
451
+ selected.reverse()
452
+
453
+ final_messages = (
454
+ system_messages
455
+ + selected
456
+ )
457
+
458
+ final_tokens = count_message_tokens(
459
+ final_messages
460
+ )
461
+
462
+ # --------------------------------------------------------
463
+ # If system prompt itself is too large,
464
+ # truncate the system content.
465
+ # --------------------------------------------------------
466
+
467
+ if final_tokens > max_tokens:
468
+
469
+ print(
470
+ "System context is too large. "
471
+ "Truncating system message."
472
+ )
473
+
474
+ new_messages = []
475
+
476
+ remaining = max_tokens
477
+
478
+ for message in final_messages:
479
+
480
+ if (
481
+ message.get("role")
482
+ != "system"
483
+ ):
484
+ continue
485
+
486
+ content = message.get(
487
+ "content",
488
+ "",
489
+ )
490
+
491
+ # Tokenize system content.
492
+ tokens = llm.tokenize(
493
+ content.encode(
494
+ "utf-8"
495
+ ),
496
+ add_bos=False,
497
+ )
498
+
499
+ if len(tokens) <= remaining:
500
+
501
+ new_messages.append(
502
+ message
503
+ )
504
+
505
+ remaining -= len(tokens)
506
+
507
+ else:
508
+
509
+ # Approximate character cutoff.
510
+ ratio = (
511
+ remaining
512
+ / max(
513
+ len(tokens),
514
+ 1,
515
+ )
516
+ )
517
+
518
+ cutoff = max(
519
+ 1,
520
+ int(
521
+ len(content)
522
+ * ratio
523
+ ),
524
+ )
525
+
526
+ truncated = content[
527
+ :cutoff
528
+ ]
529
+
530
+ new_messages.append(
531
+ {
532
+ "role": "system",
533
+ "content": truncated,
534
+ }
535
+ )
536
+
537
+ remaining = 0
538
+ break
539
+
540
+ # Add as many newest messages as fit.
541
+ for message in selected:
542
+
543
+ candidate = (
544
+ new_messages
545
+ + [message]
546
+ )
547
+
548
+ if (
549
+ count_message_tokens(
550
+ candidate
551
+ )
552
+ <= max_tokens
553
+ ):
554
+ new_messages.append(
555
+ message
556
+ )
557
+
558
+ final_messages = new_messages
559
+
560
+ final_tokens = count_message_tokens(
561
+ final_messages
562
+ )
563
+
564
+ print(
565
+ f"Context after trimming: "
566
+ f"{final_tokens} tokens"
567
+ )
568
+
569
+ return (
570
+ final_messages,
571
+ final_tokens,
572
+ )
573
+
574
+
575
+ # ============================================================
576
+ # HELPERS
577
  # ============================================================
578
 
579
  def make_id():
580
  return f"chatcmpl-{uuid.uuid4().hex}"
581
 
582
 
583
+ def safe_int(
584
+ value,
585
+ default,
586
+ ):
587
+ try:
588
+ return int(value)
589
+ except Exception:
590
+ return default
591
+
592
+
593
+ def safe_float(
594
+ value,
595
+ default,
596
+ ):
597
+ try:
598
+ return float(value)
599
+ except Exception:
600
+ return default
601
+
602
+
603
  # ============================================================
604
  # ROOT
605
  # ============================================================
 
609
 
610
  return {
611
  "status": "healthy",
612
+ "service": APP_NAME,
613
  "security": "enabled",
614
  "model": MODEL_ID,
615
  "endpoint": "/v1/chat/completions",
616
  "models_endpoint": "/v1/models",
617
  "streaming": True,
618
+ "context_size": CONTEXT_SIZE,
619
  }
620
 
621
 
 
629
  return {
630
  "status": "ok",
631
  "model": MODEL_ID,
632
+ "context_size": CONTEXT_SIZE,
633
  }
634
 
635
 
 
657
 
658
 
659
  # ============================================================
660
+ # CHAT
661
  # ============================================================
662
 
663
  @app.post(
 
669
  ):
670
 
671
  # --------------------------------------------------------
672
+ # JSON
673
  # --------------------------------------------------------
674
 
675
  try:
 
707
  MODEL_ID,
708
  )
709
 
710
+ if requested_model == f"varsh/{MODEL_ID}":
 
 
 
711
  requested_model = MODEL_ID
712
 
713
  # --------------------------------------------------------
 
745
  )
746
  )
747
 
 
748
  temperature = max(
749
  0.0,
750
  min(
 
761
  ),
762
  )
763
 
 
 
 
 
 
 
 
 
764
  # --------------------------------------------------------
765
+ # Context management
766
  # --------------------------------------------------------
767
 
768
+ messages, prompt_tokens = (
769
+ fit_messages_to_context(
770
+ messages,
771
+ MAX_INPUT_TOKENS,
772
+ )
773
+ )
774
+
775
+ # Never request more than the remaining context.
776
+ available_output = (
777
+ CONTEXT_SIZE
778
+ - prompt_tokens
779
+ - 16
780
+ )
781
+
782
+ if available_output < 1:
783
+ raise HTTPException(
784
+ status_code=400,
785
+ detail=(
786
+ "Prompt is too large even after "
787
+ "context trimming."
788
+ ),
789
+ )
790
+
791
+ max_tokens = min(
792
+ max_tokens,
793
+ available_output,
794
+ )
795
+
796
+ print(
797
+ f"Request: prompt={prompt_tokens}, "
798
+ f"max_tokens={max_tokens}, "
799
+ f"stream={stream}"
800
+ )
801
+
802
+ # ========================================================
803
+ # STREAM
804
+ # ========================================================
805
+
806
  if stream:
807
 
808
  chat_id = make_id()
 
811
  async def stream_generator():
812
 
813
  # ------------------------------------------------
814
+ # Immediate first SSE event.
 
 
 
 
 
 
815
  # ------------------------------------------------
816
 
817
  first_chunk = {
 
842
 
843
  try:
844
 
 
 
 
 
845
  async with generation_lock:
846
 
847
+ result = (
848
+ llm.create_chat_completion(
849
+ messages=messages,
850
+ temperature=temperature,
851
+ top_p=top_p,
852
+ max_tokens=max_tokens,
853
+ stream=True,
854
+ )
855
  )
856
 
857
  for chunk in result:
 
879
  if content is None:
880
  content = ""
881
 
 
 
 
 
 
882
  output_chunk = {
883
  "id": chat_id,
884
  "object": "chat.completion.chunk",
 
906
  + "\n\n"
907
  )
908
 
909
+ # ------------------------------------------------
910
+ # Final event.
911
+ # ------------------------------------------------
912
 
913
  final_chunk = {
914
  "id": chat_id,
 
938
  except Exception as exc:
939
 
940
  print(
941
+ "STREAM ERROR:",
942
  repr(exc),
943
  )
944
 
 
952
  "index": 0,
953
  "delta": {
954
  "content": (
955
+ "\n[Generation error: "
956
+ + str(exc)
957
+ + "]"
958
+ )
959
  },
960
  "finish_reason": "stop",
961
  }
 
984
  )
985
 
986
  # ========================================================
987
+ # NON-STREAM
988
  # ========================================================
989
 
990
  try:
991
 
992
  async with generation_lock:
993
 
994
+ result = (
995
+ llm.create_chat_completion(
996
+ messages=messages,
997
+ temperature=temperature,
998
+ top_p=top_p,
999
+ max_tokens=max_tokens,
1000
+ stream=False,
1001
+ )
1002
  )
1003
 
1004
  choices = result.get(
 
1054
  "usage": {
1055
  "prompt_tokens": usage.get(
1056
  "prompt_tokens",
1057
+ prompt_tokens,
1058
  ),
1059
  "completion_tokens": usage.get(
1060
  "completion_tokens",
 
1062
  ),
1063
  "total_tokens": usage.get(
1064
  "total_tokens",
1065
+ prompt_tokens,
1066
  ),
1067
  },
1068
  }