Update main.py
Browse files
main.py
CHANGED
@@ -39,6 +39,13 @@ rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
|
|
39 |
CLEANUP_INTERVAL = 60 # seconds
|
40 |
RATE_LIMIT_WINDOW = 60 # seconds
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
class Blackbox:
|
43 |
label = "Blackbox AI"
|
44 |
url = "https://www.blackbox.ai"
|
@@ -184,20 +191,34 @@ class Blackbox:
|
|
184 |
|
185 |
@staticmethod
|
186 |
def clean_response(text: str) -> str:
|
187 |
-
pattern = r'^\$\@\$v=undefined-rv1
|
188 |
cleaned_text = re.sub(pattern, '', text)
|
189 |
return cleaned_text
|
190 |
|
191 |
@classmethod
|
192 |
-
async def
|
193 |
cls,
|
194 |
model: str,
|
195 |
messages: List[Dict[str, str]],
|
196 |
proxy: Optional[str] = None,
|
197 |
websearch: bool = False,
|
198 |
**kwargs
|
199 |
-
) -> AsyncGenerator[Union[str,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
model = cls.get_model(model)
|
|
|
201 |
chat_id = cls.generate_random_string()
|
202 |
next_action = cls.generate_next_action()
|
203 |
next_router_state_tree = cls.generate_next_router_state_tree()
|
@@ -213,7 +234,7 @@ class Blackbox:
|
|
213 |
content = message.get('content', '')
|
214 |
if role and content:
|
215 |
formatted_prompt += f"{role}: {content}\n"
|
216 |
-
|
217 |
if prefix:
|
218 |
formatted_prompt = f"{prefix} {formatted_prompt}".strip()
|
219 |
|
@@ -295,42 +316,14 @@ class Blackbox:
|
|
295 |
proxy=proxy
|
296 |
) as response_api_chat:
|
297 |
response_api_chat.raise_for_status()
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
else:
|
307 |
-
yield {"type": "text", "content": cleaned_response}
|
308 |
-
else:
|
309 |
-
if websearch:
|
310 |
-
match = re.search(r'\$~~~\$(.*?)\$~~~\$', cleaned_response, re.DOTALL)
|
311 |
-
if match:
|
312 |
-
source_part = match.group(1).strip()
|
313 |
-
answer_part = cleaned_response[match.end():].strip()
|
314 |
-
try:
|
315 |
-
sources = json.loads(source_part)
|
316 |
-
source_formatted = "**Source:**\n"
|
317 |
-
for item in sources:
|
318 |
-
title = item.get('title', 'No Title')
|
319 |
-
link = item.get('link', '#')
|
320 |
-
position = item.get('position', '')
|
321 |
-
source_formatted += f"{position}. [{title}]({link})\n"
|
322 |
-
final_response = f"{answer_part}\n\n{source_formatted}"
|
323 |
-
except json.JSONDecodeError:
|
324 |
-
final_response = f"{answer_part}\n\nSource information is unavailable."
|
325 |
-
else:
|
326 |
-
final_response = cleaned_response
|
327 |
-
else:
|
328 |
-
if '$~~~$' in cleaned_response:
|
329 |
-
final_response = cleaned_response.split('$~~~$')[0].strip()
|
330 |
-
else:
|
331 |
-
final_response = cleaned_response
|
332 |
-
|
333 |
-
yield {"type": "text", "content": final_response}
|
334 |
except ClientResponseError as e:
|
335 |
error_text = f"Error {e.status}: {e.message}"
|
336 |
try:
|
@@ -339,10 +332,11 @@ class Blackbox:
|
|
339 |
error_text += f" - {cleaned_error}"
|
340 |
except Exception:
|
341 |
pass
|
342 |
-
yield
|
343 |
except Exception as e:
|
344 |
-
yield
|
345 |
|
|
|
346 |
chat_url = f'{cls.url}/chat/{chat_id}?model={model}'
|
347 |
|
348 |
try:
|
@@ -362,9 +356,9 @@ class Blackbox:
|
|
362 |
error_text += f" - {cleaned_error}"
|
363 |
except Exception:
|
364 |
pass
|
365 |
-
yield
|
366 |
except Exception as e:
|
367 |
-
yield
|
368 |
|
369 |
# Custom exception for model not working
|
370 |
class ModelNotWorkingException(Exception):
|
@@ -393,7 +387,7 @@ async def rate_limiter_per_ip(request: Request):
|
|
393 |
current_time = time.time()
|
394 |
|
395 |
# Initialize or update the count and timestamp
|
396 |
-
if current_time - rate_limit_store[client_ip]["
|
397 |
rate_limit_store[client_ip] = {"count": 1, "timestamp": current_time}
|
398 |
else:
|
399 |
if rate_limit_store[client_ip]["count"] >= RATE_LIMIT:
|
@@ -455,6 +449,7 @@ class Message(BaseModel):
|
|
455 |
class ChatRequest(BaseModel):
|
456 |
model: str
|
457 |
messages: List[Message]
|
|
|
458 |
temperature: Optional[float] = 1.0
|
459 |
top_p: Optional[float] = 1.0
|
460 |
n: Optional[int] = 1
|
@@ -463,8 +458,17 @@ class ChatRequest(BaseModel):
|
|
463 |
frequency_penalty: Optional[float] = 0.0
|
464 |
logit_bias: Optional[Dict[str, float]] = None
|
465 |
user: Optional[str] = None
|
466 |
-
|
467 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
468 |
|
469 |
@app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
|
470 |
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
@@ -481,40 +485,43 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
481 |
raise HTTPException(status_code=400, detail="Requested model is not available.")
|
482 |
|
483 |
# Process the request with actual message content, but don't log it
|
484 |
-
|
485 |
model=request.model,
|
486 |
messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
|
487 |
temperature=request.temperature,
|
488 |
-
max_tokens=request.max_tokens
|
489 |
-
websearch=request.websearch
|
490 |
)
|
491 |
|
492 |
if request.stream:
|
493 |
async def stream_response():
|
494 |
-
async for chunk in
|
495 |
-
if chunk
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
501 |
yield "data: [DONE]\n\n"
|
502 |
-
|
|
|
|
|
503 |
return StreamingResponse(stream_response(), media_type="text/event-stream")
|
504 |
else:
|
505 |
-
|
506 |
-
image_url = None
|
507 |
-
async for chunk in response_generator:
|
508 |
-
if chunk["type"] == "text":
|
509 |
-
full_response += chunk["content"]
|
510 |
-
elif chunk["type"] == "image":
|
511 |
-
image_url = chunk["url"]
|
512 |
-
elif chunk["type"] == "error":
|
513 |
-
raise HTTPException(status_code=500, detail=chunk["content"])
|
514 |
-
|
515 |
logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
|
516 |
-
|
517 |
-
response = {
|
518 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
519 |
"object": "chat.completion",
|
520 |
"created": int(datetime.now().timestamp()),
|
@@ -524,23 +531,17 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
524 |
"index": 0,
|
525 |
"message": {
|
526 |
"role": "assistant",
|
527 |
-
"content":
|
528 |
},
|
529 |
"finish_reason": "stop"
|
530 |
}
|
531 |
],
|
532 |
"usage": {
|
533 |
"prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
|
534 |
-
"completion_tokens": len(
|
535 |
-
"total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(
|
536 |
},
|
537 |
}
|
538 |
-
|
539 |
-
if image_url:
|
540 |
-
response["choices"][0]["message"]["image"] = image_url
|
541 |
-
|
542 |
-
return response
|
543 |
-
|
544 |
except ModelNotWorkingException as e:
|
545 |
logger.warning(f"Model not working: {e} | IP: {client_ip}")
|
546 |
raise HTTPException(status_code=503, detail=str(e))
|
@@ -584,4 +585,4 @@ async def http_exception_handler(request: Request, exc: HTTPException):
|
|
584 |
|
585 |
if __name__ == "__main__":
|
586 |
import uvicorn
|
587 |
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
39 |
CLEANUP_INTERVAL = 60 # seconds
|
40 |
RATE_LIMIT_WINDOW = 60 # seconds
|
41 |
|
42 |
+
class ImageResponse:
|
43 |
+
def __init__(self, images: Union[str, List[str]], alt: str = "Generated Image"):
|
44 |
+
if isinstance(images, str):
|
45 |
+
images = [images]
|
46 |
+
self.images = images
|
47 |
+
self.alt = alt
|
48 |
+
|
49 |
class Blackbox:
|
50 |
label = "Blackbox AI"
|
51 |
url = "https://www.blackbox.ai"
|
|
|
191 |
|
192 |
@staticmethod
|
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
|
199 |
+
async def create_async_generator(
|
200 |
cls,
|
201 |
model: str,
|
202 |
messages: List[Dict[str, str]],
|
203 |
proxy: Optional[str] = None,
|
204 |
websearch: bool = False,
|
205 |
**kwargs
|
206 |
+
) -> AsyncGenerator[Union[str, ImageResponse], None]:
|
207 |
+
"""
|
208 |
+
Creates an asynchronous generator for streaming responses from Blackbox AI.
|
209 |
+
|
210 |
+
Parameters:
|
211 |
+
model (str): Model to use for generating responses.
|
212 |
+
messages (List[Dict[str, str]]): Message history.
|
213 |
+
proxy (Optional[str]): Proxy URL, if needed.
|
214 |
+
websearch (bool): Enables or disables web search mode.
|
215 |
+
**kwargs: Additional keyword arguments.
|
216 |
+
|
217 |
+
Yields:
|
218 |
+
Union[str, ImageResponse]: Segments of the generated response or ImageResponse objects.
|
219 |
+
"""
|
220 |
model = cls.get_model(model)
|
221 |
+
|
222 |
chat_id = cls.generate_random_string()
|
223 |
next_action = cls.generate_next_action()
|
224 |
next_router_state_tree = cls.generate_next_router_state_tree()
|
|
|
234 |
content = message.get('content', '')
|
235 |
if role and content:
|
236 |
formatted_prompt += f"{role}: {content}\n"
|
237 |
+
|
238 |
if prefix:
|
239 |
formatted_prompt = f"{prefix} {formatted_prompt}".strip()
|
240 |
|
|
|
316 |
proxy=proxy
|
317 |
) as response_api_chat:
|
318 |
response_api_chat.raise_for_status()
|
319 |
+
# We update this part to stream the response incrementally
|
320 |
+
# Instead of waiting for the full response, we read the response as it arrives
|
321 |
+
async for chunk in response_api_chat.content.iter_chunked(1024):
|
322 |
+
if not chunk:
|
323 |
+
continue
|
324 |
+
text = chunk.decode('utf-8', errors='ignore')
|
325 |
+
cleaned_chunk = cls.clean_response(text)
|
326 |
+
yield cleaned_chunk
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
except ClientResponseError as e:
|
328 |
error_text = f"Error {e.status}: {e.message}"
|
329 |
try:
|
|
|
332 |
error_text += f" - {cleaned_error}"
|
333 |
except Exception:
|
334 |
pass
|
335 |
+
yield error_text
|
336 |
except Exception as e:
|
337 |
+
yield f"Unexpected error during /api/chat request: {str(e)}"
|
338 |
|
339 |
+
# Not clear what to do with this second request; keeping it for compatibility
|
340 |
chat_url = f'{cls.url}/chat/{chat_id}?model={model}'
|
341 |
|
342 |
try:
|
|
|
356 |
error_text += f" - {cleaned_error}"
|
357 |
except Exception:
|
358 |
pass
|
359 |
+
yield error_text
|
360 |
except Exception as e:
|
361 |
+
yield f"Unexpected error during /chat/{chat_id} request: {str(e)}"
|
362 |
|
363 |
# Custom exception for model not working
|
364 |
class ModelNotWorkingException(Exception):
|
|
|
387 |
current_time = time.time()
|
388 |
|
389 |
# Initialize or update the count and timestamp
|
390 |
+
if current_time - rate_limit_store[client_ip]["timest(由Dream 提供Free https://opus.gptuu.com)amp"] > RATE_LIMIT_WINDOW:
|
391 |
rate_limit_store[client_ip] = {"count": 1, "timestamp": current_time}
|
392 |
else:
|
393 |
if rate_limit_store[client_ip]["count"] >= RATE_LIMIT:
|
|
|
449 |
class ChatRequest(BaseModel):
|
450 |
model: str
|
451 |
messages: List[Message]
|
452 |
+
stream: Optional[bool] = False # Added for streaming support
|
453 |
temperature: Optional[float] = 1.0
|
454 |
top_p: Optional[float] = 1.0
|
455 |
n: Optional[int] = 1
|
|
|
458 |
frequency_penalty: Optional[float] = 0.0
|
459 |
logit_bias: Optional[Dict[str, float]] = None
|
460 |
user: Optional[str] = None
|
461 |
+
|
462 |
+
# Helper function to collect responses from async generator
|
463 |
+
async def collect_response_content(generator: AsyncGenerator[Union[str, ImageResponse], None]) -> str:
|
464 |
+
response_content = ''
|
465 |
+
async for chunk in generator:
|
466 |
+
if isinstance(chunk, str):
|
467 |
+
response_content += chunk
|
468 |
+
elif isinstance(chunk, ImageResponse):
|
469 |
+
# Handle image response if needed
|
470 |
+
response_content += f"[Image: {chunk.alt}] {', '.join(chunk.images)}\n"
|
471 |
+
return response_content
|
472 |
|
473 |
@app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
|
474 |
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
|
|
485 |
raise HTTPException(status_code=400, detail="Requested model is not available.")
|
486 |
|
487 |
# Process the request with actual message content, but don't log it
|
488 |
+
generator = Blackbox.create_async_generator(
|
489 |
model=request.model,
|
490 |
messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
|
491 |
temperature=request.temperature,
|
492 |
+
max_tokens=request.max_tokens
|
|
|
493 |
)
|
494 |
|
495 |
if request.stream:
|
496 |
async def stream_response():
|
497 |
+
async for chunk in generator:
|
498 |
+
if isinstance(chunk, str):
|
499 |
+
data = json.dumps({
|
500 |
+
"choices": [{
|
501 |
+
"delta": {"content": chunk},
|
502 |
+
"index": 0,
|
503 |
+
"finish_reason": None
|
504 |
+
}],
|
505 |
+
"model": request.model,
|
506 |
+
"id": f"chatcmpl-{uuid.uuid4()}",
|
507 |
+
"object": "chat.completion.chunk",
|
508 |
+
"created": int(datetime.now().timestamp()),
|
509 |
+
})
|
510 |
+
# Ensure that each chunk is sent immediately
|
511 |
+
yield f"data: {data}\n\n"
|
512 |
+
elif isinstance(chunk, ImageResponse):
|
513 |
+
# Handle image responses here if needed
|
514 |
+
pass # For now, we skip image handling in streaming
|
515 |
+
# Send the termination message
|
516 |
yield "data: [DONE]\n\n"
|
517 |
+
|
518 |
+
logger.info(f"Streaming response enabled for API key: {api_key} | IP: {client_ip}")
|
519 |
+
|
520 |
return StreamingResponse(stream_response(), media_type="text/event-stream")
|
521 |
else:
|
522 |
+
response_content = await collect_response_content(generator)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
523 |
logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
|
524 |
+
return {
|
|
|
525 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
526 |
"object": "chat.completion",
|
527 |
"created": int(datetime.now().timestamp()),
|
|
|
531 |
"index": 0,
|
532 |
"message": {
|
533 |
"role": "assistant",
|
534 |
+
"content": response_content
|
535 |
},
|
536 |
"finish_reason": "stop"
|
537 |
}
|
538 |
],
|
539 |
"usage": {
|
540 |
"prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
|
541 |
+
"completion_tokens": len(response_content.split()),
|
542 |
+
"total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.split())
|
543 |
},
|
544 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
545 |
except ModelNotWorkingException as e:
|
546 |
logger.warning(f"Model not working: {e} | IP: {client_ip}")
|
547 |
raise HTTPException(status_code=503, detail=str(e))
|
|
|
585 |
|
586 |
if __name__ == "__main__":
|
587 |
import uvicorn
|
588 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|