varsshancoder commited on
Commit
1a1f0f3
·
verified ·
1 Parent(s): cb68347

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +455 -94
app.py CHANGED
@@ -1,11 +1,14 @@
1
  import os
2
  import time
3
  import uuid
 
 
4
 
5
  from fastapi import FastAPI, Request, HTTPException, Depends
6
  from fastapi.middleware.cors import CORSMiddleware
 
7
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
8
- from fastapi.responses import JSONResponse
9
  from huggingface_hub import hf_hub_download
10
  from llama_cpp import Llama
11
 
@@ -15,43 +18,51 @@ from llama_cpp import Llama
15
  # ============================================================
16
 
17
  APP_NAME = "Varsh LLM"
 
18
  MODEL_REPO = "Qwen/Qwen2.5-Coder-7B-Instruct-GGUF"
19
  MODEL_FILE = "qwen2.5-coder-7b-instruct-q4_k_m.gguf"
20
 
 
21
  MODEL_ID = "qwen2.5-coder-7b-instruct-q4_k_m"
22
 
23
- # IMPORTANT:
24
- # Set CUSTOM_API_KEY in:
25
- # Hugging Face Space → Settings → Secrets
26
- CUSTOM_API_KEY = os.getenv("CUSTOM_API_KEY")
27
-
28
- if not CUSTOM_API_KEY:
29
- raise RuntimeError(
30
- "CUSTOM_API_KEY is not configured. "
31
- "Add CUSTOM_API_KEY in Hugging Face Space Settings > Secrets."
32
- )
33
-
34
-
35
- # ============================================================
36
- # SERVER SETTINGS
37
- # ============================================================
38
-
39
  HOST = "0.0.0.0"
40
  PORT = int(os.getenv("PORT", "7860"))
41
 
42
- # Your current HF Space has 2 vCPU.
43
  # Keep this at 2 unless you upgrade the Space.
44
  N_THREADS = int(os.getenv("N_THREADS", "2"))
45
 
46
- # Prompt-processing batch size.
47
  N_BATCH = int(os.getenv("N_BATCH", "512"))
48
 
49
- # Context size.
50
  N_CTX = int(os.getenv("N_CTX", "4096"))
51
 
52
 
53
  # ============================================================
54
- # FASTAPI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  # ============================================================
56
 
