Niansuh commited on
Commit
ca8e367
·
verified ·
1 Parent(s): d1ec04f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +83 -244
main.py CHANGED
@@ -10,11 +10,10 @@ import time
10
  from collections import defaultdict
11
  from typing import List, Dict, Any, Optional, Union, AsyncGenerator
12
 
13
- from aiohttp import ClientSession, ClientResponseError, ClientTimeout
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
  # Configure logging
20
  logging.basicConfig(
@@ -39,32 +38,14 @@ rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
39
  CLEANUP_INTERVAL = 60 # seconds
40
  RATE_LIMIT_WINDOW = 60 # seconds
41
 
42
- # Define ImageResponseModel
43
  class ImageResponseModel(BaseModel):
44
  images: str
45
  alt: str
46
 
47
- def strip_markdown(text: str) -> str:
48
- """
49
- Strips markdown syntax from the given text to ensure plain text.
50
- """
51
- # Remove bold (**text** or __text__)
52
- text = re.sub(r'(\*\*|__)(.*?)\1', r'\2', text)
53
- # Remove italic (*text* or _text_)
54
- text = re.sub(r'(\*|_)(.*?)\1', r'\2', text)
55
- # Remove inline code (`code`)
56
- text = re.sub(r'`(.*?)`', r'\1', text)
57
- # Remove links [text](url)
58
- text = re.sub(r'\[(.*?)\]\((.*?)\)', r'\1', text)
59
- # Remove images ![alt](url)
60
- text = re.sub(r'!\[(.*?)\]\((.*?)\)', r'\1', text)
61
- # Remove headers (# Header)
62
- text = re.sub(r'#+\s+(.*)', r'\1', text)
63
- # Remove any remaining markdown characters
64
- text = re.sub(r'[*_`>#]', '', text)
65
- return text
66
-
67
- # Updated Blackbox Class
68
  class Blackbox:
69
  label = "Blackbox AI"
70
  url = "https://www.blackbox.ai"
@@ -160,21 +141,7 @@ class Blackbox:
160
  "blackboxai": "/?model=blackboxai",
161
  "gpt-4o": "/?model=gpt-4o",
162
  "gemini-pro": "/?model=gemini-pro",
163
- "claude-sonnet-3.5": "/?model=claude-sonnet-3.5",
164
- "ImageGeneration": "/?model=ImageGeneration",
165
- "PythonAgent": "/?model=PythonAgent",
166
- "JavaAgent": "/?model=JavaAgent",
167
- "JavaScriptAgent": "/?model=JavaScriptAgent",
168
- "HTMLAgent": "/?model=HTMLAgent",
169
- "GoogleCloudAgent": "/?model=GoogleCloudAgent",
170
- "AndroidDeveloper": "/?model=AndroidDeveloper",
171
- "SwiftDeveloper": "/?model=SwiftDeveloper",
172
- "Next.jsAgent": "/?model=Next.jsAgent",
173
- "MongoDBAgent": "/?model=MongoDBAgent",
174
- "PyTorchAgent": "/?model=PyTorchAgent",
175
- "ReactAgent": "/?model=ReactAgent",
176
- "XcodeAgent": "/?model=XcodeAgent",
177
- "AngularJSAgent": "/?model=AngularJSAgent",
178
  }
179
 
180
  model_aliases = {
@@ -226,8 +193,6 @@ class Blackbox:
226
  def clean_response(text: str) -> str:
227
  pattern = r'^\$\@\$v=undefined-rv1\$\@\$'
228
  cleaned_text = re.sub(pattern, '', text)
229
- # Strip markdown syntax to prevent bold text
230
- cleaned_text = strip_markdown(cleaned_text)
231
  return cleaned_text
232
 
233
  @classmethod
@@ -235,9 +200,12 @@ class Blackbox:
235
  cls,
236
  model: str,
237
  messages: List[Dict[str, str]],
 
 
 
238
  proxy: Optional[str] = None,
239
  **kwargs
240
- ) -> str:
241
  model = cls.get_model(model)
242
  chat_id = cls.generate_random_string()
243
  next_action = cls.generate_next_action()
@@ -301,9 +269,9 @@ class Blackbox:
301
  "trendingAgentMode": trending_agent_mode,
302
  "isMicMode": False,
303
  "userSystemPrompt": None,
304
- "maxTokens": 1024,
305
  "playgroundTopP": 0.9,
306
- "playgroundTemperature": 0.5,
307
  "isChromeExt": False,
