Update main.py
Browse files
main.py
CHANGED
@@ -20,9 +20,7 @@ from pydantic import BaseModel
|
|
20 |
logging.basicConfig(
|
21 |
level=logging.INFO,
|
22 |
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
23 |
-
handlers=[
|
24 |
-
logging.StreamHandler()
|
25 |
-
]
|
26 |
)
|
27 |
logger = logging.getLogger(__name__)
|
28 |
|
@@ -38,10 +36,6 @@ if not API_KEYS or API_KEYS == ['']:
|
|
38 |
rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
|
39 |
|
40 |
async def get_api_key(authorization: str = Header(...)) -> str:
|
41 |
-
"""
|
42 |
-
Dependency to extract and validate the API key from the Authorization header.
|
43 |
-
Expects the header in the format: Authorization: Bearer <API_KEY>
|
44 |
-
"""
|
45 |
if not authorization.startswith('Bearer '):
|
46 |
logger.warning("Invalid authorization header format.")
|
47 |
raise HTTPException(status_code=401, detail='Invalid authorization header format')
|
@@ -52,14 +46,9 @@ async def get_api_key(authorization: str = Header(...)) -> str:
|
|
52 |
return api_key
|
53 |
|
54 |
async def rate_limiter(api_key: str = Depends(get_api_key)):
|
55 |
-
"""
|
56 |
-
Dependency to enforce rate limiting per API key.
|
57 |
-
Raises HTTP 429 if the rate limit is exceeded.
|
58 |
-
"""
|
59 |
current_time = time.time()
|
60 |
window_start = rate_limit_store[api_key]["timestamp"]
|
61 |
if current_time - window_start > 60:
|
62 |
-
# Reset the count and timestamp after the time window
|
63 |
rate_limit_store[api_key] = {"count": 1, "timestamp": current_time}
|
64 |
else:
|
65 |
if rate_limit_store[api_key]["count"] >= RATE_LIMIT:
|
@@ -244,6 +233,10 @@ class Blackbox:
|
|
244 |
random_id = ''.join(random.choices(string.ascii_letters + string.digits, k=7))
|
245 |
messages[-1]['id'] = random_id
|
246 |
messages[-1]['role'] = 'user'
|
|
|
|
|
|
|
|
|
247 |
if image is not None:
|
248 |
messages[-1]['data'] = {
|
249 |
'fileText': '',
|
@@ -283,7 +276,7 @@ class Blackbox:
|
|
283 |
data["trendingAgentMode"] = cls.trendingAgentMode[model]
|
284 |
elif model in cls.userSelectedModel:
|
285 |
data["userSelectedModel"] = cls.userSelectedModel[model]
|
286 |
-
logger.info(f"Sending request to {cls.api_endpoint} with data
|
287 |
|
288 |
timeout = ClientTimeout(total=60) # Set an appropriate timeout
|
289 |
retry_attempts = 10 # Set the number of retry attempts
|
@@ -299,7 +292,7 @@ class Blackbox:
|
|
299 |
url_match = re.search(r'https://storage\.googleapis\.com/[^\s\)]+', response_text)
|
300 |
if url_match:
|
301 |
image_url = url_match.group(0)
|
302 |
-
logger.info(f"Image URL found
|
303 |
yield ImageResponse(image_url, alt=messages[-1]['content'])
|
304 |
else:
|
305 |
logger.error("Image URL not found in the response.")
|
@@ -366,7 +359,7 @@ def create_response(content: str, model: str, finish_reason: Optional[str] = Non
|
|
366 |
return {
|
367 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
368 |
"object": "chat.completion.chunk",
|
369 |
-
"created": int(datetime.now().timestamp()),
|
370 |
"model": model,
|
371 |
"choices": [
|
372 |
{
|
@@ -380,22 +373,18 @@ def create_response(content: str, model: str, finish_reason: Optional[str] = Non
|
|
380 |
|
381 |
@app.post("/niansuhai/v1/chat/completions", dependencies=[Depends(rate_limiter)])
|
382 |
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
383 |
-
""
|
384 |
-
|
385 |
-
Protected by API key and rate limiter.
|
386 |
-
"""
|
387 |
-
logger.info(f"Received chat completions request from API key: {api_key} | Request: {request}")
|
388 |
try:
|
389 |
# Validate that the requested model is available
|
390 |
if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
|
391 |
logger.warning(f"Attempt to use unavailable model: {request.model}")
|
392 |
raise HTTPException(status_code=400, detail="Requested model is not available.")
|
393 |
|
394 |
-
|
395 |
-
|
396 |
async_generator = Blackbox.create_async_generator(
|
397 |
model=request.model,
|
398 |
-
messages=messages,
|
399 |
image=None,
|
400 |
image_name=None,
|
401 |
webSearchMode=request.webSearchMode
|
@@ -411,10 +400,8 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
411 |
else:
|
412 |
response_chunk = create_response(chunk, request.model)
|
413 |
|
414 |
-
# Yield each chunk in SSE format
|
415 |
yield f"data: {json.dumps(response_chunk)}\n\n"
|
416 |
|
417 |
-
# Signal the end of the stream
|
418 |
yield "data: [DONE]\n\n"
|
419 |
except HTTPException as he:
|
420 |
error_response = {"error": he.detail}
|
@@ -437,7 +424,7 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
437 |
return {
|
438 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
439 |
"object": "chat.completion",
|
440 |
-
"created": int(datetime.now().timestamp()),
|
441 |
"model": request.model,
|
442 |
"choices": [
|
443 |
{
|
@@ -450,9 +437,9 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
450 |
}
|
451 |
],
|
452 |
"usage": {
|
453 |
-
"prompt_tokens": sum(len(msg['content'].split()) for msg in messages),
|
454 |
"completion_tokens": len(response_content.split()),
|
455 |
-
"total_tokens": sum(len(msg['content'].split()) for msg in messages) + len(response_content.split())
|
456 |
},
|
457 |
}
|
458 |
except ModelNotWorkingException as e:
|
@@ -467,23 +454,17 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
467 |
|
468 |
@app.get("/niansuhai/v1/models", dependencies=[Depends(rate_limiter)])
|
469 |
async def get_models(api_key: str = Depends(get_api_key)):
|
470 |
-
"""
|
471 |
-
Endpoint to fetch available models.
|
472 |
-
Protected by API key and rate limiter.
|
473 |
-
"""
|
474 |
logger.info(f"Fetching available models for API key: {api_key}")
|
475 |
return {"data": [{"id": model} for model in Blackbox.models]}
|
476 |
|
477 |
# Additional endpoints for better functionality
|
478 |
@app.get("/niansuhai/v1/health", dependencies=[Depends(rate_limiter)])
|
479 |
async def health_check(api_key: str = Depends(get_api_key)):
|
480 |
-
"""Health check endpoint to verify the service is running."""
|
481 |
logger.info(f"Health check requested by API key: {api_key}")
|
482 |
return {"status": "ok"}
|
483 |
|
484 |
@app.get("/niansuhai/v1/models/{model}/status", dependencies=[Depends(rate_limiter)])
|
485 |
async def model_status(model: str, api_key: str = Depends(get_api_key)):
|
486 |
-
"""Check if a specific model is available."""
|
487 |
logger.info(f"Model status requested for '{model}' by API key: {api_key}")
|
488 |
if model in Blackbox.models:
|
489 |
return {"model": model, "status": "available"}
|
|
|
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 |
|
|
|
36 |
rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
|
37 |
|
38 |
async def get_api_key(authorization: str = Header(...)) -> str:
|
|
|
|
|
|
|
|
|
39 |
if not authorization.startswith('Bearer '):
|
40 |
logger.warning("Invalid authorization header format.")
|
41 |
raise HTTPException(status_code=401, detail='Invalid authorization header format')
|
|
|
46 |
return api_key
|
47 |
|
48 |
async def rate_limiter(api_key: str = Depends(get_api_key)):
|
|
|
|
|
|
|
|
|
49 |
current_time = time.time()
|
50 |
window_start = rate_limit_store[api_key]["timestamp"]
|
51 |
if current_time - window_start > 60:
|
|
|
52 |
rate_limit_store[api_key] = {"count": 1, "timestamp": current_time}
|
53 |
else:
|
54 |
if rate_limit_store[api_key]["count"] >= RATE_LIMIT:
|
|
|
233 |
random_id = ''.join(random.choices(string.ascii_letters + string.digits, k=7))
|
234 |
messages[-1]['id'] = random_id
|
235 |
messages[-1]['role'] = 'user'
|
236 |
+
|
237 |
+
# Don't log the full message content for privacy
|
238 |
+
logger.debug(f"Generated message ID: {random_id} for model: {model}")
|
239 |
+
|
240 |
if image is not None:
|
241 |
messages[-1]['data'] = {
|
242 |
'fileText': '',
|
|
|
276 |
data["trendingAgentMode"] = cls.trendingAgentMode[model]
|
277 |
elif model in cls.userSelectedModel:
|
278 |
data["userSelectedModel"] = cls.userSelectedModel[model]
|
279 |
+
logger.info(f"Sending request to {cls.api_endpoint} with data (excluding messages).")
|
280 |
|
281 |
timeout = ClientTimeout(total=60) # Set an appropriate timeout
|
282 |
retry_attempts = 10 # Set the number of retry attempts
|
|
|
292 |
url_match = re.search(r'https://storage\.googleapis\.com/[^\s\)]+', response_text)
|
293 |
if url_match:
|
294 |
image_url = url_match.group(0)
|
295 |
+
logger.info(f"Image URL found.")
|
296 |
yield ImageResponse(image_url, alt=messages[-1]['content'])
|
297 |
else:
|
298 |
logger.error("Image URL not found in the response.")
|
|
|
359 |
return {
|
360 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
361 |
"object": "chat.completion.chunk",
|
362 |
+
"created": int(datetime.now().timestamp()),
|
363 |
"model": model,
|
364 |
"choices": [
|
365 |
{
|
|
|
373 |
|
374 |
@app.post("/niansuhai/v1/chat/completions", dependencies=[Depends(rate_limiter)])
|
375 |
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
376 |
+
logger.info(f"Received chat completions request from API key: {api_key} | Model: {request.model}")
|
377 |
+
|
|
|
|
|
|
|
378 |
try:
|
379 |
# Validate that the requested model is available
|
380 |
if request.model not in Blackbox.models and request.model not in Blackbox.model_aliases:
|
381 |
logger.warning(f"Attempt to use unavailable model: {request.model}")
|
382 |
raise HTTPException(status_code=400, detail="Requested model is not available.")
|
383 |
|
384 |
+
# Process the request but do not log sensitive content
|
|
|
385 |
async_generator = Blackbox.create_async_generator(
|
386 |
model=request.model,
|
387 |
+
messages=[{"role": msg.role, "content": "[redacted]"} for msg in request.messages], # Redact user messages in logs
|
388 |
image=None,
|
389 |
image_name=None,
|
390 |
webSearchMode=request.webSearchMode
|
|
|
400 |
else:
|
401 |
response_chunk = create_response(chunk, request.model)
|
402 |
|
|
|
403 |
yield f"data: {json.dumps(response_chunk)}\n\n"
|
404 |
|
|
|
405 |
yield "data: [DONE]\n\n"
|
406 |
except HTTPException as he:
|
407 |
error_response = {"error": he.detail}
|
|
|
424 |
return {
|
425 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
426 |
"object": "chat.completion",
|
427 |
+
"created": int(datetime.now().timestamp()),
|
428 |
"model": request.model,
|
429 |
"choices": [
|
430 |
{
|
|
|
437 |
}
|
438 |
],
|
439 |
"usage": {
|
440 |
+
"prompt_tokens": sum(len(msg['content'].split()) for msg in request.messages),
|
441 |
"completion_tokens": len(response_content.split()),
|
442 |
+
"total_tokens": sum(len(msg['content'].split()) for msg in request.messages) + len(response_content.split())
|
443 |
},
|
444 |
}
|
445 |
except ModelNotWorkingException as e:
|
|
|
454 |
|
455 |
@app.get("/niansuhai/v1/models", dependencies=[Depends(rate_limiter)])
|
456 |
async def get_models(api_key: str = Depends(get_api_key)):
|
|
|
|
|
|
|
|
|
457 |
logger.info(f"Fetching available models for API key: {api_key}")
|
458 |
return {"data": [{"id": model} for model in Blackbox.models]}
|
459 |
|
460 |
# Additional endpoints for better functionality
|
461 |
@app.get("/niansuhai/v1/health", dependencies=[Depends(rate_limiter)])
|
462 |
async def health_check(api_key: str = Depends(get_api_key)):
|
|
|
463 |
logger.info(f"Health check requested by API key: {api_key}")
|
464 |
return {"status": "ok"}
|
465 |
|
466 |
@app.get("/niansuhai/v1/models/{model}/status", dependencies=[Depends(rate_limiter)])
|
467 |
async def model_status(model: str, api_key: str = Depends(get_api_key)):
|
|
|
468 |
logger.info(f"Model status requested for '{model}' by API key: {api_key}")
|
469 |
if model in Blackbox.models:
|
470 |
return {"model": model, "status": "available"}
|