Niansuh commited on
Commit
9ef4d64
·
verified ·
1 Parent(s): b251981

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +96 -316
main.py CHANGED
@@ -10,18 +10,14 @@ import time
10
  from collections import defaultdict
11
  from typing import List, Dict, Any, Optional, Union, AsyncGenerator
12
 
13
- from datetime import datetime # Required for timestamping
14
-
15
  from aiohttp import ClientSession, ClientResponseError
16
  from fastapi import FastAPI, HTTPException, Request, Depends, Header
17
- from fastapi.responses import JSONResponse, StreamingResponse
18
  from pydantic import BaseModel
19
 
20
- # ----------------------------- Configuration -----------------------------
21
-
22
  # Configure logging
23
  logging.basicConfig(
24
- level=logging.DEBUG, # Set to DEBUG for detailed logs; change to INFO in production
25
  format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
26
  handlers=[logging.StreamHandler()]
27
  )
@@ -42,50 +38,19 @@ rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
42
  CLEANUP_INTERVAL = 60 # seconds
43
  RATE_LIMIT_WINDOW = 60 # seconds
44
 
45
- # ----------------------------- Pydantic Models -----------------------------
46
-
47
  class ImageResponseModel(BaseModel):
48
- images: str # URL of the generated image
49
  alt: str
50
 
51
- class Message(BaseModel):
52
- role: str
53
- content: str
54
-
55
- class ChatRequest(BaseModel):
56
- model: str
57
- messages: List[Message]
58
- temperature: Optional[float] = 1.0
59
- top_p: Optional[float] = 1.0
60
- n: Optional[int] = 1
61
- max_tokens: Optional[int] = None
62
- presence_penalty: Optional[float] = 0.0
63
- frequency_penalty: Optional[float] = 0.0
64
- logit_bias: Optional[Dict[str, float]] = None
65
- user: Optional[str] = None
66
- stream: Optional[bool] = False
67
- webSearchMode: Optional[bool] = False # Added based on old code
68
-
69
- # ----------------------------- Helper Functions -----------------------------
70
-
71
- def create_response(content: str, model: str) -> Dict[str, Any]:
72
- """
73
- Formats the response chunk.
74
- """
75
- return {
76
- "model": model,
77
- "content": content
78
- }
79
-
80
- # ----------------------------- Blackbox Class -----------------------------
81
-
82
  class Blackbox:
83
  label = "Blackbox AI"
84
  url = "https://www.blackbox.ai"
85
  api_endpoint = "https://www.blackbox.ai/api/chat"
86
  working = True
87
  supports_gpt_4 = True
88
- supports_stream = True # New attribute for streaming support
89
  supports_system_message = True
90
  supports_message_history = True
91
 
@@ -94,7 +59,7 @@ class Blackbox:
94
  models = [
95
  default_model,
96
  'blackboxai-pro',
97
- *image_models, # Incorporate image models
98
  "llama-3.1-8b",
99
  'llama-3.1-70b',
100
  'llama-3.1-405b',
@@ -234,15 +199,8 @@ class Blackbox:
234
  model: str,
235
  messages: List[Dict[str, str]],
236
  proxy: Optional[str] = None,
237
- websearch: bool = False,
238
  **kwargs
239
- ) -> Union[str, ImageResponseModel]:
240
- """
241
- Generates a response by interacting with the Blackbox API.
242
- Performs two POST requests:
243
- 1. Initiates the chat and obtains a chat_id.
244
- 2. Retrieves the chat response using the chat_id.
245
- """
246
  model = cls.get_model(model)
247
  chat_id = cls.generate_random_string()
248
  next_action = cls.generate_next_action()
@@ -316,25 +274,12 @@ class Blackbox:
316
  "clickedForceWebSearch": False,
317
  "visitFromDelta": False,
318
  "mobileClient": False,
319
- "webSearchMode": websearch,
320
  "userSelectedModel": cls.userSelectedModel.get(model, model)
321
  }
322
 
323
- headers_chat = {
324
- 'Accept': 'text/x-component',
325
- 'Content-Type': 'text/plain;charset=UTF-8',
326
- 'Referer': f'{cls.url}/chat/{chat_id}?model={model}',
327
- 'next-action': next_action,
328
- 'next-router-state-tree': next_router_state_tree,
329
- 'next-url': '/'
330
- }
331
- headers_chat_combined = {**common_headers, **headers_chat}
332
-
333
- data_chat = '[]'
334
-
335
  async with ClientSession(headers=common_headers) as session:
336
  try:
337
- # First POST request to initiate the chat and get chat_id
338
  async with session.post(
339
  cls.api_endpoint,
340
  headers=headers_api_chat_combined,
@@ -343,108 +288,29 @@ class Blackbox:
343
  ) as response_api_chat:
344
  response_api_chat.raise_for_status()
345
  text = await response_api_chat.text()
346
- logger.debug(f"Raw response from Blackbox API (initiate chat): {text}") # Added logging
347
  cleaned_response = cls.clean_response(text)
348
- logger.debug(f"Cleaned response (initiate chat): {cleaned_response}") # Added logging
349
-
350
- # Second POST request to retrieve the chat response using chat_id
351
- chat_url = f'{cls.url}/chat/{chat_id}?model={model}'
352
- async with session.post(
353
- chat_url,
354
- headers=headers_chat_combined,
355
- data=data_chat,
356
- proxy=proxy
357
- ) as response_chat:
358
- response_chat.raise_for_status()
359
- chat_response_text = await response_chat.text()
360
- logger.debug(f"Raw response from Blackbox API (retrieve chat): {chat_response_text}") # Added logging
361
- cleaned_chat_response = cls.clean_response(chat_response_text)
362
- logger.debug(f"Cleaned response (retrieve chat): {cleaned_chat_response}") # Added logging
363
-
364
- if model in cls.image_models:
365
- match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_chat_response)
366
- if match:
367
- image_url = match.group(1)
368
- image_response = ImageResponseModel(images=image_url, alt="Generated Image")
369
- logger.debug(f"Image URL extracted: {image_url}") # Added logging
370
- return image_response
371
- else:
372
- logger.debug("No image URL found in the response.") # Added logging
373
- return cleaned_chat_response
374
- else:
375
- if websearch:
376
- match = re.search(r'\$~~~\$(.*?)\$~~~\$', cleaned_chat_response, re.DOTALL)
377
- if match:
378
- source_part = match.group(1).strip()
379
- answer_part = cleaned_chat_response[match.end():].strip()
380
- try:
381
- sources = json.loads(source_part)
382
- source_formatted = "**Source:**\n"
383
- for item in sources:
384
- title = item.get('title', 'No Title')
385
- link = item.get('link', '#')
386
- position = item.get('position', '')
387
- source_formatted += f"{position}. [{title}]({link})\n"
388
- final_response = f"{answer_part}\n\n{source_formatted}"
389
- except json.JSONDecodeError:
390
- final_response = f"{answer_part}\n\nSource information is unavailable."
391
- else:
392
- final_response = cleaned_chat_response
393
- else:
394
- if '$~~~$' in cleaned_chat_response:
395
- final_response = cleaned_chat_response.split('$~~~$')[0].strip()
396
- else:
397
- final_response = cleaned_chat_response
398
-
399
- logger.debug(f"Final response to return: {final_response}") # Added logging
400
- return final_response
401
  except ClientResponseError as e:
402
  error_text = f"Error {e.status}: {e.message}"
403
  try:
404
  error_response = await e.response.text()
405
  cleaned_error = cls.clean_response(error_response)
406
  error_text += f" - {cleaned_error}"
407
- logger.error(f"ClientResponseError: {error_text}") # Added logging
408
  except Exception:
409
  pass
410
  return error_text
411
  except Exception as e:
412
- logger.exception(f"Unexpected error during /api/chat request: {str(e)}") # Added logging
413
  return f"Unexpected error during /api/chat request: {str(e)}"
414
 
415
- @classmethod
416
- async def create_async_generator(
417
- cls,
418
- model: str,
419
- messages: List[Dict[str, str]],
420
- proxy: Optional[str] = None,
421
- websearch: bool = False,
422
- **kwargs
423
- ) -> AsyncGenerator[Union[str, ImageResponseModel], None]:
424
- """
425
- Creates an asynchronous generator for streaming responses from Blackbox AI.
426
- """
427
- try:
428
- response = await cls.generate_response(
429
- model=model,
430
- messages=messages,
431
- proxy=proxy,
432
- websearch=websearch,
433
- **kwargs
434
- )
435
- if isinstance(response, ImageResponseModel):
436
- yield response
437
- else:
438
- yield response
439
- except Exception as e:
440
- logger.exception(f"Error in create_async_generator: {str(e)}")
441
- yield f"Unexpected error: {str(e)}"
442
-
443
- # ----------------------------- FastAPI App Setup -----------------------------
444
 
