Niansuh commited on
Commit
02fd09b
·
verified ·
1 Parent(s): cfe0e5f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +180 -412
main.py CHANGED
@@ -18,7 +18,7 @@ from datetime import datetime
18
 
19
  # Configure logging
20
  logging.basicConfig(
21
- level=logging.INFO,
22
  format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
23
  handlers=[logging.StreamHandler()]
24
  )
@@ -289,7 +289,9 @@ class Blackbox:
289
  ) as response_api_chat:
290
  response_api_chat.raise_for_status()
291
  text = await response_api_chat.text()
 
292
  cleaned_response = cls.clean_response(text)
 
293
  return cleaned_response
294
  except ClientResponseError as e:
295
  error_text = f"Error {e.status}: {e.message}"
@@ -297,431 +299,197 @@ class Blackbox:
297
  error_response = await e.response.text()
298
  cleaned_error = cls.clean_response(error_response)
299
  error_text += f" - {cleaned_error}"
 
300
  except Exception:
301
  pass
302
  return error_text
303
  except Exception as e:
 
304
  return f"Unexpected error during /api/chat request: {str(e)}"
305
 
306
- @classmethod
307
- async def create_async_generator(
308
- cls,
309
- model: str,
310
- messages: List[Dict[str, str]],
311
- proxy: Optional[str] = None,
312
- websearch: bool = False,
313
- **kwargs
314
- ) -> AsyncGenerator[Union[str, ImageResponse], None]:
315
- """
316
- Creates an asynchronous generator for streaming responses from Blackbox AI.
317
 
318
- Parameters:
319
- model (str): Model to use for generating responses.
320
- messages (List[Dict[str, str]]): Message history.
321
- proxy (Optional[str]): Proxy URL, if needed.
322
- websearch (bool): Enables or disables web search mode.
323
- **kwargs: Additional keyword arguments.
324
-
325
- Yields:
326
- Union[str, ImageResponse]: Segments of the generated response or ImageResponse objects.
327
  """
328
- model = cls.get_model(model)
329
-
330
- chat_id = cls.generate_random_string()
331
- next_action = cls.generate_next_action()
332
- next_router_state_tree = cls.generate_next_router_state_tree()
333
-
334
- agent_mode = cls.agentMode.get(model, {})
335
- trending_agent_mode = cls.trendingAgentMode.get(model, {})
336
-
337
- prefix = cls.model_prefixes.get(model, "")
338
-
339
- formatted_prompt = ""
340
- for message in messages:
341
- role = message.get('role', '').capitalize()
342
- content = message.get('content', '')
343
- if role and content:
344
- formatted_prompt += f"{role}: {content}\n"
345
-
346
- if prefix:
347
- formatted_prompt = f"{prefix} {formatted_prompt}".strip()
348
-
349
- referer_path = cls.model_referers.get(model, f"/?model={model}")
350
- referer_url = f"{cls.url}{referer_path}"
351
-
352
- common_headers = {
353
- 'accept': '*/*',
354
- 'accept-language': 'en-US,en;q=0.9',
355
- 'cache-control': 'no-cache',
356
- 'origin': cls.url,
357
- 'pragma': 'no-cache',
358
- 'priority': 'u=1, i',
359
- 'sec-ch-ua': '"Chromium";v="129", "Not=A?Brand";v="8"',
360
- 'sec-ch-ua-mobile': '?0',
361
- 'sec-ch-ua-platform': '"Linux"',
362
- 'sec-fetch-dest': 'empty',
363
- 'sec-fetch-mode': 'cors',
364
- 'sec-fetch-site': 'same-origin',
365
- 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) '
366
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
367
- 'Chrome/129.0.0.0 Safari/537.36'
368
- }
369
-
370
- headers_api_chat = {
371
- 'Content-Type': 'application/json',
372
- 'Referer': referer_url
373
- }
374
- headers_api_chat_combined = {**common_headers, **headers_api_chat}
375
-
376
- payload_api_chat = {
377
- "messages": [
378
- {
379
- "id": chat_id,
380
- "content": formatted_prompt,
381
- "role": "user"
382
- }
383
- ],
384
- "id": chat_id,
385
- "previewToken": None,
386
- "userId": None,
387
- "codeModelMode": True,
388
- "agentMode": agent_mode,
389
- "trendingAgentMode": trending_agent_mode,
390
- "isMicMode": False,
391
- "userSystemPrompt": None,
392
- "maxTokens": 1024,
393
- "playgroundTopP": 0.9,
394
- "playgroundTemperature": 0.5,
395
- "isChromeExt": False,
396
- "githubToken": None,
397
- "clickedAnswer2": False,
398
- "clickedAnswer3": False,
399
- "clickedForceWebSearch": False,
400
- "visitFromDelta": False,
401
- "mobileClient": False,
402
- "webSearchMode": websearch,
403
- "userSelectedModel": cls.userSelectedModel.get(model, model)
404
- }
405
-
406
- headers_chat = {
407
- 'Accept': 'text/x-component',
408
- 'Content-Type': 'text/plain;charset=UTF-8',
409
- 'Referer': f'{cls.url}/chat/{chat_id}?model={model}',
410
- 'next-action': next_action,
411
- 'next-router-state-tree': next_router_state_tree,
412
- 'next-url': '/'
413
- }
414
- headers_chat_combined = {**common_headers, **headers_chat}
415
-
416
- data_chat = '[]'
417
-
418
- async with ClientSession(headers=common_headers) as session:
419
- try:
420
- async with session.post(
421
- cls.api_endpoint,
422
- headers=headers_api_chat_combined,
423
- json=payload_api_chat,
424
- proxy=proxy
425
- ) as response_api_chat:
426
- response_api_chat.raise_for_status()
427
- text = await response_api_chat.text()
428
- cleaned_response = cls.clean_response(text)
429
 