308
  "githubToken": None,
309
  "clickedAnswer2": False,
@@ -311,11 +279,11 @@ class Blackbox:
311
  "clickedForceWebSearch": False,
312
  "visitFromDelta": False,
313
  "mobileClient": False,
314
- "webSearchMode": False,
315
  "userSelectedModel": cls.userSelectedModel.get(model, model)
316
  }
317
 
318
- async with ClientSession(headers=common_headers, timeout=ClientTimeout(total=60)) as session:
319
  try:
320
  async with session.post(
321
  cls.api_endpoint,
@@ -324,25 +292,43 @@ class Blackbox:
324
  proxy=proxy
325
  ) as response_api_chat:
326
  response_api_chat.raise_for_status()
327
- # Instead of reading the entire response, iterate over chunks
328
- async for data in response_api_chat.content.iter_chunked(1024):
329
- decoded_data = data.decode('utf-8')
330
- cleaned_response = cls.clean_response(decoded_data)
331
- if model in cls.image_models:
332
- match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_response)
 
 
 
 
 
 
 
 
333
  if match:
334
- image_url = match.group(1)
335
- image_response = ImageResponseModel(images=image_url, alt="Generated Image")
336
- return image_response.dict()
 
 
 
 
 
 
 
 
 
 
337
  else:
338
- return cleaned_response
339
  else:
340
  if '$~~~$' in cleaned_response:
341
  final_response = cleaned_response.split('$~~~$')[0].strip()
342
  else:
343
  final_response = cleaned_response
344
 
345
- return final_response
346
  except ClientResponseError as e:
347
  error_text = f"Error {e.status}: {e.message}"
348
  try:
@@ -355,158 +341,6 @@ class Blackbox:
355
  except Exception as e:
356
  return f"Unexpected error during /api/chat request: {str(e)}"
357
 