57
  app = FastAPI(
@@ -61,6 +72,10 @@ app = FastAPI(
61
  )
62
 
63
 
 
 
 
 
64
  app.add_middleware(
65
  CORSMiddleware,
66
  allow_origins=["*"],
@@ -84,7 +99,9 @@ def verify_api_key(
84
  raise HTTPException(
85
  status_code=401,
86
  detail="Missing API key.",
87
- headers={"WWW-Authenticate": "Bearer"},
 
 
88
  )
89
 
90
  if credentials.scheme.lower() != "bearer":
@@ -112,6 +129,7 @@ print("=" * 70)
112
 
113
  print(f"Model repository : {MODEL_REPO}")
114
  print(f"Model file : {MODEL_FILE}")
 
115
  print(f"Threads : {N_THREADS}")
116
  print(f"Batch size : {N_BATCH}")
117
  print(f"Context size : {N_CTX}")
@@ -124,31 +142,62 @@ model_path = hf_hub_download(
124
  )
125
 
126
  print(f"Model path: {model_path}")
 
127
  print("Loading Qwen 2.5 Coder 7B...")
128
 
129
  llm = Llama(
130
  model_path=model_path,
 
 
131
  n_ctx=N_CTX,
 
 
132
  n_threads=N_THREADS,
 
 
133
  n_batch=N_BATCH,
 
 
 
 
134
  verbose=False,
135
  )
136
 
137
  print("Model loaded successfully.")
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  print("=" * 70)
140
  print("Varsh LLM API READY")
141
  print("=" * 70)
 
142
  print(f"Listening on: http://{HOST}:{PORT}")
143
  print("Root endpoint: /")
 
144
  print("Models endpoint: /v1/models")
145
  print("Chat endpoint: /v1/chat/completions")
146
  print("Authentication: Bearer API key")
 
147
  print("=" * 70)
148
 
149
 
150
  # ============================================================
151
- # ROOT / HEALTH
152
  # ============================================================
153
 
154
  @app.get("/")
@@ -160,19 +209,25 @@ def root():
160
  "model": MODEL_ID,
161
  "endpoint": "/v1/chat/completions",
162
  "models_endpoint": "/v1/models",
 
163
  }
164
 
165
 
 
 
 
 
166
  @app.get("/health")
167
  def health():
168
  return {
169
  "status": "healthy",
 
170
  "model": MODEL_ID,
171
  }
172
 
173
 
174
  # ============================================================
175
- # OPENAI-COMPATIBLE MODELS ENDPOINT
176
  # ============================================================
177
 
178
  @app.get(
@@ -193,11 +248,16 @@ def list_models():
193
  }
194
 
195
 
 
 
 
 
196
  @app.get(
197
  "/v1/models/{model_id}",
198
  dependencies=[Depends(verify_api_key)],
199
  )
200
  def get_model(model_id: str):
 
201
  if model_id != MODEL_ID:
202
  raise HTTPException(
203
  status_code=404,
@@ -212,6 +272,59 @@ def get_model(model_id: str):
212
  }
213
 
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  # ============================================================
216
  # CHAT COMPLETIONS
217
  # ============================================================
@@ -228,6 +341,7 @@ async def chat_completions(request: Request):
228
 
229
  try:
230
  body = await request.json()
 
231
  except Exception:
232
  raise HTTPException(
233
  status_code=400,
@@ -235,118 +349,356 @@ async def chat_completions(request: Request):
235
  )
236
 
237
  # --------------------------------------------------------
238
- # Extract parameters
239
  # --------------------------------------------------------
240
 
241
- requested_model = body.get("model", MODEL_ID)
 
 
 
242
 
243
  if requested_model != MODEL_ID:
 
244
  raise HTTPException(
245
  status_code=404,
246
  detail=f"Model '{requested_model}' not found.",
247
  )
248
 
249
- messages = body.get("messages")
 
 
250
 
251
- if not messages or not isinstance(messages, list):
252
- raise HTTPException(
253
- status_code=400,
254
- detail="The 'messages' field is required and must be a list.",
255
- )
256
 
257
- temperature = body.get("temperature", 0.2)
258
- max_tokens = body.get("max_tokens", 1024)
 
259
 
260
- # Optional OpenAI-compatible parameters
261
- top_p = body.get("top_p", 0.95)
262
- stop = body.get("stop", None)
 
263
 
264
- stream = body.get("stream", False)
 
 
 
265
 
266
- # Current implementation returns normal JSON.
267
- # Streaming can be added separately once basic routing is stable.
268
- if stream:
269
- raise HTTPException(
270
- status_code=400,
271
- detail="Streaming is not enabled on this deployment.",
272
- )
 
 
 
 
 
 
 
273
 
274
  # --------------------------------------------------------
275
- # Sanitize messages
276
  # --------------------------------------------------------
277
 
278
- clean_messages = []
 
 
 
279
 
280
- for message in messages:
281
- if not isinstance(message, dict):
282
- continue
283
 
284
- role = message.get("role", "user")
285
- content = message.get("content", "")
 
 
286
 
287
- if content is None:
288
- content = ""
 
289
 
290
- # Handle normal string content.
291
- # For now, multimodal content arrays are not supported.
292
- if not isinstance(content, str):
293
- raise HTTPException(
294
- status_code=400,
295
- detail="Message content must be a string.",
296
- )
297
 
298
- clean_messages.append(
299
- {
300
- "role": role,
301
- "content": content,
302
- }
303
- )
304
 
305
- if not clean_messages:
306
- raise HTTPException(
307
- status_code=400,
308
- detail="No valid messages were provided.",
309
- )
310
 
311
- # --------------------------------------------------------
312
- # Generate response
313
- # --------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
- request_id = f"chatcmpl-{uuid.uuid4().hex}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
- created = int(time.time())
 
 
318
 
319
  start_time = time.perf_counter()
320
 
321
  try:
322
- response = llm.create_chat_completion(
323
- messages=clean_messages,
324
- temperature=float(temperature),
325
- max_tokens=int(max_tokens),
326
- top_p=float(top_p),
327
- stop=stop,
328
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
  except Exception as e:
331
- print(f"Inference error: {e}")
 
 
 
 
332
 
333
  raise HTTPException(
334
  status_code=500,
335
  detail=f"Model inference failed: {str(e)}",
336
  )
337
 
338
- generation_time = time.perf_counter() - start_time
 
 
 
339
 
340
  # --------------------------------------------------------
341
- # Extract generated text
342
  # --------------------------------------------------------
343
 
344
  try:
 
345
  choice = response["choices"][0]
346
 
347
- message = choice["message"]
 
 
 
348
 
349
- text_output = message.get("content", "")
 
 
 
350
 
351
  finish_reason = choice.get(
352
  "finish_reason",
@@ -354,18 +706,27 @@ async def chat_completions(request: Request):
354
  )
355
 
356
  except Exception as e:
357
- print(f"Response parsing error: {e}")
 
 
 
358
 
359
  raise HTTPException(
360
  status_code=500,
361
- detail="Invalid response returned by the model.",
 
 
 
362
  )
363
 
364
  # --------------------------------------------------------
365
  # Usage
366
  # --------------------------------------------------------
367
 
368
- usage = response.get("usage", {})
 
 
 
369
 
370
  prompt_tokens = usage.get(
371
  "prompt_tokens",
@@ -386,7 +747,7 @@ async def chat_completions(request: Request):
386
  f"Request {request_id} | "
387
  f"prompt={prompt_tokens} | "
388
  f"completion={completion_tokens} | "
389
- f"time={generation_time:.2f}s"
390
  )
391
 
392
  # --------------------------------------------------------
@@ -415,7 +776,7 @@ async def chat_completions(request: Request):
415
  "total_tokens": total_tokens,
416
  },
417
  "x_generation_time_seconds": round(
418
- generation_time,
419
  3,
420
  ),
421
  }
 
1
  import os
2
  import time
3
  import uuid
4
+ import json
5
+ import threading
6
 
7
  from fastapi import FastAPI, Request, HTTPException, Depends
8
  from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.responses import JSONResponse, StreamingResponse
10
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
11
+
12
  from huggingface_hub import hf_hub_download
13
  from llama_cpp import Llama
14
 
 
18
  # ============================================================
19
 
20
  APP_NAME = "Varsh LLM"
21
+
22
  MODEL_REPO = "Qwen/Qwen2.5-Coder-7B-Instruct-GGUF"
23
  MODEL_FILE = "qwen2.5-coder-7b-instruct-q4_k_m.gguf"
24
 
25
+ # This MUST exactly match the model ID expected by OmniRoute.
26
  MODEL_ID = "qwen2.5-coder-7b-instruct-q4_k_m"
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  HOST = "0.0.0.0"
29
  PORT = int(os.getenv("PORT", "7860"))
30
 
31
+ # Your current HF Space = 2 vCPU.
32
  # Keep this at 2 unless you upgrade the Space.
33
  N_THREADS = int(os.getenv("N_THREADS", "2"))
34
 
35
+ # llama.cpp prompt processing batch size.
36
  N_BATCH = int(os.getenv("N_BATCH", "512"))
37
 
38
+ # Context window.
39
  N_CTX = int(os.getenv("N_CTX", "4096"))
40
 
41
 
42
  # ============================================================
43
+ # API KEY
44
+ # ============================================================
45
+
46
+ # Set this in:
47
+ #
48
+ # Hugging Face Space
49
+ # -> Settings
50
+ # -> Secrets
51
+ # -> CUSTOM_API_KEY
52
+ #
53
+ # DO NOT hard-code the secret in this file.
54
+
55
+ CUSTOM_API_KEY = os.getenv("CUSTOM_API_KEY")
56
+
57
+ if not CUSTOM_API_KEY:
58
+ raise RuntimeError(
59
+ "CUSTOM_API_KEY is not configured. "
60
+ "Add CUSTOM_API_KEY in Hugging Face Space Settings > Secrets."
61
+ )
62
+
63
+
64
+ # ============================================================
65
+ # FASTAPI APPLICATION
66
  # ============================================================
67
 
68
  app = FastAPI(
 
72
  )
73
 
74
 
75
+ # ============================================================
76
+ # CORS
77
+ # ============================================================
78
+
79
  app.add_middleware(
80
  CORSMiddleware,
81
  allow_origins=["*"],
 
99
  raise HTTPException(
100
  status_code=401,
101
  detail="Missing API key.",
102
+ headers={
103
+ "WWW-Authenticate": "Bearer"
104
+ },
105
  )
106
 
107
  if credentials.scheme.lower() != "bearer":
 
129
 
130
  print(f"Model repository : {MODEL_REPO}")
131
  print(f"Model file : {MODEL_FILE}")
132
+ print(f"Model ID : {MODEL_ID}")
133
  print(f"Threads : {N_THREADS}")
134
  print(f"Batch size : {N_BATCH}")
135
  print(f"Context size : {N_CTX}")
 
142
  )
143
 
144
  print(f"Model path: {model_path}")
145
+
146
  print("Loading Qwen 2.5 Coder 7B...")
147
 
148
  llm = Llama(
149
  model_path=model_path,
150
+
151
+ # Context window
152
  n_ctx=N_CTX,
153
+
154
+ # CPU threads
155
  n_threads=N_THREADS,
156
+
157
+ # Prompt processing batch
158
  n_batch=N_BATCH,
159
+
160
+ # Let llama.cpp use its Qwen chat template
161
+ chat_format=None,
162
+
163
  verbose=False,
164
  )
165
 
166
  print("Model loaded successfully.")
167
 
168
+
169
+ # ============================================================
170
+ # GENERATION LOCK
171
+ # ============================================================
172
+
173
+ # The HF Space has one model instance on CPU.
174
+ #
175
+ # This prevents multiple requests from trying to generate
176
+ # through the same llama.cpp instance simultaneously.
177
+
178
+ generation_lock = threading.Lock()
179
+
180
+
181
+ # ============================================================
182
+ # STARTUP INFORMATION
183
+ # ============================================================
184
+
185
  print("=" * 70)
186
  print("Varsh LLM API READY")
187
  print("=" * 70)
188
+
189
  print(f"Listening on: http://{HOST}:{PORT}")
190
  print("Root endpoint: /")
191
+ print("Health endpoint: /health")
192
  print("Models endpoint: /v1/models")
193
  print("Chat endpoint: /v1/chat/completions")
194
  print("Authentication: Bearer API key")
195
+ print("Streaming: ENABLED")
196
  print("=" * 70)
197
 
198
 
199
  # ============================================================
200
+ # ROOT
201
  # ============================================================
202
 
203
  @app.get("/")
 
209
  "model": MODEL_ID,
210
  "endpoint": "/v1/chat/completions",
211
  "models_endpoint": "/v1/models",
212
+ "streaming": True,
213
  }