430
- if model in cls.image_models:
431
- match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_response)
432
- if match:
433
- image_url = match.group(1)
434
- image_response = ImageResponse(images=image_url, alt="Generated Image")
435
- yield image_response
436
- else:
437
- yield cleaned_response
438
- else:
439
- if websearch:
440
- match = re.search(r'\$~~~\$(.*?)\$~~~\$', cleaned_response, re.DOTALL)
441
- if match:
442
- source_part = match.group(1).strip()
443
- answer_part = cleaned_response[match.end():].strip()
444
- try:
445
- sources = json.loads(source_part)
446
- source_formatted = "**Source:**\n"
447
- for item in sources:
448
- title = item.get('title', 'No Title')
449
- link = item.get('link', '#')
450
- position = item.get('position', '')
451
- source_formatted += f"{position}. [{title}]({link})\n"
452
- final_response = f"{answer_part}\n\n{source_formatted}"
453
- except json.JSONDecodeError:
454
- final_response = f"{answer_part}\n\nSource information is unavailable."
455
- else:
456
- final_response = cleaned_response
457
- else:
458
- if '$~~~$' in cleaned_response:
459
- final_response = cleaned_response.split('$~~~$')[0].strip()
460
- else:
461
- final_response = cleaned_response
462
-
463
- yield final_response
464
- except ClientResponseError as e:
465
- error_text = f"Error {e.status}: {e.message}"
466
- try:
467
- error_response = await e.response.text()
468
- cleaned_error = cls.clean_response(error_response)
469
- error_text += f" - {cleaned_error}"
470
- except Exception:
471
- pass
472
- yield error_text
473
- except Exception as e:
474
- yield f"Unexpected error during /api/chat request: {str(e)}"
475
 
476
- chat_url = f'{cls.url}/chat/{chat_id}?model={model}'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
 