445
- app = FastAPI()
446
-
447
- # ----------------------------- Rate Limiter Cleanup Task -----------------------------
 
 
 
448
 
449
  async def cleanup_rate_limit_stores():
450
  """
@@ -458,14 +324,46 @@ async def cleanup_rate_limit_stores():
458
  logger.debug(f"Cleaned up rate_limit_store for IP: {ip}")
459
  await asyncio.sleep(CLEANUP_INTERVAL)
460
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  # Add the cleanup task when the app starts
462
  @app.on_event("startup")
463
  async def startup_event():
464
  asyncio.create_task(cleanup_rate_limit_stores())
465
  logger.info("Started rate limit store cleanup task.")
466
 
467
- # ----------------------------- Security Middleware -----------------------------
468
-
469
  @app.middleware("http")
470
  async def security_middleware(request: Request, call_next):
471
  client_ip = request.client.host
@@ -488,41 +386,22 @@ async def security_middleware(request: Request, call_next):
488
  response = await call_next(request)
489
  return response
490
 
491
- # ----------------------------- API Key Dependency -----------------------------
492
-
493
- async def get_api_key(request: Request, authorization: str = Header(None)) -> str:
494
- """
495
- Dependency to extract and validate the API key from the Authorization header.
496
- """
497
- client_ip = request.client.host
498
- if authorization is None or not authorization.startswith('Bearer '):
499
- logger.warning(f"Invalid or missing authorization header from IP: {client_ip}")
500
- raise HTTPException(status_code=401, detail='Invalid authorization header format')
501
- api_key = authorization[7:]
502
- if api_key not in API_KEYS:
503
- logger.warning(f"Invalid API key attempted: {api_key} from IP: {client_ip}")
504
- raise HTTPException(status_code=401, detail='Invalid API key')
505
- return api_key
506
-
507
- # ----------------------------- Rate Limiter Dependency -----------------------------
508
-
509
- async def rate_limiter_per_ip(request: Request):
510
- """
511
- Rate limiter that enforces a limit based on the client's IP address.
512
- """
513
- client_ip = request.client.host
514
- current_time = time.time()
515
-
516
- # Initialize or update the count and timestamp
517
- if current_time - rate_limit_store[client_ip]["timestamp"] > RATE_LIMIT_WINDOW:
518
- rate_limit_store[client_ip] = {"count": 1, "timestamp": current_time}
519
- else:
520
- if rate_limit_store[client_ip]["count"] >= RATE_LIMIT:
521
- logger.warning(f"Rate limit exceeded for IP address: {client_ip}")
522
- raise HTTPException(status_code=429, detail='Rate limit exceeded for IP address | NiansuhAI')
523
- rate_limit_store[client_ip]["count"] += 1
524
 
525
- # ----------------------------- Chat Completions Endpoint -----------------------------
 
 
 
 
 
 
 
 
 
 
526
 
527
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
528
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
@@ -538,132 +417,36 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
538
  logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
539
  raise HTTPException(status_code=400, detail="Requested model is not available.")
540
 
541
- # Check if the model is an image generation model
542
- is_image_model = request.model in Blackbox.image_models
543
-
544
- if request.stream:
545
- # Streaming response
546
- async_generator = Blackbox.create_async_generator(
547
- model=request.model,
548
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
549
- websearch=request.webSearchMode
550
- )
551
-
552
- async def generate():
553
- try:
554
- async for chunk in async_generator:
555
- if isinstance(chunk, ImageResponseModel):
556
- image_markdown = f"![image]({chunk.images})"
557
- response_chunk = create_response(image_markdown, request.model)
558
- else:
559
- response_chunk = create_response(chunk, request.model)
560
-
561
- yield f"data: {json.dumps(response_chunk)}\n\n"
562
-
563
- yield "data: [DONE]\n\n"
564
- except HTTPException as he:
565
- error_response = {"error": he.detail}
566
- yield f"data: {json.dumps(error_response)}\n\n"
567
- except Exception as e:
568
- logger.exception(f"Error during streaming response generation from IP: {client_ip}.")
569
- error_response = {"error": str(e)}
570
- yield f"data: {json.dumps(error_response)}\n\n"
571
-
572
- return StreamingResponse(generate(), media_type="text/event-stream")
573
- else:
574
- # Non-streaming response
575
- async_generator = Blackbox.create_async_generator(
576
- model=request.model,
577
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
578
- websearch=request.webSearchMode
579
- )
580
-
581
- response_content = ""
582
- async for chunk in async_generator:
583
- if isinstance(chunk, ImageResponseModel):
584
- response_content += f"![image]({chunk.images})\n"
585
- else:
586
- response_content += chunk
587
-
588
- logger.info(f"Completed non-streaming response generation for API key: {api_key} | IP: {client_ip}")
589
- return {
590
- "id": f"chatcmpl-{uuid.uuid4()}",
591
- "object": "chat.completion",
592
- "created": int(datetime.now().timestamp()),
593
- "model": request.model,
594
- "choices": [
595
- {
596
- "message": {
597
- "role": "assistant",
598
- "content": response_content
599
- },
600
- "finish_reason": "stop",
601
- "index": 0
602
- }
603
- ],
604
- "usage": {
605
- "prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
606
- "completion_tokens": len(response_content.split()),
607
- "total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.split())
608
- },
609
- }
610
- except ModelNotWorkingException as e:
611
- logger.warning(f"Model not working: {e} | IP: {client_ip}")
612
- raise HTTPException(status_code=503, detail=str(e))
613
- except HTTPException as he:
614
- logger.warning(f"HTTPException: {he.detail} | IP: {client_ip}")
615
- raise he
616
- except Exception as e:
617
- logger.exception(f"An unexpected error occurred while processing the chat completions request from IP: {client_ip}.")
618
- raise HTTPException(status_code=500, detail=str(e))
619
-
620
- # ----------------------------- Streaming Endpoint (Optional) -----------------------------
621
-
622
- @app.post("/v1/chat/completions/stream", dependencies=[Depends(rate_limiter_per_ip)])
623
- async def chat_completions_stream(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
624
- """
625
- Optional endpoint for streaming responses. Can be removed if not needed.
626
- """
627
- client_ip = req.client.host
628
- redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
629
-
630
- logger.info(f"Received streaming chat completions request from API key: {api_key} | IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages}")
631
-
632
- try:
633
- # Validate that the requested model is available
634
- if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
635
- logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
636
- raise HTTPException(status_code=400, detail="Requested model is not available.")
637
-
638
- # Check if the model is an image generation model
639
- is_image_model = request.model in Blackbox.image_models
640
-
641
- # Create an asynchronous generator
642
- async_gen = Blackbox.create_async_generator(
643
  model=request.model,
644
  messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
645
- websearch=request.webSearchMode
 
646
  )
647
 
648
- async def stream_response() -> AsyncGenerator[bytes, None]:
649
- async for chunk in async_gen:
650
- if isinstance(chunk, ImageResponseModel):
651
- # For image responses, send the URL directly
652
- yield json.dumps({
653
- "role": "assistant",
654
- "content": chunk.images
655
- }).encode('utf-8') + b'\n'
656
- else:
657
- yield json.dumps({
658
  "role": "assistant",
659
- "content": chunk
660
- }).encode('utf-8') + b'\n'
661
-
662
- logger.info(f"Streaming response started for API key: {api_key} | IP: {client_ip}")
663
- return StreamingResponse(
664
- stream_response(),
665
- media_type='text/event-stream'
666
- )
 
 
 
667
  except ModelNotWorkingException as e:
668
  logger.warning(f"Model not working: {e} | IP: {client_ip}")
669
  raise HTTPException(status_code=503, detail=str(e))
@@ -671,25 +454,24 @@ async def chat_completions_stream(request: ChatRequest, req: Request, api_key: s
671
  logger.warning(f"HTTPException: {he.detail} | IP: {client_ip}")
672
  raise he
673
  except Exception as e:
674
- logger.exception(f"An unexpected error occurred while processing the streaming chat completions request from IP: {client_ip}.")
675
  raise HTTPException(status_code=500, detail=str(e))
676
 
677
- # ----------------------------- Additional Endpoints -----------------------------
678
-
679
  @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
680
  async def get_models(req: Request):
681
  client_ip = req.client.host
682
  logger.info(f"Fetching available models from IP: {client_ip}")
683
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
684
 
 
685
  @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
686
  async def health_check(req: Request):
687
  client_ip = req.client.host
688
  logger.info(f"Health check requested from IP: {client_ip}")
689
  return {"status": "ok"}
690
 
691
- # ----------------------------- Exception Handlers -----------------------------
692
-
693
  @app.exception_handler(HTTPException)
694
  async def http_exception_handler(request: Request, exc: HTTPException):
695
  client_ip = request.client.host
@@ -706,8 +488,6 @@ async def http_exception_handler(request: Request, exc: HTTPException):
706
  },
707
  )
708
 
709
- # ----------------------------- Main Entry Point -----------------------------
710
-
711
  if __name__ == "__main__":
712
  import uvicorn
713
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
10
  from collections import defaultdict
11
  from typing import List, Dict, Any, Optional, Union, AsyncGenerator
12
 
 
 
13
  from aiohttp import ClientSession, ClientResponseError
14
  from fastapi import FastAPI, HTTPException, Request, Depends, Header
15
+ from fastapi.responses import JSONResponse
16
  from pydantic import BaseModel
17
 
 
 
18
  # Configure logging
19
  logging.basicConfig(
20
+ level=logging.INFO,
21
  format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
22
  handlers=[logging.StreamHandler()]
23
  )
 
38
  CLEANUP_INTERVAL = 60 # seconds
39
  RATE_LIMIT_WINDOW = 60 # seconds
40
 
41
+ # Define ImageResponse for image models
 
42
  class ImageResponseModel(BaseModel):
43
+ images: str
44
  alt: str
45
 
46
+ # Updated Blackbox class with new models and functionalities
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  class Blackbox:
48
  label = "Blackbox AI"
49
  url = "https://www.blackbox.ai"
50
  api_endpoint = "https://www.blackbox.ai/api/chat"
51
  working = True
52
  supports_gpt_4 = True
53
+ supports_stream = True
54
  supports_system_message = True
55
  supports_message_history = True
56
 
 
59
  models = [
60
  default_model,
61
  'blackboxai-pro',
62
+ *image_models,
63
  "llama-3.1-8b",
64
  'llama-3.1-70b',
65
  'llama-3.1-405b',
 
199
  model: str,
200
  messages: List[Dict[str, str]],
201
  proxy: Optional[str] = None,
 
202
  **kwargs
203
+ ) -> str:
 
 
 
 
 
 
204
  model = cls.get_model(model)
205
  chat_id = cls.generate_random_string()
206
  next_action = cls.generate_next_action()
 
274
  "clickedForceWebSearch": False,
275
  "visitFromDelta": False,
276
  "mobileClient": False,
277
+ "webSearchMode": False,
278
  "userSelectedModel": cls.userSelectedModel.get(model, model)
279
  }
280
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  async with ClientSession(headers=common_headers) as session:
282
  try:
 
283
  async with session.post(
284
  cls.api_endpoint,
285
  headers=headers_api_chat_combined,
 
288
  ) as response_api_chat:
289
  response_api_chat.raise_for_status()
290
  text = await response_api_chat.text()
 
291
  cleaned_response = cls.clean_response(text)
292
+ return cleaned_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  except ClientResponseError as e:
294
  error_text = f"Error {e.status}: {e.message}"
295
  try:
296
  error_response = await e.response.text()
297
  cleaned_error = cls.clean_response(error_response)
298
  error_text += f" - {cleaned_error}"
 
299
  except Exception:
300
  pass
301
  return error_text
302
  except Exception as e:
 
303
  return f"Unexpected error during /api/chat request: {str(e)}"
304
 
305
+ # If needed, you can integrate create_async_generator here
306
+ # ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
308
+ # Custom exception for model not working
309
+ class ModelNotWorkingException(Exception):
310
+ def __init__(self, model: str):
311
+ self.model = model
312
+ self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
313
+ super().__init__(self.message)
314
 
315
  async def cleanup_rate_limit_stores():
316
  """
 