358
- @classmethod
359
- async def create_async_generator(
360
- cls,
361
- model: str,
362
- messages: List[Dict[str, str]],
363
- proxy: Optional[str] = None,
364
- websearch: bool = False,
365
- **kwargs
366
- ) -> AsyncGenerator[str, None]:
367
- """
368
- Creates an asynchronous generator for streaming responses from Blackbox AI.
369
-
370
- Parameters:
371
- model (str): Model to use for generating responses.
372
- messages (List[Dict[str, str]]): Message history.
373
- proxy (Optional[str]): Proxy URL, if needed.
374
- websearch (bool): Enables or disables web search mode.
375
- **kwargs: Additional keyword arguments.
376
-
377
- Yields:
378
- str: Segments of the generated response.
379
- """
380
- model = cls.get_model(model)
381
-
382
- chat_id = cls.generate_random_string()
383
- next_action = cls.generate_next_action()
384
- next_router_state_tree = cls.generate_next_router_state_tree()
385
-
386
- agent_mode = cls.agentMode.get(model, {})
387
- trending_agent_mode = cls.trendingAgentMode.get(model, {})
388
-
389
- prefix = cls.model_prefixes.get(model, "")
390
-
391
- formatted_prompt = ""
392
- for message in messages:
393
- role = message.get('role', '').capitalize()
394
- content = message.get('content', '')
395
- if role and content:
396
- formatted_prompt += f"{role}: {content}\n"
397
-
398
- if prefix:
399
- formatted_prompt = f"{prefix} {formatted_prompt}".strip()
400
-
401
- referer_path = cls.model_referers.get(model, f"/?model={model}")
402
- referer_url = f"{cls.url}{referer_path}"
403
-
404
- common_headers = {
405
- 'accept': '*/*',
406
- 'accept-language': 'en-US,en;q=0.9',
407
- 'cache-control': 'no-cache',
408
- 'origin': cls.url,
409
- 'pragma': 'no-cache',
410
- 'priority': 'u=1, i',
411
- 'sec-ch-ua': '"Chromium";v="129", "Not=A?Brand";v="8"',
412
- 'sec-ch-ua-mobile': '?0',
413
- 'sec-ch-ua-platform': '"Linux"',
414
- 'sec-fetch-dest': 'empty',
415
- 'sec-fetch-mode': 'cors',
416
- 'sec-fetch-site': 'same-origin',
417
- 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) '
418
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
419
- 'Chrome/129.0.0.0 Safari/537.36'
420
- }
421
-
422
- headers_api_chat = {
423
- 'Content-Type': 'application/json',
424
- 'Referer': referer_url
425
- }
426
- headers_api_chat_combined = {**common_headers, **headers_api_chat}
427
-
428
- payload_api_chat = {
429
- "messages": [
430
- {
431
- "id": chat_id,
432
- "content": formatted_prompt,
433
- "role": "user"
434
- }
435
- ],
436
- "id": chat_id,
437
- "previewToken": None,
438
- "userId": None,
439
- "codeModelMode": True,
440
- "agentMode": agent_mode,
441
- "trendingAgentMode": trending_agent_mode,
442
- "isMicMode": False,
443
- "userSystemPrompt": None,
444
- "maxTokens": 1024,
445
- "playgroundTopP": 0.9,
446
- "playgroundTemperature": 0.5,
447
- "isChromeExt": False,
448
- "githubToken": None,
449
- "clickedAnswer2": False,
450
- "clickedAnswer3": False,
451
- "clickedForceWebSearch": False,
452
- "visitFromDelta": False,
453
- "mobileClient": False,
454
- "webSearchMode": websearch,
455
- "userSelectedModel": cls.userSelectedModel.get(model, model)
456
- }
457
-
458
- headers_chat = {
459
- 'Accept': 'text/x-component',
460
- 'Content-Type': 'text/plain;charset=UTF-8',
461
- 'Referer': f'{cls.url}/chat/{chat_id}?model={model}',
462
- 'next-action': next_action,
463
- 'next-router-state-tree': next_router_state_tree,
464
- 'next-url': '/'
465
- }
466
- headers_chat_combined = {**common_headers, **headers_chat}
467
-
468
- data_chat = '[]'
469
-
470
- async with ClientSession(headers=common_headers, timeout=ClientTimeout(total=60)) as session:
471
- try:
472
- async with session.post(
473
- cls.api_endpoint,
474
- headers=headers_api_chat_combined,
475
- json=payload_api_chat,
476
- proxy=proxy
477
- ) as response_api_chat:
478
- response_api_chat.raise_for_status()
479
- # Iterate over the response in chunks
480
- async for data in response_api_chat.content.iter_any():
481
- decoded_data = data.decode('utf-8')
482
- cleaned_response = cls.clean_response(decoded_data)
483
- if model in cls.image_models:
484
- match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_response)
485
- if match:
486
- image_url = match.group(1)
487
- image_response = ImageResponseModel(images=image_url, alt="Generated Image")
488
- yield f"Image URL: {image_response.images}\n"
489
- else:
490
- yield cleaned_response
491
- else:
492
- if '$~~~$' in cleaned_response:
493
- final_response = cleaned_response.split('$~~~$')[0].strip()
494
- else:
495
- final_response = cleaned_response
496
-
497
- yield f"{final_response}\n"
498
- except ClientResponseError as e:
499
- error_text = f"Error {e.status}: {e.message}"
500
- try:
501
- error_response = await e.response.text()
502
- cleaned_error = cls.clean_response(error_response)
503
- error_text += f" - {cleaned_error}"
504
- except Exception:
505
- pass
506
- yield error_text
507
- except Exception as e:
508
- yield f"Unexpected error during /api/chat request: {str(e)}"
509
-
510
  # Custom exception for model not working
511
  class ModelNotWorkingException(Exception):
512
  def __init__(self, model: str):
@@ -599,12 +433,12 @@ class ChatRequest(BaseModel):
599
  temperature: Optional[float] = 1.0
600
  top_p: Optional[float] = 1.0
601
  n: Optional[int] = 1
602
- max_tokens: Optional[int] = None
603
  presence_penalty: Optional[float] = 0.0
604
  frequency_penalty: Optional[float] = 0.0
605
  logit_bias: Optional[Dict[str, float]] = None
606
  user: Optional[str] = None
607
- stream: Optional[bool] = False # Added stream parameter
608
 
609
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
610
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
@@ -612,7 +446,7 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
612
  # Redact user messages only for logging purposes
613
  redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
614
 
615
- logger.info(f"Received chat completions request from API key: {api_key} | IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages} | Stream: {request.stream}")
616
 
617
  try:
618
  # Validate that the requested model is available
@@ -620,36 +454,41 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
620
  logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
621
  raise HTTPException(status_code=400, detail="Requested model is not available.")
622
 