214
 
215
 
216
+ # ============================================================
217
+ # HEALTH
218
+ # ============================================================
219
+
220
  @app.get("/health")
221
  def health():
222
  return {
223
  "status": "healthy",
224
+ "service": APP_NAME,
225
  "model": MODEL_ID,
226
  }
227
 
228
 
229
  # ============================================================
230
+ # OPENAI /v1/models
231
  # ============================================================
232
 
233
  @app.get(
 
248
  }
249
 
250
 
251
+ # ============================================================
252
+ # OPENAI /v1/models/{model_id}
253
+ # ============================================================
254
+
255
  @app.get(
256
  "/v1/models/{model_id}",
257
  dependencies=[Depends(verify_api_key)],
258
  )
259
  def get_model(model_id: str):
260
+
261
  if model_id != MODEL_ID:
262
  raise HTTPException(
263
  status_code=404,
 
272
  }
273
 
274
 
275
+ # ============================================================
276
+ # MESSAGE VALIDATION
277
+ # ============================================================
278
+
279
+ def clean_messages(messages):
280
+
281
+ if not isinstance(messages, list):
282
+ raise HTTPException(
283
+ status_code=400,
284
+ detail="'messages' must be a list.",
285
+ )
286
+
287
+ cleaned = []
288
+
289
+ for message in messages:
290
+
291
+ if not isinstance(message, dict):
292
+ raise HTTPException(
293
+ status_code=400,
294
+ detail="Each message must be an object.",
295
+ )
296
+
297
+ role = message.get("role", "user")
298
+ content = message.get("content", "")
299
+
300
+ if content is None:
301
+ content = ""
302
+
303
+ if not isinstance(content, str):
304
+ raise HTTPException(
305
+ status_code=400,
306
+ detail=(
307
+ "Message content must be a string. "
308
+ "Multimodal content is not supported."
309
+ ),
310
+ )
311
+
312
+ cleaned.append(
313
+ {
314
+ "role": role,
315
+ "content": content,
316
+ }
317
+ )
318
+
319
+ if not cleaned:
320
+ raise HTTPException(
321
+ status_code=400,
322
+ detail="No messages were provided.",
323
+ )
324
+
325
+ return cleaned
326
+
327
+
328
  # ============================================================