324
  logger.debug(f"Cleaned up rate_limit_store for IP: {ip}")
325
  await asyncio.sleep(CLEANUP_INTERVAL)
326
 
327
+ async def rate_limiter_per_ip(request: Request):
328
+ """
329
+ Rate limiter that enforces a limit based on the client's IP address.
330
+ """
331
+ client_ip = request.client.host
332
+ current_time = time.time()
333
+
334
+ # Initialize or update the count and timestamp
335
+ if current_time - rate_limit_store[client_ip]["timestamp"] > RATE_LIMIT_WINDOW:
336
+ rate_limit_store[client_ip] = {"count": 1, "timestamp": current_time}
337
+ else:
338
+ if rate_limit_store[client_ip]["count"] >= RATE_LIMIT:
339
+ logger.warning(f"Rate limit exceeded for IP address: {client_ip}")
340
+ raise HTTPException(status_code=429, detail='Rate limit exceeded for IP address | NiansuhAI')
341
+ rate_limit_store[client_ip]["count"] += 1
342
+
343
+ async def get_api_key(request: Request, authorization: str = Header(None)) -> str:
344
+ """
345
+ Dependency to extract and validate the API key from the Authorization header.
346
+ """
347
+ client_ip = request.client.host
348
+ if authorization is None or not authorization.startswith('Bearer '):
349
+ logger.warning(f"Invalid or missing authorization header from IP: {client_ip}")
350
+ raise HTTPException(status_code=401, detail='Invalid authorization header format')
351
+ api_key = authorization[7:]
352
+ if api_key not in API_KEYS:
353
+ logger.warning(f"Invalid API key attempted: {api_key} from IP: {client_ip}")
354
+ raise HTTPException(status_code=401, detail='Invalid API key')
355
+ return api_key
356
+
357
+ # FastAPI app setup
358
+ app = FastAPI()
359
+
360
  # Add the cleanup task when the app starts