478
- try:
479
- async with session.post(
480
- chat_url,
481
- headers=headers_chat_combined,
482
- data=data_chat,
483
- proxy=proxy
484
- ) as response_chat:
485
- response_chat.raise_for_status()
486
- pass
487
- except ClientResponseError as e:
488
- error_text = f"Error {e.status}: {e.message}"
489
- try:
490
- error_response = await e.response.text()
491
- cleaned_error = cls.clean_response(error_response)
492
- error_text += f" - {cleaned_error}"
493
- except Exception:
494
- pass
495
- yield error_text
496
- except Exception as e:
497
- yield f"Unexpected error during /chat/{chat_id} request: {str(e)}"
498
-
499
- # Custom exception for model not working
500
- class ModelNotWorkingException(Exception):
501
- def __init__(self, model: str):
502
- self.model = model
503
- self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
504
- super().__init__(self.message)
505
-
506
- async def cleanup_rate_limit_stores():
507
- """
508
- Periodically cleans up stale entries in the rate_limit_store to prevent memory bloat.
509
- """
510
- while True:
511
- current_time = time.time()
512
- ips_to_delete = [ip for ip, value in rate_limit_store.items() if current_time - value["timestamp"] > RATE_LIMIT_WINDOW * 2]
513
- for ip in ips_to_delete:
514
- del rate_limit_store[ip]
515
- logger.debug(f"Cleaned up rate_limit_store for IP: {ip}")
516
- await asyncio.sleep(CLEANUP_INTERVAL)
517
-
518
- async def rate_limiter_per_ip(request: Request):
519
- """
520
- Rate limiter that enforces a limit based on the client's IP address.
521
- """
522
- client_ip = request.client.host
523
- current_time = time.time()
524
-
525
- # Initialize or update the count and timestamp
526
- if current_time - rate_limit_store[client_ip]["timestamp"] > RATE_LIMIT_WINDOW:
527
- rate_limit_store[client_ip] = {"count": 1, "timestamp": current_time}
528
- else:
529
- if rate_limit_store[client_ip]["count"] >= RATE_LIMIT:
530
- logger.warning(f"Rate limit exceeded for IP address: {client_ip}")
531
- raise HTTPException(status_code=429, detail='Rate limit exceeded for IP address | NiansuhAI')
532
- rate_limit_store[client_ip]["count"] += 1
533
-
534
- async def get_api_key(request: Request, authorization: str = Header(None)) -> str:
535
- """
536
- Dependency to extract and validate the API key from the Authorization header.
537
- """
538
- client_ip = request.client.host
539
- if authorization is None or not authorization.startswith('Bearer '):
540
- logger.warning(f"Invalid or missing authorization header from IP: {client_ip}")
541
- raise HTTPException(status_code=401, detail='Invalid authorization header format')
542
- api_key = authorization[7:]
543
- if api_key not in API_KEYS:
544
- logger.warning(f"Invalid API key attempted: {api_key} from IP: {client_ip}")
545
- raise HTTPException(status_code=401, detail='Invalid API key')
546
- return api_key
547
-
548
- # FastAPI app setup
549
- app = FastAPI()
550
-
551
- # Add the cleanup task when the app starts
552
- @app.on_event("startup")
553
- async def startup_event():
554
- asyncio.create_task(cleanup_rate_limit_stores())
555
- logger.info("Started rate limit store cleanup task.")
556
-
557
- # Middleware to enhance security and enforce Content-Type for specific endpoints
558
- @app.middleware("http")
559
- async def security_middleware(request: Request, call_next):
560
- client_ip = request.client.host
561
- # Enforce that POST requests to /v1/chat/completions must have Content-Type: application/json
562
- if request.method == "POST" and request.url.path == "/v1/chat/completions":
563
- content_type = request.headers.get("Content-Type")
564
- if content_type != "application/json":
565
- logger.warning(f"Invalid Content-Type from IP: {client_ip} for path: {request.url.path}")
566
- return JSONResponse(
567
- status_code=400,
568
- content={
569
- "error": {
570
- "message": "Content-Type must be application/json",
571
- "type": "invalid_request_error",
572
- "param": None,
573
- "code": None
574
  }
 
 
 
 
 
575
  },
576
- )
577
- response = await call_next(request)
578
- return response
579
-
580
- # Request Models
581
- class Message(BaseModel):
582
- role: str
583
- content: str
584
-
585
- class ChatRequest(BaseModel):
586
- model: str
587
- messages: List[Message]
588
- temperature: Optional[float] = 1.0
589
- top_p: Optional[float] = 1.0
590
- n: Optional[int] = 1
591
- max_tokens: Optional[int] = None
592
- presence_penalty: Optional[float] = 0.0
593
- frequency_penalty: Optional[float] = 0.0
594
- logit_bias: Optional[Dict[str, float]] = None
595
- user: Optional[str] = None
596
-
597
- @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
598
- async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
599
- client_ip = req.client.host
600
- # Redact user messages only for logging purposes
601
- redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
602
-
603
- logger.info(f"Received chat completions request from API key: {api_key} | IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages}")
604
-
605
- try:
606
- # Validate that the requested model is available
607
- if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
608
- logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
609
- raise HTTPException(status_code=400, detail="Requested model is not available.")
610
-
611
- # Process the request with actual message content, but don't log it
612
- response_content = await Blackbox.generate_response(
613
- model=request.model,
614
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
615
- temperature=request.temperature,
616
- max_tokens=request.max_tokens
617
- )
618
-
619
- logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
620
- return {
621
- "id": f"chatcmpl-{uuid.uuid4()}",
622
- "object": "chat.completion",
623
- "created": int(datetime.now().timestamp()),
624
- "model": request.model,
625
- "choices": [
626
- {
627
- "index": 0,
628
- "message": {
629
- "role": "assistant",
630
- "content": response_content
631
- },
632
- "finish_reason": "stop"
633
  }
634
- ],
635
- "usage": {
636
- "prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
637
- "completion_tokens": len(response_content.split()),
638
- "total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.split())
639
  },
640
- }
641
- except ModelNotWorkingException as e:
642
- logger.warning(f"Model not working: {e} | IP: {client_ip}")
643
- raise HTTPException(status_code=503, detail=str(e))
644
- except HTTPException as he:
645
- logger.warning(f"HTTPException: {he.detail} | IP: {client_ip}")
646
- raise he
647
- except Exception as e:
648
- logger.exception(f"An unexpected error occurred while processing the chat completions request from IP: {client_ip}.")
649
- raise HTTPException(status_code=500, detail=str(e))
650
-
651
- # Endpoint: GET /v1/models
652
- @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
653
- async def get_models(req: Request):
654
- client_ip = req.client.host
655
- logger.info(f"Fetching available models from IP: {client_ip}")
656
- return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
657
-
658
- # Endpoint: GET /v1/health
659
- @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
660
- async def health_check(req: Request):
661
- client_ip = req.client.host
662
- logger.info(f"Health check requested from IP: {client_ip}")
663
- return {"status": "ok"}
664
-
665
- # Custom exception handler to match OpenAI's error format
666
- @app.exception_handler(HTTPException)
667
- async def http_exception_handler(request: Request, exc: HTTPException):
668
- client_ip = request.client.host
669
- logger.error(f"HTTPException: {exc.detail} | Path: {request.url.path} | IP: {client_ip}")
670
- return JSONResponse(
671
- status_code=exc.status_code,
672
- content={
673
- "error": {
674
- "message": exc.detail,
675
- "type": "invalid_request_error",
676
- "param": None,
677
- "code": None
678
- }
679
- },
680
- )
681
-
682
- # Optional: Additional Endpoint for Streaming Responses (Using create_async_generator)
683
- # This endpoint leverages the new create_async_generator method for streaming responses.
684
- # Note: Streaming responses may require clients that support Server-Sent Events (SSE) or WebSockets.
685
-
686
- @app.post("/v1/chat/completions/stream", dependencies=[Depends(rate_limiter_per_ip)])
687
- async def chat_completions_stream(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
688
- client_ip = req.client.host
689
- # Redact user messages only for logging purposes
690
- redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
691
-
692
- logger.info(f"Received streaming chat completions request from API key: {api_key} | IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages}")
693
-
694
- try:
695
- # Validate that the requested model is available
696
- if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
697
- logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
698
- raise HTTPException(status_code=400, detail="Requested model is not available.")
699
-
700
- # Create an asynchronous generator for the response
701
- async_generator = Blackbox.create_async_generator(
702
- model=request.model,
703
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
704
- temperature=request.temperature,
705
- max_tokens=request.max_tokens
706
  )
707
 
708
- logger.info(f"Started streaming response for API key: {api_key} | IP: {client_ip}")
709
- return JSONResponse(
710
- status_code=200,
711
- content={"stream": True, "message": "Streaming started."}
712
- )
713
- # Note: Implement proper streaming responses using StreamingResponse or WebSockets if needed.
714
-
715
- except ModelNotWorkingException as e:
716
- logger.warning(f"Model not working: {e} | IP: {client_ip}")
717
- raise HTTPException(status_code=503, detail=str(e))
718
- except HTTPException as he:
719
- logger.warning(f"HTTPException: {he.detail} | IP: {client_ip}")
720
- raise he
721
- except Exception as e:
722
- logger.exception(f"An unexpected error occurred while processing the streaming chat completions request from IP: {client_ip}.")
723
- raise HTTPException(status_code=500, detail=str(e))
724
-
725
- if __name__ == "__main__":
726
- import uvicorn
727
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
18
 
19
  # Configure logging
20
  logging.basicConfig(
21
+ level=logging.DEBUG, # Set to DEBUG to capture detailed logs
22
  format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
23
  handlers=[logging.StreamHandler()]
24
  )
 
289
  ) as response_api_chat:
290
  response_api_chat.raise_for_status()
291
  text = await response_api_chat.text()
292
+ logger.debug(f"Raw response from Blackbox API: {text}") # Log raw response
293
  cleaned_response = cls.clean_response(text)
294
+ logger.debug(f"Cleaned response: {cleaned_response}") # Log cleaned response
295
  return cleaned_response
296
  except ClientResponseError as e:
297
  error_text = f"Error {e.status}: {e.message}"
 
299
  error_response = await e.response.text()
300
  cleaned_error = cls.clean_response(error_response)
301
  error_text += f" - {cleaned_error}"
302
+ logger.error(f"Blackbox API ClientResponseError: {error_text}")
303
  except Exception:
304
  pass
305
  return error_text
306
  except Exception as e:
307
+ logger.exception(f"Unexpected error during /api/chat request: {str(e)}")
308
  return f"Unexpected error during /api/chat request: {str(e)}"
309
 
310
+ # Custom exception for model not working
311
+ class ModelNotWorkingException(Exception):
312
+ def __init__(self, model: str):
313
+ self.model = model
314
+ self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
315
+ super().__init__(self.message)
 
 
 
 
 
316
 
317
+ async def cleanup_rate_limit_stores():
 
 
 
 
 
 
 
 
318
  """
319
+ Periodically cleans up stale entries in the rate_limit_store to prevent memory bloat.
320
+ """
321
+ while True:
322
+ current_time = time.time()
323
+ ips_to_delete = [ip for ip, value in rate_limit_store.items() if current_time - value["timestamp"] > RATE_LIMIT_WINDOW * 2]
324
+ for ip in ips_to_delete:
325
+ del rate_limit_store[ip]
326
+ logger.debug(f"Cleaned up rate_limit_store for IP: {ip}")
327
+ await asyncio.sleep(CLEANUP_INTERVAL)
328
+
329
+ async def rate_limiter_per_ip(request: Request):
330
+ """
331
+ Rate limiter that enforces a limit based on the client's IP address.
332
+ """
333
+ client_ip = request.client.host
334
+ current_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
+ # Initialize or update the count and timestamp
337
+ if current_time - rate_limit_store[client_ip]["timestamp"] > RATE_LIMIT_WINDOW:
338
+ rate_limit_store[client_ip] = {"count": 1, "timestamp": current_time}
339
+ else:
340
+ if rate_limit_store[client_ip]["count"] >= RATE_LIMIT:
341
+ logger.warning(f"Rate limit exceeded for IP address: {client_ip}")
342
+ raise HTTPException(status_code=429, detail='Rate limit exceeded for IP address | NiansuhAI')
343
+ rate_limit_store[client_ip]["count"] += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
+ async def get_api_key(request: Request, authorization: str = Header(None)) -> str:
346
+ """
347
+ Dependency to extract and validate the API key from the Authorization header.
348
+ """
349
+ client_ip = request.client.host
350
+ if authorization is None or not authorization.startswith('Bearer '):
351
+ logger.warning(f"Invalid or missing authorization header from IP: {client_ip}")
352
+ raise HTTPException(status_code=401, detail='Invalid authorization header format')
353
+ api_key = authorization[7:]
354
+ if api_key not in API_KEYS:
355
+ logger.warning(f"Invalid API key attempted: {api_key} from IP: {client_ip}")
356
+ raise HTTPException(status_code=401, detail='Invalid API key')
357
+ return api_key
358
+
359
+ # FastAPI app setup
360
+ app = FastAPI()
361
+
362
+ # Add the cleanup task when the app starts
363
+ @app.on_event("startup")
364
+ async def startup_event():
365
+ asyncio.create_task(cleanup_rate_limit_stores())
366
+ logger.info("Started rate limit store cleanup task.")
367
+
368
+ # Middleware to enhance security and enforce Content-Type for specific endpoints
369
+ @app.middleware("http")
370
+ async def security_middleware(request: Request, call_next):
371
+ client_ip = request.client.host
372
+ # Enforce that POST requests to /v1/chat/completions must have Content-Type: application/json
373
+ if request.method == "POST" and request.url.path == "/v1/chat/completions":
374
+ content_type = request.headers.get("Content-Type")
375
+ if content_type != "application/json":
376
+ logger.warning(f"Invalid Content-Type from IP: {client_ip} for path: {request.url.path}")
377
+ return JSONResponse(
378
+ status_code=400,
379
+ content={
380
+ "error": {
381
+ "message": "Content-Type must be application/json",
382
+ "type": "invalid_request_error",
383
+ "param": None,
384
+ "code": None
385
+ }
386
+ },
387
+ )
388
+ response = await call_next(request)
389
+ return response
390
+
391
+ # Request Models
392
+ class Message(BaseModel):
393
+ role: str
394
+ content: str
395
+
396
+ class ChatRequest(BaseModel):
397
+ model: str
398
+ messages: List[Message]
399
+ temperature: Optional[float] = 1.0
400
+ top_p: Optional[float] = 1.0
401
+ n: Optional[int] = 1
402
+ max_tokens: Optional[int] = None
403
+ presence_penalty: Optional[float] = 0.0
404
+ frequency_penalty: Optional[float] = 0.0
405
+ logit_bias: Optional[Dict[str, float]] = None
406
+ user: Optional[str] = None
407
+
408
+ @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
409
+ async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
410
+ client_ip = req.client.host
411
+ # Redact user messages only for logging purposes
412
+ redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
413
+
414
+ logger.info(f"Received chat completions request from API key: {api_key} | IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages}")
415
+
416
+ try:
417
+ # Validate that the requested model is available
418
+ if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
419
+ logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
420
+ raise HTTPException(status_code=400, detail="Requested model is not available.")
421
+
422
+ # Process the request with actual message content, but don't log it
423
+ response_content = await Blackbox.generate_response(
424
+ model=request.model,
425
+ messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
426
+ temperature=request.temperature,
427
+ max_tokens=request.max_tokens
428
+ )
429
 