329
  # CHAT COMPLETIONS
330
  # ============================================================
 
341
 
342
  try:
343
  body = await request.json()
344
+
345
  except Exception:
346
  raise HTTPException(
347
  status_code=400,
 
349
  )
350
 
351
  # --------------------------------------------------------
352
+ # Model
353
  # --------------------------------------------------------
354
 
355
+ requested_model = body.get(
356
+ "model",
357
+ MODEL_ID,
358
+ )
359
 
360
  if requested_model != MODEL_ID:
361
+
362
  raise HTTPException(
363
  status_code=404,
364
  detail=f"Model '{requested_model}' not found.",
365
  )
366
 
367
+ # --------------------------------------------------------
368
+ # Messages
369
+ # --------------------------------------------------------
370
 
371
+ messages = clean_messages(
372
+ body.get("messages")
373
+ )
 
 
374
 
375
+ # --------------------------------------------------------
376
+ # Parameters
377
+ # --------------------------------------------------------
378
 
379
+ temperature = body.get(
380
+ "temperature",
381
+ 0.2,
382
+ )
383
 
384
+ max_tokens = body.get(
385
+ "max_tokens",
386
+ 1024,
387
+ )
388
 
389
+ top_p = body.get(
390
+ "top_p",
391
+ 0.95,
392
+ )
393
+
394
+ stop = body.get(
395
+ "stop",
396
+ None,
397
+ )
398
+
399
+ stream = body.get(
400
+ "stream",
401
+ False,
402
+ )
403
 