361
  @app.on_event("startup")
362
  async def startup_event():
363
  asyncio.create_task(cleanup_rate_limit_stores())
364
  logger.info("Started rate limit store cleanup task.")
365
 
366
+ # Middleware to enhance security and enforce Content-Type for specific endpoints
 
367
  @app.middleware("http")
368
  async def security_middleware(request: Request, call_next):
369
  client_ip = request.client.host
 
386
  response = await call_next(request)
387
  return response
388
 
389
+ # Request Models
390
+ class Message(BaseModel):
391
+ role: str
392
+ content: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
+ class ChatRequest(BaseModel):
395
+ model: str
396
+ messages: List[Message]
397
+ temperature: Optional[float] = 1.0
398
+ top_p: Optional[float] = 1.0
399
+ n: Optional[int] = 1
400
+ max_tokens: Optional[int] = None
401
+ presence_penalty: Optional[float] = 0.0
402
+ frequency_penalty: Optional[float] = 0.0
403
+ logit_bias: Optional[Dict[str, float]] = None
404
+ user: Optional[str] = None
405
 
406
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
407
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
 
417
  logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
418
  raise HTTPException(status_code=400, detail="Requested model is not available.")
419
 
420
+ # Process the request with actual message content, but don't log it
421
+ response_content = await Blackbox.generate_response(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  model=request.model,
423
  messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
424
+ temperature=request.temperature,
425
+ max_tokens=request.max_tokens
426
  )
