Niansuh commited on
Commit
4a435c4
·
verified ·
1 Parent(s): c003de6

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +35 -72
main.py CHANGED
@@ -19,7 +19,7 @@ from pydantic import BaseModel
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
  )
25
  logger = logging.getLogger(__name__)
@@ -373,13 +373,19 @@ class Blackbox:
373
  # FastAPI app setup
374
  app = FastAPI()
375
 
376
- # Middleware to enhance security
377
  @app.middleware("http")
378
  async def security_middleware(request: Request, call_next):
 
 
 
 
 
379
  # Enforce that POST requests to sensitive endpoints must have a valid Content-Type
380
- if request.method == "POST" and request.url.path in ["/v1/chat/completions", "/v1/completions"]:
381
  content_type = request.headers.get("Content-Type")
382
  if content_type != "application/json":
 
383
  return JSONResponse(
384
  status_code=400,
385
  content={
@@ -391,7 +397,11 @@ async def security_middleware(request: Request, call_next):
391
  }
392
  },
393
  )
 
 
394
  response = await call_next(request)
 
 
395
  return response
396
 
397
  class Message(BaseModel):
@@ -431,15 +441,16 @@ def create_response(content: str, model: str, finish_reason: Optional[str] = Non
431
 
432
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter)])
433
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
 
434
  # Redact user messages only for logging purposes
435
  redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
436
 
437
- logger.info(f"Received chat completions request from API key: {api_key} | Model: {request.model} | Messages: {redacted_messages}")
438
 
439
  try:
440
  # Validate that the requested model is available
441
  if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
442
- logger.warning(f"Attempt to use unavailable model: {request.model}")
443
  raise HTTPException(status_code=400, detail="Requested model is not available. | NiansuhAI")
444
 
445
  # Process the request with actual message content, but don't log it
@@ -481,7 +492,7 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
481
  else:
482
  response_content += chunk
483
 
484
- logger.info(f"Completed non-streaming response generation for API key: {api_key}")
485
  return {
486
  "id": f"chatcmpl-{uuid.uuid4()}",
487
  "object": "chat.completion",
@@ -504,15 +515,17 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
504
  },
505
  }
506
  except ModelNotWorkingException as e:
507
- logger.warning(f"Model not working: {e}")
508
  raise HTTPException(status_code=503, detail=str(e))
509
  except HTTPException as he:
510
- logger.warning(f"HTTPException: {he.detail}")
511
  raise he
512
  except Exception as e:
513
  logger.exception("An unexpected error occurred while processing the chat completions request.")
514
  raise HTTPException(status_code=500, detail=str(e))
515
 
 
 
516
  # Return 'about:blank' when accessing the endpoint via GET
517
  @app.get("/v1/chat/completions")
518
  async def chat_completions_get():
@@ -520,31 +533,36 @@ async def chat_completions_get():
520
  return RedirectResponse(url='about:blank')
521
 
522
  @app.get("/v1/models")
523
- async def get_models():
524
- logger.info("Fetching available models")
 
525
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
526
 
527
  # Additional endpoints for better functionality
528
  @app.get("/v1/health")
529
  async def health_check(req: Request):
530
- logger.info("Health check requested")
 
531
  return {"status": "ok"}
532
 
533
  @app.get("/v1/models/{model}/status")
534
- async def model_status(model: str):
535
- logger.info(f"Model status requested for '{model}'")
 
536
  if model in Blackbox.models:
537
  return {"model": model, "status": "available"}
538
  elif model in Blackbox.model_aliases and Blackbox.model_aliases[model] in Blackbox.models:
539
  actual_model = Blackbox.model_aliases[model]
540
  return {"model": actual_model, "status": "available via alias"}
541
  else:
542
- logger.warning(f"Model not found: {model}")
543
  raise HTTPException(status_code=404, detail="Model not found")
544
 
545
  # Custom exception handler to match OpenAI's error format
546
  @app.exception_handler(HTTPException)
547
  async def http_exception_handler(request: Request, exc: HTTPException):
 
 