404
  # --------------------------------------------------------
405
+ # Optional seed
406
  # --------------------------------------------------------
407
 
408
+ seed = body.get(
409
+ "seed",
410
+ None,
411
+ )
412
 
413
+ # --------------------------------------------------------
414
+ # Request metadata
415
+ # --------------------------------------------------------
416
 
417
+ request_id = (
418
+ "chatcmpl-"
419
+ + uuid.uuid4().hex
420
+ )
421
 
422
+ created = int(
423
+ time.time()
424
+ )
425
 
426
+ # ========================================================
427
+ # STREAMING
428
+ # ========================================================
 
 
 
 
429
 
430
+ if stream:
 
 
 
 
 
431
 
432
+ def generate_stream():
 
 
 
 
433
 
434
+ start_time = time.perf_counter()
435
+
436
+ try:
437
+
438
+ # ------------------------------------------------
439
+ # Lock model generation
440
+ # ------------------------------------------------
441
+
442
+ with generation_lock:
443
+
444
+ stream_kwargs = {
445
+ "messages": messages,
446
+ "temperature": float(temperature),
447
+ "max_tokens": int(max_tokens),
448
+ "top_p": float(top_p),
449
+ "stream": True,
450
+ }
451
+
452
+ if stop is not None:
453
+ stream_kwargs["stop"] = stop
454
+
455
+ if seed is not None:
456
+ stream_kwargs["seed"] = int(seed)
457
+
458
+ response_stream = (
459
+ llm.create_chat_completion(
460
+ **stream_kwargs
461
+ )
462
+ )
463
+
464
+ first_chunk = True
465
+
466
+ for chunk in response_stream:
467
+
468
+ choices = chunk.get(
469
+ "choices",
470
+ [],
471
+ )
472
+
473
+ if not choices:
474
+ continue
475
+
476
+ choice = choices[0]
477
+
478
+ delta = choice.get(
479
+ "delta",
480
+ {},
481
+ )
482
+
483
+ finish_reason = choice.get(
484
+ "finish_reason",
485
+ None,
486
+ )
487
+
488
+ # ----------------------------------------
489
+ # First chunk
490
+ # ----------------------------------------
491
+
492
+ if first_chunk:
493
+
494
+ first_chunk = False
495
+
496
+ first_data = {
497
+ "id": request_id,
498
+ "object": "chat.completion.chunk",
499
+ "created": created,
500
+ "model": MODEL_ID,
501
+ "choices": [
502
+ {
503
+ "index": 0,
504
+ "delta": {
505
+ "role": "assistant"
506
+ },
507
+ "finish_reason": None,
508
+ }
509
+ ],
510
+ }
511
+
512
+ yield (
513
+ "data: "
514
+ + json.dumps(
515
+ first_data,
516
+ ensure_ascii=False,
517
+ )
518
+ + "\n\n"
519
+ )
520
+
521
+ # ----------------------------------------
522
+ # Content chunk
523
+ # ----------------------------------------
524
+
525
+ content = delta.get(
526
+ "content",
527
+ "",
528
+ )
529
+
530
+ if content:
531
+
532
+ data = {
533
+ "id": request_id,
534
+ "object": "chat.completion.chunk",
535
+ "created": created,
536
+ "model": MODEL_ID,
537
+ "choices": [
538
+ {
539
+ "index": 0,
540
+ "delta": {
541
+ "content": content
542
+ },
543
+ "finish_reason": None,
544
+ }
545
+ ],
546
+ }
547
+
548
+ yield (
549
+ "data: "
550
+ + json.dumps(
551
+ data,
552
+ ensure_ascii=False,
553
+ )
554
+ + "\n\n"
555
+ )
556
+
557
+ # ----------------------------------------
558
+ # Finish reason
559
+ # ----------------------------------------
560
+
561
+ if finish_reason is not None:
562
+
563
+ final_data = {
564
+ "id": request_id,
565
+ "object": "chat.completion.chunk",
566
+ "created": created,
567
+ "model": MODEL_ID,
568
+ "choices": [
569
+ {
570
+ "index": 0,
571
+ "delta": {},
572
+ "finish_reason": finish_reason,
573
+ }
574
+ ],
575
+ }
576
+
577
+ yield (
578
+ "data: "
579
+ + json.dumps(
580
+ final_data,
581
+ ensure_ascii=False,
582
+ )
583
+ + "\n\n"
584
+ )
585
+
586
+ # ------------------------------------------------
587
+ # DONE
588
+ # ------------------------------------------------
589
+
590
+ yield "data: [DONE]\n\n"
591
+
592
+ elapsed = (
593
+ time.perf_counter()
594
+ - start_time
595
+ )
596
+
597
+ print(
598
+ f"Stream {request_id} "
599
+ f"completed in "
600
+ f"{elapsed:.2f}s"
601
+ )
602
+
603
+ except Exception as e:
604
+
605
+ print(
606
+ f"Streaming error "
607
+ f"{request_id}: {e}"
608
+ )
609
+
610
+ error_data = {
611
+ "error": {
612
+ "message": str(e),
613
+ "type": "server_error",
614
+ }
615
+ }
616
 