427
 
428
+ logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
429
+ return {
430
+ "id": f"chatcmpl-{uuid.uuid4()}",
431
+ "object": "chat.completion",
432
+ "created": int(datetime.now().timestamp()),
433
+ "model": request.model,
434
+ "choices": [
435
+ {
436
+ "index": 0,
437
+ "message": {
438
  "role": "assistant",
439
+ "content": response_content
440
+ },
441
+ "finish_reason": "stop"
442
+ }
443
+ ],
444
+ "usage": {
445
+ "prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
446
+ "completion_tokens": len(response_content.split()),
447
+ "total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.split())
448
+ },
449
+ }
450
  except ModelNotWorkingException as e:
451
  logger.warning(f"Model not working: {e} | IP: {client_ip}")
452
  raise HTTPException(status_code=503, detail=str(e))
 
454
  logger.warning(f"HTTPException: {he.detail} | IP: {client_ip}")
455
  raise he
456
  except Exception as e:
457
+ logger.exception(f"An unexpected error occurred while processing the chat completions request from IP: {client_ip}.")
458
  raise HTTPException(status_code=500, detail=str(e))
459
 
460
+ # Endpoint: GET /v1/models
 
461
  @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
462
  async def get_models(req: Request):
463
  client_ip = req.client.host
464
  logger.info(f"Fetching available models from IP: {client_ip}")
465
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
466
 
467
+ # Endpoint: GET /v1/health
468
  @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
469
  async def health_check(req: Request):
470
  client_ip = req.client.host
471
  logger.info(f"Health check requested from IP: {client_ip}")
472
  return {"status": "ok"}
473
 
474
+ # Custom exception handler to match OpenAI's error format
 
475
  @app.exception_handler(HTTPException)
476
  async def http_exception_handler(request: Request, exc: HTTPException):
477
  client_ip = request.client.host
 
488
  },
489
  )
490
 
 
 
491
  if __name__ == "__main__":
492
  import uvicorn
493
  uvicorn.run(app, host="0.0.0.0", port=8000)