548
  return JSONResponse(
549
  status_code=exc.status_code,
550
  content={
@@ -562,68 +580,13 @@ class TokenizerRequest(BaseModel):
562
  text: str
563
 
564
  @app.post("/v1/tokenizer")
565
- async def tokenizer(request: TokenizerRequest):
 
566
  text = request.text
567
  token_count = len(text.split())
 
568
  return {"text": text, "tokens": token_count}
569
 
570
- # New endpoint: /v1/completions to support text completions
571
- class CompletionRequest(BaseModel):
572
- model: str
573
- prompt: str
574
- max_tokens: Optional[int] = 16
575
- temperature: Optional[float] = 1.0
576
- top_p: Optional[float] = 1.0
577
- n: Optional[int] = 1
578
- stream: Optional[bool] = False
579
- stop: Optional[Union[str, List[str]]] = None
580
- logprobs: Optional[int] = None
581
- echo: Optional[bool] = False
582
- presence_penalty: Optional[float] = 0.0
583
- frequency_penalty: Optional[float] = 0.0
584
- best_of: Optional[int] = 1
585
- logit_bias: Optional[Dict[str, float]] = None
586
- user: Optional[str] = None
587
-
588
- @app.post("/v1/completions")
589
- async def completions(request: CompletionRequest, req: Request):
590
- logger.info(f"Received completion request | Model: {request.model}")
591
-
592
- try:
593
- # Validate that the requested model is available
594
- if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
595
- logger.warning(f"Attempt to use unavailable model: {request.model}")
596
- raise HTTPException(status_code=400, detail="Requested model is not available.")
597
-
598
- # Simulate a simple completion by echoing the prompt
599
- completion_text = f"{request.prompt} [Completed by {request.model}]"
600
-
601
- return {
602
- "id": f"cmpl-{uuid.uuid4()}",
603
- "object": "text_completion",
604
- "created": int(datetime.now().timestamp()),
605
- "model": request.model,
606
- "choices": [
607
- {
608
- "text": completion_text,
609
- "index": 0,
610
- "logprobs": None,
611
- "finish_reason": "length"
612
- }
613
- ],
614
- "usage": {
615
- "prompt_tokens": len(request.prompt.split()),
616
- "completion_tokens": len(completion_text.split()),
617
- "total_tokens": len(request.prompt.split()) + len(completion_text.split())
618
- }
619
- }
620
- except HTTPException as he:
621
- logger.warning(f"HTTPException: {he.detail}")
622
- raise he
623
- except Exception as e:
624
- logger.exception("An unexpected error occurred while processing the completions request.")
625
- raise HTTPException(status_code=500, detail=str(e))
626
-
627
  if __name__ == "__main__":
628
  import uvicorn
629
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
19
  # Configure logging
20
  logging.basicConfig(
21
  level=logging.INFO,
22
+ format="%(asctime)s [%(levelname)s] %(name)s [IP: %(client_ip)s]: %(message)s",
23
  handlers=[logging.StreamHandler()]
24
  )
25
  logger = logging.getLogger(__name__)
 
373
  # FastAPI app setup
374
  app = FastAPI()
375
 
376
+ # Middleware to enhance security and log client IP
377
  @app.middleware("http")
378
  async def security_middleware(request: Request, call_next):
379
+ client_ip = request.client.host
380
+ # Enrich the logger with client_ip
381
+ extra = {"client_ip": client_ip}
382
+ logger = logging.LoggerAdapter(logging.getLogger(__name__), extra)
383
+
384
  # Enforce that POST requests to sensitive endpoints must have a valid Content-Type
385
+ if request.method == "POST" and request.url.path == "/v1/chat/completions":
386
  content_type = request.headers.get("Content-Type")
387
  if content_type != "application/json":
388
+ logger.warning("Invalid Content-Type for /v1/chat/completions")
389
  return JSONResponse(
390
  status_code=400,
391
  content={
 
397
  }
398
  },
399
  )
400
+ # Log the incoming request
401
+ logger.info(f"Incoming request: {request.method} {request.url.path}")
402
  response = await call_next(request)
403
+ # Log the response status
404
+ logger.info(f"Response status: {response.status_code}")
405
  return response
406
 
407
  class Message(BaseModel):
 
441
 
442
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter)])
443
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
444
+ client_ip = req.client.host
445
  # Redact user messages only for logging purposes