623
- if request.stream:
624
- # Streaming response
625
- async def event_generator():
626
- async for chunk in Blackbox.create_async_generator(
627
- model=request.model,
628
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
629
- proxy=None, # Add proxy if needed
630
- websearch=False # Modify if websearch is needed
631
- ):
632
- if isinstance(chunk, ImageResponseModel):
633
- # Yield image URLs as plain text in SSE format
634
- yield f"data: Image URL: {chunk.images}\n\n"
635
- else:
636
- # Ensure chunk is a string and yield as plain text in SSE format
637
- if isinstance(chunk, str):
638
- yield f"data: {chunk}\n\n"
639
- else:
640
- yield f"data: {str(chunk)}\n\n"
641
-
642
- logger.info(f"Initiating streaming response for API key: {api_key} | IP: {client_ip}")
643
- return StreamingResponse(event_generator(), media_type='text/event-stream')
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  else:
645
- # Non-streaming response
646
- response_content = await Blackbox.generate_response(
647
- model=request.model,
648
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
649
- temperature=request.temperature,
650
- max_tokens=request.max_tokens
651
- )
652
-
653
  logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
654
  return {
655
  "id": f"chatcmpl-{uuid.uuid4()}",
@@ -684,14 +523,14 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
684
 
685
  # Endpoint: GET /v1/models
686
  @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
687
- async def get_models(req: Request):
688
  client_ip = req.client.host
689
  logger.info(f"Fetching available models from IP: {client_ip}")
690
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
691
 
692
  # Endpoint: GET /v1/health
693
  @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
694
- async def health_check(req: Request):
695
  client_ip = req.client.host
696
  logger.info(f"Health check requested from IP: {client_ip}")
697
  return {"status": "ok"}
 
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(
 
38
  CLEANUP_INTERVAL = 60 # seconds
39
  RATE_LIMIT_WINDOW = 60 # seconds
40
 
41
+ # Define ImageResponse for handling image outputs
42
  class ImageResponseModel(BaseModel):
43
  images: str
44
  alt: str
45
 
46
+ # Define Messages type for better type hinting
47
+ Messages = List[Dict[str, Any]]
48
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  class Blackbox:
50
  label = "Blackbox AI"
51
  url = "https://www.blackbox.ai"
 
141
  "blackboxai": "/?model=blackboxai",
142
  "gpt-4o": "/?model=gpt-4o",
143
  "gemini-pro": "/?model=gemini-pro",
144
+ "claude-sonnet-3.5": "/?model=claude-sonnet-3.5"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
 
147
  model_aliases = {
 
193
  def clean_response(text: str) -> str:
194
  pattern = r'^\$\@\$v=undefined-rv1\$\@\$'
195
  cleaned_text = re.sub(pattern, '', text)
 
 
196
  return cleaned_text
197
 
198
  @classmethod
 
200
  cls,
201
  model: str,
202
  messages: List[Dict[str, str]],
203
+ temperature: Optional[float] = 1.0,
204
+ max_tokens: Optional[int] = 1024,
205
+ websearch: bool = False,
206
  proxy: Optional[str] = None,
207
  **kwargs
208
+ ) -> Union[str, ImageResponseModel]:
209
  model = cls.get_model(model)
210
  chat_id = cls.generate_random_string()
211
  next_action = cls.generate_next_action()
 
269
  "trendingAgentMode": trending_agent_mode,
270
  "isMicMode": False,
271
  "userSystemPrompt": None,
272
+ "maxTokens": max_tokens,
273
  "playgroundTopP": 0.9,
274
+ "playgroundTemperature": temperature,
275
  "isChromeExt": False,
276
  "githubToken": None,
277
  "clickedAnswer2": False,
 
279
  "clickedForceWebSearch": False,
280
  "visitFromDelta": False,
281
  "mobileClient": False,
282
+ "webSearchMode": websearch,
283
  "userSelectedModel": cls.userSelectedModel.get(model, model)
284
  }
285
 
286
+ async with ClientSession(headers=common_headers) as session:
287
  try:
288
  async with session.post(
289
  cls.api_endpoint,
 
292
  proxy=proxy
293
  ) as response_api_chat:
294
  response_api_chat.raise_for_status()
295
+ text = await response_api_chat.text()
296
+ cleaned_response = cls.clean_response(text)
297
+
298
+ if model in cls.image_models:
299
+ match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_response)
300
+ if match:
301
+ image_url = match.group(1)
302
+ image_response = ImageResponseModel(images=image_url, alt="Generated Image")
303
+ return image_response
304
+ else:
305
+ return cleaned_response
306
+ else:
307
+ if websearch:
308
+ match = re.search(r'\$~~~\$(.*?)\$~~~\$', cleaned_response, re.DOTALL)
309
  if match:
310
+ source_part = match.group(1).strip()
311
+ answer_part = cleaned_response[match.end():].strip()
312
+ try:
313
+ sources = json.loads(source_part)
314
+ source_formatted = "**Source:**\n"
315
+ for item in sources:
316
+ title = item.get('title', 'No Title')
317
+ link = item.get('link', '#')
318
+ position = item.get('position', '')
319
+ source_formatted += f"{position}. [{title}]({link})\n"
320
+ final_response = f"{answer_part}\n\n{source_formatted}"
321
+ except json.JSONDecodeError:
322
+ final_response = f"{answer_part}\n\nSource information is unavailable."
323
  else:
324
+ final_response = cleaned_response
325
  else:
326
  if '$~~~$' in cleaned_response:
327
  final_response = cleaned_response.split('$~~~$')[0].strip()
328
  else:
329
  final_response = cleaned_response
330
 
331
+ return final_response
332
  except ClientResponseError as e:
333
  error_text = f"Error {e.status}: {e.message}"
334
  try:
 
341
  except Exception as e:
342
  return f"Unexpected error during /api/chat request: {str(e)}"
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  # Custom exception for model not working
345
  class ModelNotWorkingException(Exception):
346
  def __init__(self, model: str):
 
433
  temperature: Optional[float] = 1.0
434
  top_p: Optional[float] = 1.0
435
  n: Optional[int] = 1
436
+ max_tokens: Optional[int] = 1024
437
  presence_penalty: Optional[float] = 0.0
438
  frequency_penalty: Optional[float] = 0.0
439
  logit_bias: Optional[Dict[str, float]] = None
440
  user: Optional[str] = None
441
+ websearch: Optional[bool] = False # Added websearch parameter
442
 
443
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
444
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
 
446
  # Redact user messages only for logging purposes
447
  redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
448
 
449
+ logger.info(f"Received chat completions request from API key: {api_key} | IP: {client_ip} | Model: {request.model} | Messages: {redacted_messages} | Websearch: {request.websearch}")
450
 
451
  try:
452
  # Validate that the requested model is available
 
454
  logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
455
  raise HTTPException(status_code=400, detail="Requested model is not available.")
456
 
457
+ # Generate response using the updated Blackbox class
458
+ response_content = await Blackbox.generate_response(
459
+ model=request.model,
460
+ messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
461
+ temperature=request.temperature,
462
+ max_tokens=request.max_tokens,
463
+ websearch=request.websearch
464
+ )
465
+
466
+ # Handle image responses
467
+ if isinstance(response_content, ImageResponseModel):
468
+ logger.info(f"Generated image for API key: {api_key} | IP: {client_ip}")
469
+ return {
470
+ "id": f"chatcmpl-{uuid.uuid4()}",
471
+ "object": "chat.completion",
472
+ "created": int(datetime.now().timestamp()),
473
+ "model": request.model,
474
+ "choices": [
475
+ {
476
+ "index": 0,
477
+ "message": {
478
+ "role": "assistant",
479
+ "content": response_content.images,
480
+ "alt": response_content.alt
481
+ },
482
+ "finish_reason": "stop"
483
+ }
484
+ ],
485
+ "usage": {
486
+ "prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
487
+ "completion_tokens": len(response_content.images.split()),
488
+ "total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.images.split())
489
+ },
490
+ }
491
  else:
 
 
 
 
 
 
 
 
492
  logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
493
  return {
494
  "id": f"chatcmpl-{uuid.uuid4()}",
 
523
 
524
  # Endpoint: GET /v1/models
525
  @app.get("/v1/models", dependencies=[Depends(rate_limiter_per_ip)])
526
+ async def get_models(req: Request, api_key: str = Depends(get_api_key)):
527
  client_ip = req.client.host
528
  logger.info(f"Fetching available models from IP: {client_ip}")
529
  return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
530
 
531
  # Endpoint: GET /v1/health
532
  @app.get("/v1/health", dependencies=[Depends(rate_limiter_per_ip)])
533
+ async def health_check(req: Request, api_key: str = Depends(get_api_key)):
534
  client_ip = req.client.host
535
  logger.info(f"Health check requested from IP: {client_ip}")
536
  return {"status": "ok"}