Spaces:
mxrkai
/
Runtime error

Niansuh commited on
Commit
6924925
·
verified ·
1 Parent(s): 6929f59

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +96 -134
main.py CHANGED
@@ -12,23 +12,19 @@ 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, StreamingResponse
16
  from pydantic import BaseModel
17
  from datetime import datetime
18
 
19
- # =====================
20
- # 1. Configure Logging
21
- # =====================
22
  logging.basicConfig(
23
- level=logging.DEBUG, # Set to DEBUG for detailed logs during development
24
  format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
25
  handlers=[logging.StreamHandler()]
26
  )
27
  logger = logging.getLogger(__name__)
28
 
29
- # ============================
30
- # 2. Load Environment Variables
31
- # ============================
32
  API_KEYS = os.getenv('API_KEYS', '').split(',') # Comma-separated API keys
33
  RATE_LIMIT = int(os.getenv('RATE_LIMIT', '60')) # Requests per minute
34
 
@@ -36,50 +32,19 @@ if not API_KEYS or API_KEYS == ['']:
36
  logger.error("No API keys found. Please set the API_KEYS environment variable.")
37
  raise Exception("API_KEYS environment variable not set.")
38
 
39
- # ====================================
40
- # 3. Define Rate Limiting Structures
41
- # ====================================
42
  rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
43
 
44
  # Define cleanup interval and window
45
  CLEANUP_INTERVAL = 60 # seconds
46
  RATE_LIMIT_WINDOW = 60 # seconds
47
 
48
- # ========================
49
- # 4. Define Pydantic Models
50
- # ========================
51
  class ImageResponseModel(BaseModel):
52
  images: str
53
  alt: str
54
 
55
- class Message(BaseModel):
56
- role: str
57
- content: str
58
-
59
- class ChatRequest(BaseModel):
60
- model: str
61
- messages: List[Message]
62
- temperature: Optional[float] = 1.0
63
- top_p: Optional[float] = 1.0
64
- n: Optional[int] = 1
65
- max_tokens: Optional[int] = None
66
- presence_penalty: Optional[float] = 0.0
67
- frequency_penalty: Optional[float] = 0.0
68
- logit_bias: Optional[Dict[str, float]] = None
69
- user: Optional[str] = None
70
-
71
- # ===============================
72
- # 5. Define Custom Exceptions
73
- # ===============================
74
- class ModelNotWorkingException(Exception):
75
- def __init__(self, model: str):
76
- self.model = model
77
- self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
78
- super().__init__(self.message)
79
-
80
- # =======================
81
- # 6. Define the Blackbox
82
- # =======================
83
  class Blackbox:
84
  label = "Blackbox AI"
85
  url = "https://www.blackbox.ai"
@@ -175,7 +140,21 @@ class Blackbox:
175
  "blackboxai": "/?model=blackboxai",
176
  "gpt-4o": "/?model=gpt-4o",
177
  "gemini-pro": "/?model=gemini-pro",
178
- "claude-sonnet-3.5": "/?model=claude-sonnet-3.5"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  }
180
 
181
  model_aliases = {
@@ -227,12 +206,7 @@ class Blackbox:
227
  def clean_response(text: str) -> str:
228
  pattern = r'^\$\@\$v=undefined-rv1\$\@\$'
229
  cleaned_text = re.sub(pattern, '', text)
230
- try:
231
- response_json = json.loads(cleaned_text)
232
- # Adjust based on actual response structure
233
- return response_json.get("response", response_json.get("data", cleaned_text))
234
- except json.JSONDecodeError:
235
- return cleaned_text.strip()
236
 
237
  @classmethod
238
  async def generate_response(
@@ -252,7 +226,6 @@ class Blackbox:
252
 
253
  prefix = cls.model_prefixes.get(model, "")
254
 
255
- # Construct the prompt
256
  formatted_prompt = ""
257
  for message in messages:
258
  role = message.get('role', '').capitalize()
@@ -298,12 +271,30 @@ class Blackbox:
298
  "role": "user"
299
  }
300
  ],
301
- "model": model # Simplified payload for testing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  }
303
 
304
  async with ClientSession(headers=common_headers) as session:
305
  try:
306
- logger.debug(f"Payload sent to Blackbox API: {json.dumps(payload_api_chat)}")
307
  async with session.post(
308
  cls.api_endpoint,
309
  headers=headers_api_chat_combined,
@@ -312,9 +303,7 @@ class Blackbox:
312
  ) as response_api_chat:
313
  response_api_chat.raise_for_status()
314
  text = await response_api_chat.text()
315
- logger.debug(f"Raw response from Blackbox API: {text}") # Log raw response
316
  cleaned_response = cls.clean_response(text)
317
- logger.debug(f"Cleaned response: {cleaned_response}") # Log cleaned response
318
  return cleaned_response
319
  except ClientResponseError as e:
320
  error_text = f"Error {e.status}: {e.message}"
@@ -322,43 +311,18 @@ class Blackbox:
322
  error_response = await e.response.text()
323
  cleaned_error = cls.clean_response(error_response)
324
  error_text += f" - {cleaned_error}"
325
- logger.error(f"Blackbox API ClientResponseError: {error_text}")
326
  except Exception:
327
  pass
328
  return error_text
329
  except Exception as e:
330
- logger.exception(f"Unexpected error during /api/chat request: {str(e)}")
331
  return f"Unexpected error during /api/chat request: {str(e)}"
332
 
333
- # ============================
334
- # 7. Initialize FastAPI App
335
- # ============================
336
- app = FastAPI()
337
-
338
- # ====================================
339
- # 8. Define Middleware and Dependencies
340
- # ====================================
341
- @app.middleware("http")
342
- async def security_middleware(request: Request, call_next):
343
- client_ip = request.client.host
344
- # Enforce that POST requests to /v1/chat/completions must have Content-Type: application/json
345
- if request.method == "POST" and request.url.path == "/v1/chat/completions":
346
- content_type = request.headers.get("Content-Type")
347
- if content_type != "application/json":
348
- logger.warning(f"Invalid Content-Type from IP: {client_ip} for path: {request.url.path}")
349
- return JSONResponse(
350
- status_code=400,
351
- content={
352
- "error": {
353
- "message": "Content-Type must be application/json",
354
- "type": "invalid_request_error",
355
- "param": None,
356
- "code": None
357
- }
358
- },
359
- )
360
- response = await call_next(request)
361
- return response
362
 
363
  async def cleanup_rate_limit_stores():
