Update main.py
Browse files
main.py
CHANGED
@@ -1,18 +1,19 @@
|
|
1 |
import os
|
2 |
import re
|
|
|
|
|
3 |
import uuid
|
4 |
import json
|
5 |
import logging
|
6 |
import asyncio
|
7 |
import time
|
8 |
from collections import defaultdict
|
9 |
-
from typing import List, Dict, Any, Optional, AsyncGenerator
|
10 |
-
|
11 |
from datetime import datetime
|
12 |
|
13 |
from aiohttp import ClientSession, ClientTimeout, ClientError
|
14 |
from fastapi import FastAPI, HTTPException, Request, Depends, Header
|
15 |
-
from fastapi.responses import StreamingResponse
|
16 |
from pydantic import BaseModel
|
17 |
|
18 |
# Configure logging
|
@@ -25,26 +26,48 @@ logger = logging.getLogger(__name__)
|
|
25 |
|
26 |
# Load environment variables
|
27 |
API_KEYS = os.getenv('API_KEYS', '').split(',') # Comma-separated API keys
|
28 |
-
|
29 |
|
30 |
if not API_KEYS or API_KEYS == ['']:
|
31 |
logger.error("No API keys found. Please set the API_KEYS environment variable.")
|
32 |
raise Exception("API_KEYS environment variable not set.")
|
33 |
|
34 |
-
# Simple in-memory rate limiter
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
-
async def rate_limiter(
|
38 |
-
client_host = request.client.host
|
39 |
current_time = time.time()
|
40 |
-
|
|
|
41 |
if current_time - window_start > 60:
|
42 |
-
|
43 |
else:
|
44 |
-
if
|
45 |
-
logger.warning(f"Rate limit exceeded for
|
46 |
-
raise HTTPException(status_code=429, detail='Rate limit exceeded
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
# Custom exception for model not working
|
50 |
class ModelNotWorkingException(Exception):
|
@@ -195,7 +218,7 @@ class Blackbox:
|
|
195 |
if not cls.working or model not in cls.models:
|
196 |
logger.error(f"Model {model} is not working or not supported.")
|
197 |
raise ModelNotWorkingException(model)
|
198 |
-
|
199 |
headers = {
|
200 |
"accept": "*/*",
|
201 |
"accept-language": "en-US,en;q=0.9",
|
@@ -211,7 +234,7 @@ class Blackbox:
|
|
211 |
"sec-fetch-dest": "empty",
|
212 |
"sec-fetch-mode": "cors",
|
213 |
"sec-fetch-site": "same-origin",
|
214 |
-
"user-agent": "Mozilla/5.0 (X11; Linux x86_64)",
|
215 |
}
|
216 |
|
217 |
if model in cls.model_prefixes:
|
@@ -219,7 +242,7 @@ class Blackbox:
|
|
219 |
if not messages[0]['content'].startswith(prefix):
|
220 |
logger.debug(f"Adding prefix '{prefix}' to the first message.")
|
221 |
messages[0]['content'] = f"{prefix} {messages[0]['content']}"
|
222 |
-
|
223 |
random_id = ''.join(random.choices(string.ascii_letters + string.digits, k=7))
|
224 |
messages[-1]['id'] = random_id
|
225 |
messages[-1]['role'] = 'user'
|
@@ -235,7 +258,7 @@ class Blackbox:
|
|
235 |
}
|
236 |
messages[-1]['content'] = 'FILE:BB\n$#$\n\n$#$\n' + messages[-1]['content']
|
237 |
logger.debug("Image data added to the message.")
|
238 |
-
|
239 |
data = {
|
240 |
"messages": messages,
|
241 |
"id": random_id,
|
@@ -268,8 +291,8 @@ class Blackbox:
|
|
268 |
data["userSelectedModel"] = cls.userSelectedModel[model]
|
269 |
logger.info(f"Sending request to {cls.api_endpoint} with data (excluding messages).")
|
270 |
|
271 |
-
timeout = ClientTimeout(total=
|
272 |
-
retry_attempts =
|
273 |
|
274 |
for attempt in range(retry_attempts):
|
275 |
try:
|
@@ -335,204 +358,162 @@ class Blackbox:
|
|
335 |
# FastAPI app setup
|
336 |
app = FastAPI()
|
337 |
|
338 |
-
# Implement per-IP rate limiting middleware
|
339 |
-
@app.middleware("http")
|
340 |
-
async def rate_limit_middleware(request: Request, call_next):
|
341 |
-
await rate_limiter(request)
|
342 |
-
response = await call_next(request)
|
343 |
-
return response
|
344 |
-
|
345 |
-
# Pydantic models for OpenAI API
|
346 |
class Message(BaseModel):
|
347 |
role: str
|
348 |
content: str
|
349 |
|
350 |
-
class
|
351 |
model: str
|
352 |
messages: List[Message]
|
353 |
temperature: Optional[float] = 1.0
|
354 |
top_p: Optional[float] = 1.0
|
355 |
n: Optional[int] = 1
|
356 |
stream: Optional[bool] = False
|
357 |
-
stop: Optional[
|
358 |
max_tokens: Optional[int] = None
|
359 |
presence_penalty: Optional[float] = 0.0
|
360 |
frequency_penalty: Optional[float] = 0.0
|
361 |
logit_bias: Optional[Dict[str, float]] = None
|
362 |
user: Optional[str] = None
|
|
|
363 |
|
364 |
-
def
|
365 |
return {
|
366 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
367 |
-
"object": "chat.completion",
|
368 |
"created": int(datetime.now().timestamp()),
|
369 |
"model": model,
|
370 |
"choices": [
|
371 |
{
|
372 |
"index": 0,
|
373 |
-
"
|
374 |
-
|
375 |
-
"content": content
|
376 |
-
},
|
377 |
-
"finish_reason": "stop"
|
378 |
}
|
379 |
],
|
380 |
-
"usage":
|
381 |
}
|
382 |
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
if content:
|
388 |
-
delta['content'] = content
|
389 |
-
return {
|
390 |
-
"object": "chat.completion.chunk",
|
391 |
-
"created": int(datetime.now().timestamp()),
|
392 |
-
"model": "", # Model name can be added if necessary
|
393 |
-
"choices": [
|
394 |
-
{
|
395 |
-
"delta": delta,
|
396 |
-
"index": 0,
|
397 |
-
"finish_reason": finish_reason
|
398 |
-
}
|
399 |
-
]
|
400 |
-
}
|
401 |
|
402 |
-
|
403 |
-
async def chat_completions(request: ChatCompletionRequest, authorization: str = Header(None)):
|
404 |
-
# Verify API key
|
405 |
-
if not authorization or not authorization.startswith('Bearer '):
|
406 |
-
logger.warning("Invalid authorization header format.")
|
407 |
-
raise HTTPException(status_code=401, detail='Invalid authorization header format.')
|
408 |
-
api_key = authorization[7:]
|
409 |
-
if api_key not in API_KEYS:
|
410 |
-
logger.warning(f"Invalid API key attempted: {api_key}")
|
411 |
-
raise HTTPException(status_code=401, detail='Invalid API key.')
|
412 |
-
|
413 |
-
logger.info(f"Received chat completion request for model: {request.model}")
|
414 |
|
415 |
-
# Validate model
|
416 |
-
if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
|
417 |
-
logger.warning(f"Attempt to use unavailable model: {request.model}")
|
418 |
-
raise HTTPException(status_code=400, detail="The model is not available.")
|
419 |
-
|
420 |
-
# Process the request
|
421 |
try:
|
422 |
-
#
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
|
|
|
|
|
|
|
|
|
|
429 |
)
|
430 |
|
431 |
-
if image_generation_requested:
|
432 |
-
model = 'ImageGeneration'
|
433 |
-
# For image generation, use the last message as prompt
|
434 |
-
prompt = messages[-1]['content']
|
435 |
-
# Build messages for the Blackbox.create_async_generator
|
436 |
-
messages = [{"role": "user", "content": prompt}]
|
437 |
-
async_generator = Blackbox.create_async_generator(
|
438 |
-
model=model,
|
439 |
-
messages=messages,
|
440 |
-
image=None,
|
441 |
-
image_name=None,
|
442 |
-
webSearchMode=False
|
443 |
-
)
|
444 |
-
|
445 |
-
# Collect images
|
446 |
-
images = []
|
447 |
-
count = 0
|
448 |
-
async for response in async_generator:
|
449 |
-
if isinstance(response, ImageResponse):
|
450 |
-
images.append(response.url)
|
451 |
-
count += 1
|
452 |
-
if count >= request.n:
|
453 |
-
break
|
454 |
-
|
455 |
-
# Build response content with image URLs
|
456 |
-
response_content = "\n".join(f"" for url in images)
|
457 |
-
completion_tokens = len(response_content.split())
|
458 |
-
else:
|
459 |
-
# Use the requested model
|
460 |
-
async_generator = Blackbox.create_async_generator(
|
461 |
-
model=request.model,
|
462 |
-
messages=messages,
|
463 |
-
image=None,
|
464 |
-
image_name=None,
|
465 |
-
webSearchMode=False
|
466 |
-
)
|
467 |
-
# Usage tracking
|
468 |
-
completion_tokens = 0 # Will be updated as we process the response
|
469 |
-
|
470 |
-
prompt_tokens = sum(len(msg['content'].split()) for msg in messages)
|
471 |
-
|
472 |
if request.stream:
|
473 |
async def generate():
|
474 |
-
nonlocal completion_tokens
|
475 |
try:
|
476 |
-
# Initial delta with role
|
477 |
-
initial_chunk = create_stream_response_chunk(content=None, role="assistant")
|
478 |
-
yield f"data: {json.dumps(initial_chunk)}\n\n"
|
479 |
-
|
480 |
async for chunk in async_generator:
|
481 |
-
if isinstance(chunk,
|
482 |
-
|
483 |
-
response_chunk =
|
484 |
-
yield f"data: {json.dumps(response_chunk)}\n\n"
|
485 |
-
elif isinstance(chunk, ImageResponse):
|
486 |
-
content = f""
|
487 |
-
completion_tokens += len(content.split())
|
488 |
-
response_chunk = create_stream_response_chunk(content=content)
|
489 |
-
yield f"data: {json.dumps(response_chunk)}\n\n"
|
490 |
else:
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
yield "data: [DONE]\n\n"
|
|
|
|
|
|
|
496 |
except Exception as e:
|
497 |
logger.exception("Error during streaming response generation.")
|
498 |
-
|
|
|
|
|
499 |
return StreamingResponse(generate(), media_type="text/event-stream")
|
500 |
else:
|
501 |
response_content = ""
|
502 |
async for chunk in async_generator:
|
503 |
-
if isinstance(chunk,
|
|
|
|
|
504 |
response_content += chunk
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
"
|
510 |
-
"
|
511 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
512 |
}
|
513 |
-
return create_chat_completion_response(response_content, request.model, usage)
|
514 |
except ModelNotWorkingException as e:
|
515 |
logger.warning(f"Model not working: {e}")
|
516 |
raise HTTPException(status_code=503, detail=str(e))
|
|
|
|
|
|
|
517 |
except Exception as e:
|
518 |
logger.exception("An unexpected error occurred while processing the chat completions request.")
|
519 |
raise HTTPException(status_code=500, detail=str(e))
|
520 |
|
521 |
-
@app.get("/v1/models")
|
522 |
-
async def get_models(
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
536 |
|
537 |
if __name__ == "__main__":
|
538 |
import uvicorn
|
|
|
1 |
import os
|
2 |
import re
|
3 |
+
import random
|
4 |
+
import string
|
5 |
import uuid
|
6 |
import json
|
7 |
import logging
|
8 |
import asyncio
|
9 |
import time
|
10 |
from collections import defaultdict
|
11 |
+
from typing import List, Dict, Any, Optional, AsyncGenerator, Union
|
|
|
12 |
from datetime import datetime
|
13 |
|
14 |
from aiohttp import ClientSession, ClientTimeout, ClientError
|
15 |
from fastapi import FastAPI, HTTPException, Request, Depends, Header
|
16 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
17 |
from pydantic import BaseModel
|
18 |
|
19 |
# Configure logging
|
|
|
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 |
|
31 |
if not API_KEYS or API_KEYS == ['']:
|
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
|
36 |
+
rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
|
37 |
+
ip_rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
|
38 |
+
|
39 |
+
async def get_api_key(authorization: str = Header(...)) -> str:
|
40 |
+
if not authorization.startswith('Bearer '):
|
41 |
+
logger.warning("Invalid authorization header format.")
|
42 |
+
raise HTTPException(status_code=401, detail='Invalid authorization header format')
|
43 |
+
api_key = authorization[7:]
|
44 |
+
if api_key not in API_KEYS:
|
45 |
+
logger.warning(f"Invalid API key attempted: {api_key}")
|
46 |
+
raise HTTPException(status_code=401, detail='Invalid API key')
|
47 |
+
return api_key
|
48 |
|
49 |
+
async def rate_limiter(req: Request, api_key: str = Depends(get_api_key)):
|
|
|
50 |
current_time = time.time()
|
51 |
+
# Rate limiting per API key
|
52 |
+
window_start = rate_limit_store[api_key]["timestamp"]
|
53 |
if current_time - window_start > 60:
|
54 |
+
rate_limit_store[api_key] = {"count": 1, "timestamp": current_time}
|
55 |
else:
|
56 |
+
if rate_limit_store[api_key]["count"] >= RATE_LIMIT:
|
57 |
+
logger.warning(f"Rate limit exceeded for API key: {api_key}")
|
58 |
+
raise HTTPException(status_code=429, detail='Rate limit exceeded for API key')
|
59 |
+
rate_limit_store[api_key]["count"] += 1
|
60 |
+
|
61 |
+
# Rate limiting per IP address
|
62 |
+
client_ip = req.client.host
|
63 |
+
window_start_ip = ip_rate_limit_store[client_ip]["timestamp"]
|
64 |
+
if current_time - window_start_ip > 60:
|
65 |
+
ip_rate_limit_store[client_ip] = {"count": 1, "timestamp": current_time}
|
66 |
+
else:
|
67 |
+
if ip_rate_limit_store[client_ip]["count"] >= RATE_LIMIT:
|
68 |
+
logger.warning(f"Rate limit exceeded for IP address: {client_ip}")
|
69 |
+
raise HTTPException(status_code=429, detail='Rate limit exceeded for IP address')
|
70 |
+
ip_rate_limit_store[client_ip]["count"] += 1
|
71 |
|
72 |
# Custom exception for model not working
|
73 |
class ModelNotWorkingException(Exception):
|
|
|
218 |
if not cls.working or model not in cls.models:
|
219 |
logger.error(f"Model {model} is not working or not supported.")
|
220 |
raise ModelNotWorkingException(model)
|
221 |
+
|
222 |
headers = {
|
223 |
"accept": "*/*",
|
224 |
"accept-language": "en-US,en;q=0.9",
|
|
|
234 |
"sec-fetch-dest": "empty",
|
235 |
"sec-fetch-mode": "cors",
|
236 |
"sec-fetch-site": "same-origin",
|
237 |
+
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
|
238 |
}
|
239 |
|
240 |
if model in cls.model_prefixes:
|
|
|
242 |
if not messages[0]['content'].startswith(prefix):
|
243 |
logger.debug(f"Adding prefix '{prefix}' to the first message.")
|
244 |
messages[0]['content'] = f"{prefix} {messages[0]['content']}"
|
245 |
+
|
246 |
random_id = ''.join(random.choices(string.ascii_letters + string.digits, k=7))
|
247 |
messages[-1]['id'] = random_id
|
248 |
messages[-1]['role'] = 'user'
|
|
|
258 |
}
|
259 |
messages[-1]['content'] = 'FILE:BB\n$#$\n\n$#$\n' + messages[-1]['content']
|
260 |
logger.debug("Image data added to the message.")
|
261 |
+
|
262 |
data = {
|
263 |
"messages": messages,
|
264 |
"id": random_id,
|
|
|
291 |
data["userSelectedModel"] = cls.userSelectedModel[model]
|
292 |
logger.info(f"Sending request to {cls.api_endpoint} with data (excluding messages).")
|
293 |
|
294 |
+
timeout = ClientTimeout(total=60) # Set an appropriate timeout
|
295 |
+
retry_attempts = 10 # Set the number of retry attempts
|
296 |
|
297 |
for attempt in range(retry_attempts):
|
298 |
try:
|
|
|
358 |
# FastAPI app setup
|
359 |
app = FastAPI()
|
360 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
361 |
class Message(BaseModel):
|
362 |
role: str
|
363 |
content: str
|
364 |
|
365 |
+
class ChatRequest(BaseModel):
|
366 |
model: str
|
367 |
messages: List[Message]
|
368 |
temperature: Optional[float] = 1.0
|
369 |
top_p: Optional[float] = 1.0
|
370 |
n: Optional[int] = 1
|
371 |
stream: Optional[bool] = False
|
372 |
+
stop: Optional[Union[str, List[str]]] = None
|
373 |
max_tokens: Optional[int] = None
|
374 |
presence_penalty: Optional[float] = 0.0
|
375 |
frequency_penalty: Optional[float] = 0.0
|
376 |
logit_bias: Optional[Dict[str, float]] = None
|
377 |
user: Optional[str] = None
|
378 |
+
webSearchMode: Optional[bool] = False # Custom parameter
|
379 |
|
380 |
+
def create_response(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
|
381 |
return {
|
382 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
383 |
+
"object": "chat.completion.chunk",
|
384 |
"created": int(datetime.now().timestamp()),
|
385 |
"model": model,
|
386 |
"choices": [
|
387 |
{
|
388 |
"index": 0,
|
389 |
+
"delta": {"content": content, "role": "assistant"},
|
390 |
+
"finish_reason": finish_reason,
|
|
|
|
|
|
|
391 |
}
|
392 |
],
|
393 |
+
"usage": None,
|
394 |
}
|
395 |
|
396 |
+
@app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter)])
|
397 |
+
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
398 |
+
# Redact user messages only for logging purposes
|
399 |
+
redacted_messages = [{"role": msg.role, "content": "[redacted]"} for msg in request.messages]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
|
401 |
+
logger.info(f"Received chat completions request from API key: {api_key} | Model: {request.model} | Messages: {redacted_messages}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
403 |
try:
|
404 |
+
# Validate that the requested model is available
|
405 |
+
if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
|
406 |
+
logger.warning(f"Attempt to use unavailable model: {request.model}")
|
407 |
+
raise HTTPException(status_code=400, detail="Requested model is not available.")
|
408 |
+
|
409 |
+
# Process the request with actual message content, but don't log it
|
410 |
+
async_generator = Blackbox.create_async_generator(
|
411 |
+
model=request.model,
|
412 |
+
messages=[{"role": msg.role, "content": msg.content} for msg in request.messages], # Actual message content used here
|
413 |
+
image=None,
|
414 |
+
image_name=None,
|
415 |
+
webSearchMode=request.webSearchMode
|
416 |
)
|
417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
418 |
if request.stream:
|
419 |
async def generate():
|
|
|
420 |
try:
|
|
|
|
|
|
|
|
|
421 |
async for chunk in async_generator:
|
422 |
+
if isinstance(chunk, ImageResponse):
|
423 |
+
image_markdown = f""
|
424 |
+
response_chunk = create_response(image_markdown, request.model)
|
|
|
|
|
|
|
|
|
|
|
|
|
425 |
else:
|
426 |
+
response_chunk = create_response(chunk, request.model)
|
427 |
+
|
428 |
+
yield f"data: {json.dumps(response_chunk)}\n\n"
|
429 |
+
|
430 |
yield "data: [DONE]\n\n"
|
431 |
+
except HTTPException as he:
|
432 |
+
error_response = {"error": he.detail}
|
433 |
+
yield f"data: {json.dumps(error_response)}\n\n"
|
434 |
except Exception as e:
|
435 |
logger.exception("Error during streaming response generation.")
|
436 |
+
error_response = {"error": str(e)}
|
437 |
+
yield f"data: {json.dumps(error_response)}\n\n"
|
438 |
+
|
439 |
return StreamingResponse(generate(), media_type="text/event-stream")
|
440 |
else:
|
441 |
response_content = ""
|
442 |
async for chunk in async_generator:
|
443 |
+
if isinstance(chunk, ImageResponse):
|
444 |
+
response_content += f"\n"
|
445 |
+
else:
|
446 |
response_content += chunk
|
447 |
+
|
448 |
+
logger.info(f"Completed non-streaming response generation for API key: {api_key}")
|
449 |
+
return {
|
450 |
+
"id": f"chatcmpl-{uuid.uuid4()}",
|
451 |
+
"object": "chat.completion",
|
452 |
+
"created": int(datetime.now().timestamp()),
|
453 |
+
"model": request.model,
|
454 |
+
"choices": [
|
455 |
+
{
|
456 |
+
"message": {
|
457 |
+
"role": "assistant",
|
458 |
+
"content": response_content
|
459 |
+
},
|
460 |
+
"finish_reason": "stop",
|
461 |
+
"index": 0
|
462 |
+
}
|
463 |
+
],
|
464 |
+
"usage": {
|
465 |
+
"prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
|
466 |
+
"completion_tokens": len(response_content.split()),
|
467 |
+
"total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.split())
|
468 |
+
},
|
469 |
}
|
|
|
470 |
except ModelNotWorkingException as e:
|
471 |
logger.warning(f"Model not working: {e}")
|
472 |
raise HTTPException(status_code=503, detail=str(e))
|
473 |
+
except HTTPException as he:
|
474 |
+
logger.warning(f"HTTPException: {he.detail}")
|
475 |
+
raise he
|
476 |
except Exception as e:
|
477 |
logger.exception("An unexpected error occurred while processing the chat completions request.")
|
478 |
raise HTTPException(status_code=500, detail=str(e))
|
479 |
|
480 |
+
@app.get("/v1/models", dependencies=[Depends(rate_limiter)])
|
481 |
+
async def get_models(api_key: str = Depends(get_api_key)):
|
482 |
+
logger.info(f"Fetching available models for API key: {api_key}")
|
483 |
+
return {"data": [{"id": model, "object": "model"} for model in Blackbox.models]}
|
484 |
+
|
485 |
+
# Additional endpoints for better functionality
|
486 |
+
@app.get("/v1/health", dependencies=[Depends(rate_limiter)])
|
487 |
+
async def health_check(api_key: str = Depends(get_api_key)):
|
488 |
+
logger.info(f"Health check requested by API key: {api_key}")
|
489 |
+
return {"status": "ok"}
|
490 |
+
|
491 |
+
@app.get("/v1/models/{model}/status", dependencies=[Depends(rate_limiter)])
|
492 |
+
async def model_status(model: str, api_key: str = Depends(get_api_key)):
|
493 |
+
logger.info(f"Model status requested for '{model}' by API key: {api_key}")
|
494 |
+
if model in Blackbox.models:
|
495 |
+
return {"model": model, "status": "available"}
|
496 |
+
elif model in Blackbox.model_aliases:
|
497 |
+
actual_model = Blackbox.model_aliases[model]
|
498 |
+
return {"model": actual_model, "status": "available via alias"}
|
499 |
+
else:
|
500 |
+
logger.warning(f"Model not found: {model}")
|
501 |
+
raise HTTPException(status_code=404, detail="Model not found")
|
502 |
+
|
503 |
+
# Custom exception handler to match OpenAI's error format
|
504 |
+
@app.exception_handler(HTTPException)
|
505 |
+
async def http_exception_handler(request: Request, exc: HTTPException):
|
506 |
+
return JSONResponse(
|
507 |
+
status_code=exc.status_code,
|
508 |
+
content={
|
509 |
+
"error": {
|
510 |
+
"message": exc.detail,
|
511 |
+
"type": "invalid_request_error",
|
512 |
+
"param": None,
|
513 |
+
"code": None
|
514 |
+
}
|
515 |
+
},
|
516 |
+
)
|
517 |
|
518 |
if __name__ == "__main__":
|
519 |
import uvicorn
|