446
  redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
447
 
448
+ logger.info(f"Received chat completions request from API key: {api_key} | Client IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages}")
449
 
450
  try:
451
  # Validate that the requested model is available
452
  if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
453
+ logger.warning(f"Attempt to use unavailable model: {request.model} | Client IP: {client_ip}")
454
  raise HTTPException(status_code=400, detail="Requested model is not available. | NiansuhAI")
455
 
456
  # Process the request with actual message content, but don't log it
 
492
  else:
493
  response_content += chunk
494
 
495
+ logger.info(f"Completed non-streaming response generation for API key: {api_key} | Client IP: {client_ip}")
496
  return {
497
  "id": f"chatcmpl-{uuid.uuid4()}",
498
  "object": "chat.completion",
 
515
  },
516
  }
517
  except ModelNotWorkingException as e:
518
+ logger.warning(f"Model not working: {e} | Client IP: {client_ip}")
519
  raise HTTPException(status_code=503, detail=str(e))
520
  except HTTPException as he:
521
+ logger.warning(f"HTTPException: {he.detail} | Client IP: {client_ip}")
522
  raise he
523
  except Exception as e:
524
  logger.exception("An unexpected error occurred while processing the chat completions request.")
525
  raise HTTPException(status_code=500, detail=str(e))
526
 
527
+ # Removed the /v1/completions endpoint as per user request
528
+
529
  # Return 'about:blank' when accessing the endpoint via GET
530
  @app.get("/v1/chat/completions")
531
  async def chat_completions_get():
 
533
  return RedirectResponse(url='about:blank')
534
 
535
  @app.get("/v1/models")
536
+ async def get_models(req: Request):
537
+ client_ip = req.client.host
538
+ logger.info(f"Fetching available models | Client IP: {client_ip}")
539
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
540
 
541
  # Additional endpoints for better functionality
542
  @app.get("/v1/health")
543
  async def health_check(req: Request):
544
+ client_ip = req.client.host
545
+ logger.info(f"Health check requested | Client IP: {client_ip}")
546
  return {"status": "ok"}
547
 
548
  @app.get("/v1/models/{model}/status")
549
+ async def model_status(model: str, req: Request):
550
+ client_ip = req.client.host
551
+ logger.info(f"Model status requested for '{model}' | Client IP: {client_ip}")
552
  if model in Blackbox.models:
553
  return {"model": model, "status": "available"}
554
  elif model in Blackbox.model_aliases and Blackbox.model_aliases[model] in Blackbox.models:
555
  actual_model = Blackbox.model_aliases[model]
556
  return {"model": actual_model, "status": "available via alias"}
557
  else:
558
+ logger.warning(f"Model not found: {model} | Client IP: {client_ip}")
559
  raise HTTPException(status_code=404, detail="Model not found")
560
 
561
  # Custom exception handler to match OpenAI's error format
562
  @app.exception_handler(HTTPException)
563
  async def http_exception_handler(request: Request, exc: HTTPException):
564
+ client_ip = request.client.host
565
+ logger.error(f"HTTPException: {exc.detail} | Client IP: {client_ip}")
566
  return JSONResponse(
567
  status_code=exc.status_code,
568
  content={
 
580
  text: str
581
 
582
  @app.post("/v1/tokenizer")
583
+ async def tokenizer(request: TokenizerRequest, req: Request):
584
+ client_ip = req.client.host
585
  text = request.text
586
  token_count = len(text.split())
587
+ logger.info(f"Tokenizer called | Client IP: {client_ip} | Tokens: {token_count}")
588
  return {"text": text, "tokens": token_count}
589
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  if __name__ == "__main__":
591
  import uvicorn
592
  uvicorn.run(app, host="0.0.0.0", port=8000)