364
  """
@@ -402,17 +366,55 @@ async def get_api_key(request: Request, authorization: str = Header(None)) -> st
402
  raise HTTPException(status_code=401, detail='Invalid API key')
403
  return api_key
404
 
405
- # =====================================
406
- # 9. Define FastAPI Event Handlers
407
- # =====================================
 
408
  @app.on_event("startup")
409
  async def startup_event():
410
  asyncio.create_task(cleanup_rate_limit_stores())
411
  logger.info("Started rate limit store cleanup task.")
412
 
413
- # ==========================================
414
- # 10. Define FastAPI Endpoints
415
- # ==========================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
417
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
418
  client_ip = req.client.host
@@ -467,21 +469,21 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
467
  logger.exception(f"An unexpected error occurred while processing the chat completions request from IP: {client_ip}.")
468
  raise HTTPException(status_code=500, detail=str(e))
469
 
 
470
  @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
471
  async def get_models(req: Request):
472
  client_ip = req.client.host
473
  logger.info(f"Fetching available models from IP: {client_ip}")
474
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
475
 
 
476
  @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
477
  async def health_check(req: Request):
478
  client_ip = req.client.host
479
  logger.info(f"Health check requested from IP: {client_ip}")
480
  return {"status": "ok"}
481
 
482
- # ========================================
483
- # 11. Define Custom Exception Handler
484
- # ========================================
485
  @app.exception_handler(HTTPException)
486
  async def http_exception_handler(request: Request, exc: HTTPException):
487
  client_ip = request.client.host
@@ -498,46 +500,6 @@ async def http_exception_handler(request: Request, exc: HTTPException):
498
  },
499
  )
500
 
501
- # ============================
502
- # 12. Optional: Streaming Endpoint
503
- # ============================
504
- @app.post("/v1/chat/completions/stream", dependencies=[Depends(rate_limiter_per_ip)])
505
- async def chat_completions_stream(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
506
- client_ip = req.client.host
507
- # Redact user messages only for logging purposes
508
- redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
509
-
510
- logger.info(f"Received streaming chat completions request from API key: {api_key} | IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages}")
511
-
512
- try:
513
- # Validate that the requested model is available
514
- if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
515
- logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
516
- raise HTTPException(status_code=400, detail="Requested model is not available.")
517
-
518
- # Create an asynchronous generator for the response
519
- async_generator = Blackbox.create_async_generator(
520
- model=request.model,
521
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
522
- temperature=request.temperature,
523
- max_tokens=request.max_tokens
524
- )
525
-
526
- logger.info(f"Started streaming response for API key: {api_key} | IP: {client_ip}")
527
- return StreamingResponse(async_generator, media_type="text/event-stream")
528
- except ModelNotWorkingException as e:
529
- logger.warning(f"Model not working: {e} | IP: {client_ip}")
530
- raise HTTPException(status_code=503, detail=str(e))
531
- except HTTPException as he:
532
- logger.warning(f"HTTPException: {he.detail} | IP: {client_ip}")
533
- raise he
534
- except Exception as e:
535
- logger.exception(f"An unexpected error occurred while processing the streaming chat completions request from IP: {client_ip}.")
536
- raise HTTPException(status_code=500, detail=str(e))
537
-
538
- # ========================================
539
- # 13. Run the Application with Uvicorn
540
- # ========================================
541
  if __name__ == "__main__":
542
  import uvicorn
543
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
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
  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
  )
25
  logger = logging.getLogger(__name__)
26
 
27
+ # Load environment variables
 
 
28
  API_KEYS = os.getenv('API_KEYS', '').split(',') # Comma-separated API keys
29
  RATE_LIMIT = int(os.getenv('RATE_LIMIT', '60')) # Requests per minute
30
 
 
32
  logger.error("No API keys found. Please set the API_KEYS environment variable.")
33
  raise Exception("API_KEYS environment variable not set.")
34
 
35
+ # Simple in-memory rate limiter based solely on IP addresses
 
 
36
  rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
37
 
38
  # Define cleanup interval and window
39
  CLEANUP_INTERVAL = 60 # seconds
40
  RATE_LIMIT_WINDOW = 60 # seconds
41
 
42
+ # Define ImageResponse if needed
 
 
43
  class ImageResponseModel(BaseModel):
44
  images: str
45
  alt: str
46
 
47
+ # Updated Blackbox Class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  class Blackbox:
49
  label = "Blackbox AI"
50
  url = "https://www.blackbox.ai"
 
140
  "blackboxai": "/?model=blackboxai",
141
  "gpt-4o": "/?model=gpt-4o",
142
  "gemini-pro": "/?model=gemini-pro",
143
+ "claude-sonnet-3.5": "/?model=claude-sonnet-3.5",
144
+ "ImageGeneration": "/?model=ImageGeneration",
145
+ "PythonAgent": "/?model=PythonAgent",
146
+ "JavaAgent": "/?model=JavaAgent",
147
+ "JavaScriptAgent": "/?model=JavaScriptAgent",
148
+ "HTMLAgent": "/?model=HTMLAgent",
149
+ "GoogleCloudAgent": "/?model=GoogleCloudAgent",
150
+ "AndroidDeveloper": "/?model=AndroidDeveloper",
151
+ "SwiftDeveloper": "/?model=SwiftDeveloper",
152
+ "Next.jsAgent": "/?model=Next.jsAgent",
153
+ "MongoDBAgent": "/?model=MongoDBAgent",
154
+ "PyTorchAgent": "/?model=PyTorchAgent",
155
+ "ReactAgent": "/?model=ReactAgent",
156
+ "XcodeAgent": "/?model=XcodeAgent",
157
+ "AngularJSAgent": "/?model=AngularJSAgent",
158
  }
159
 
160
  model_aliases = {
 
206
  def clean_response(text: str) -> str:
207
  pattern = r'^\$\@\$v=undefined-rv1\$\@\$'
208
  cleaned_text = re.sub(pattern, '', text)
209
+ return cleaned_text
 
 
 
 
 
210
 
211
  @classmethod
212
  async def generate_response(
 
226
 
227
  prefix = cls.model_prefixes.get(model, "")
228
 
 
229
  formatted_prompt = ""
230
  for message in messages:
231
  role = message.get('role', '').capitalize()
 
271
  "role": "user"
272
  }
273
  ],
274
+ "id": chat_id,
275
+ "previewToken": None,
276
+ "userId": None,
277
+ "codeModelMode": True,
278
+ "agentMode": agent_mode,
279
+ "trendingAgentMode": trending_agent_mode,
280
+ "isMicMode": False,
281
+ "userSystemPrompt": None,
282
+ "maxTokens": 1024,
283
+ "playgroundTopP": 0.9,
284
+ "playgroundTemperature": 0.5,
285
+ "isChromeExt": False,
286
+ "githubToken": None,
287
+ "clickedAnswer2": False,
288
+ "clickedAnswer3": False,
289
+ "clickedForceWebSearch": False,
290
+ "visitFromDelta": False,
291
+ "mobileClient": False,
292
+ "webSearchMode": False,
293
+ "userSelectedModel": cls.userSelectedModel.get(model, model)
294
  }
295
 
296
  async with ClientSession(headers=common_headers) as session:
297
  try:
 
298
  async with session.post(
299
  cls.api_endpoint,
300
  headers=headers_api_chat_combined,
 
303
  ) as response_api_chat:
304
  response_api_chat.raise_for_status()
305
  text = await response_api_chat.text()
 
306
  cleaned_response = cls.clean_response(text)
 
307
  return cleaned_response
308
  except ClientResponseError as e:
309
  error_text = f"Error {e.status}: {e.message}"
 
311
  error_response = await e.response.text()
312
  cleaned_error = cls.clean_response(error_response)
313
  error_text += f" - {cleaned_error}"
 
314
  except Exception:
315
  pass
316
  return error_text
317
  except Exception as e:
 
318
  return f"Unexpected error during /api/chat request: {str(e)}"
319
 
320
+ # Custom exception for model not working
321
+ class ModelNotWorkingException(Exception):
322
+ def __init__(self, model: str):
323
+ self.model = model
324
+ self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
325
+ super().__init__(self.message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
  async def cleanup_rate_limit_stores():
328
  """
 