617
+ yield (
618
+ "data: "
619
+ + json.dumps(
620
+ error_data,
621
+ ensure_ascii=False,
622
+ )
623
+ + "\n\n"
624
+ )
625
+
626
+ yield "data: [DONE]\n\n"
627
+
628
+ return StreamingResponse(
629
+ generate_stream(),
630
+ media_type="text/event-stream",
631
+ headers={
632
+ "Cache-Control": "no-cache",
633
+ "Connection": "keep-alive",
634
+ "X-Accel-Buffering": "no",
635
+ },
636
+ )
637
 
638
+ # ========================================================
639
+ # NON-STREAMING
640
+ # ========================================================
641
 
642
  start_time = time.perf_counter()
643
 
644
  try:
645
+
646
+ with generation_lock:
647
+
648
+ completion_kwargs = {
649
+ "messages": messages,
650
+ "temperature": float(temperature),
651
+ "max_tokens": int(max_tokens),
652
+ "top_p": float(top_p),
653
+ "stream": False,
654
+ }
655
+
656
+ if stop is not None:
657
+ completion_kwargs["stop"] = stop
658
+
659
+ if seed is not None:
660
+ completion_kwargs["seed"] = int(seed)
661
+
662
+ response = (
663
+ llm.create_chat_completion(
664
+ **completion_kwargs
665
+ )
666
+ )
667
 