430
+ logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
431
+ return {
432
+ "id": f"chatcmpl-{uuid.uuid4()}",
433
+ "object": "chat.completion",
434
+ "created": int(datetime.now().timestamp()),
435
+ "model": request.model,
436
+ "choices": [
437
+ {
438
+ "index": 0,
439
+ "message": {
440
+ "role": "assistant",
441
+ "content": response_content
442
+ },
443
+ "finish_reason": "stop"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
+ ],
446
+ "usage": {
447
+ "prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
448
+ "completion_tokens": len(response_content.split()),
449
+ "total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.split())
450
  },
451
+ }
452
+ except ModelNotWorkingException as e:
453
+ logger.warning(f"Model not working: {e} | IP: {client_ip}")
454
+ raise HTTPException(status_code=503, detail=str(e))
455
+ except HTTPException as he:
456
+ logger.warning(f"HTTPException: {he.detail} | IP: {client_ip}")
457
+ raise he
458
+ except Exception as e:
459
+ logger.exception(f"An unexpected error occurred while processing the chat completions request from IP: {client_ip}.")
460
+ raise HTTPException(status_code=500, detail=str(e))
461
+
462
+ # Endpoint: GET /v1/models
463
+ @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
464
+ async def get_models(req: Request):
465
+ client_ip = req.client.host
466
+ logger.info(f"Fetching available models from IP: {client_ip}")
467
+ return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
468
+
469
+ # Endpoint: GET /v1/health
470
+ @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
471
+ async def health_check(req: Request):
472
+ client_ip = req.client.host
473
+ logger.info(f"Health check requested from IP: {client_ip}")
474
+ return {"status": "ok"}
475
+
476
+ # Custom exception handler to match OpenAI's error format
477
+ @app.exception_handler(HTTPException)
478
+ async def http_exception_handler(request: Request, exc: HTTPException):
479
+ client_ip = request.client.host
480
+ logger.error(f"HTTPException: {exc.detail} | Path: {request.url.path} | IP: {client_ip}")
481
+ return JSONResponse(
482
+ status_code=exc.status_code,
483
+ content={
484
+ "error": {
485
+ "message": exc.detail,
486
+ "type": "invalid_request_error",
487
+ "param": None,
488
+ "code": None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  }
 
 
 
 
 
490
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  )
492
 
493
+ if __name__ == "__main__":
494
+ import uvicorn
495
+ uvicorn.run(app, host="0.0.0.0", port=8000)