366
  raise HTTPException(status_code=401, detail='Invalid API key')
367
  return api_key
368
 
369
+ # FastAPI app setup
370
+ app = FastAPI()
371
+
372
+ # Add the cleanup task when the app starts
373
  @app.on_event("startup")
374
  async def startup_event():
375
  asyncio.create_task(cleanup_rate_limit_stores())
376
  logger.info("Started rate limit store cleanup task.")
377
 
378
+ # Middleware to enhance security and enforce Content-Type for specific endpoints
379
+ @app.middleware("http")
380
+ async def security_middleware(request: Request, call_next):
381
+ client_ip = request.client.host
382
+ # Enforce that POST requests to /v1/chat/completions must have Content-Type: application/json
383
+ if request.method == "POST" and request.url.path == "/v1/chat/completions":
384
+ content_type = request.headers.get("Content-Type")
385
+ if content_type != "application/json":
386
+ logger.warning(f"Invalid Content-Type from IP: {client_ip} for path: {request.url.path}")
387
+ return JSONResponse(
388
+ status_code=400,
389
+ content={
390
+ "error": {
391
+ "message": "Content-Type must be application/json",
392
+ "type": "invalid_request_error",
393
+ "param": None,
394
+ "code": None
395
+ }
396
+ },
397
+ )
398
+ response = await call_next(request)
399
+ return response
400
+
401
+ # Request Models
402
+ class Message(BaseModel):
403
+ role: str
404
+ content: str
405
+
406
+ class ChatRequest(BaseModel):
407
+ model: str
408
+ messages: List[Message]
409
+ temperature: Optional[float] = 1.0
410
+ top_p: Optional[float] = 1.0
411
+ n: Optional[int] = 1
412
+ max_tokens: Optional[int] = None
413
+ presence_penalty: Optional[float] = 0.0
414
+ frequency_penalty: Optional[float] = 0.0
415
+ logit_bias: Optional[Dict[str, float]] = None
416
+ user: Optional[str] = None
417
+
418
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
419
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
420
  client_ip = req.client.host
 
469
  logger.exception(f"An unexpected error occurred while processing the chat completions request from IP: {client_ip}.")
470
  raise HTTPException(status_code=500, detail=str(e))
471
 
472
+ # Endpoint: GET /v1/models
473
  @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
474
  async def get_models(req: Request):
475
  client_ip = req.client.host
476
  logger.info(f"Fetching available models from IP: {client_ip}")
477
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
478
 
479
+ # Endpoint: GET /v1/health
480
  @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
481
  async def health_check(req: Request):
482
  client_ip = req.client.host
483
  logger.info(f"Health check requested from IP: {client_ip}")
484
  return {"status": "ok"}
485
 
486
+ # Custom exception handler to match OpenAI's error format
 
 
487
  @app.exception_handler(HTTPException)
488
  async def http_exception_handler(request: Request, exc: HTTPException):
489
  client_ip = request.client.host
 
500
  },
501
  )
502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  if __name__ == "__main__":
504
  import uvicorn
505
  uvicorn.run(app, host="0.0.0.0", port=8000)