668
  except Exception as e:
669
+
670
+ print(
671
+ f"Inference error "
672
+ f"{request_id}: {e}"
673
+ )
674
 
675
  raise HTTPException(
676
  status_code=500,
677
  detail=f"Model inference failed: {str(e)}",
678
  )
679
 
680
+ elapsed = (
681
+ time.perf_counter()
682
+ - start_time
683
+ )
684
 
685
  # --------------------------------------------------------
686
+ # Extract response
687
  # --------------------------------------------------------
688
 
689
  try:
690
+
691
  choice = response["choices"][0]
692
 
693
+ message = choice.get(
694
+ "message",
695
+ {},
696
+ )
697
 
698
+ text_output = message.get(
699
+ "content",
700
+ "",
701
+ )
702
 
703
  finish_reason = choice.get(
704
  "finish_reason",
 
706
  )
707
 
708
  except Exception as e:
709
+
710
+ print(
711
+ f"Response parsing error: {e}"
712
+ )
713
 
714
  raise HTTPException(
715
  status_code=500,
716
+ detail=(
717
+ "Invalid response returned "
718
+ "by the model."
719
+ ),
720
  )
721
 
722
  # --------------------------------------------------------
723
  # Usage
724
  # --------------------------------------------------------
725
 
726
+ usage = response.get(
727
+ "usage",
728
+ {},
729
+ )
730
 
731
  prompt_tokens = usage.get(
732
  "prompt_tokens",
 
747
  f"Request {request_id} | "
748
  f"prompt={prompt_tokens} | "
749
  f"completion={completion_tokens} | "
750
+ f"time={elapsed:.2f}s"
751
  )
752
 
753
  # --------------------------------------------------------
 
776
  "total_tokens": total_tokens,
777
  },
778
  "x_generation_time_seconds": round(
779
+ elapsed,
780
  3,
781
  ),
782
  }