Niansuh commited on
Commit
9ef03f2
·
verified ·
1 Parent(s): ba9aaa0

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +63 -182
main.py CHANGED
@@ -9,11 +9,10 @@ import asyncio
9
  import time
10
  from collections import defaultdict
11
  from typing import List, Dict, Any, Optional, Union, AsyncGenerator
12
- from datetime import datetime
13
 
14
  from aiohttp import ClientSession, ClientResponseError
15
  from fastapi import FastAPI, HTTPException, Request, Depends, Header
16
- from fastapi.responses import JSONResponse, StreamingResponse
17
  from pydantic import BaseModel
18
 
19
  # Configure logging
@@ -39,13 +38,6 @@ rate_limit_store = defaultdict(lambda: {"count": 0, "timestamp": time.time()})
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,7 +183,7 @@ class Blackbox:
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
 
@@ -203,19 +195,9 @@ class Blackbox:
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
 
@@ -234,7 +216,7 @@ class Blackbox:
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
 
@@ -295,18 +277,6 @@ class Blackbox:
295
  "userSelectedModel": cls.userSelectedModel.get(model, model)
296
  }
297
 
298
- headers_chat = {
299
- 'Accept': 'text/x-component',
300
- 'Content-Type': 'text/plain;charset=UTF-8',
301
- 'Referer': f'{cls.url}/chat/{chat_id}?model={model}',
302
- 'next-action': next_action,
303
- 'next-router-state-tree': next_router_state_tree,
304
- 'next-url': '/'
305
- }
306
- headers_chat_combined = {**common_headers, **headers_chat}
307
-
308
- data_chat = '[]'
309
-
310
  async with ClientSession(headers=common_headers) as session:
311
  try:
312
  async with session.post(
@@ -316,14 +286,42 @@ class Blackbox:
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:
@@ -336,79 +334,6 @@ class Blackbox:
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:
343
- async with session.post(
344
- chat_url,
345
- headers=headers_chat_combined,
346
- data=data_chat,
347
- proxy=proxy
348
- ) as response_chat:
349
- response_chat.raise_for_status()
350
- pass
351
- except ClientResponseError as e:
352
- error_text = f"Error {e.status}: {e.message}"
353
- try:
354
- error_response = await e.response.text()
355
- cleaned_error = cls.clean_response(error_response)
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):
365
- def __init__(self, model: str):
366
- self.model = model
367
- self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
368
- super().__init__(self.message)
369
-
370
- async def cleanup_rate_limit_stores():
371
- """
372
- Periodically cleans up stale entries in the rate_limit_store to prevent memory bloat.
373
- """
374
- while True:
375
- current_time = time.time()
376
- ips_to_delete = [ip for ip, value in rate_limit_store.items() if current_time - value["timestamp"] > RATE_LIMIT_WINDOW * 2]
377
- for ip in ips_to_delete:
378
- del rate_limit_store[ip]
379
- logger.debug(f"Cleaned up rate_limit_store for IP: {ip}")
380
- await asyncio.sleep(CLEANUP_INTERVAL)
381
-
382
- async def rate_limiter_per_ip(request: Request):
383
- """
384
- Rate limiter that enforces a limit based on the client's IP address.
385
- """
386
- client_ip = request.client.host
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:
394
- logger.warning(f"Rate limit exceeded for IP address: {client_ip}")
395
- raise HTTPException(status_code=429, detail='Rate limit exceeded for IP address | NiansuhAI')
396
- rate_limit_store[client_ip]["count"] += 1
397
-
398
- async def get_api_key(request: Request, authorization: str = Header(None)) -> str:
399
- """
400
- Dependency to extract and validate the API key from the Authorization header.
401
- """
402
- client_ip = request.client.host
403
- if authorization is None or not authorization.startswith('Bearer '):
404
- logger.warning(f"Invalid or missing authorization header from IP: {client_ip}")
405
- raise HTTPException(status_code=401, detail='Invalid authorization header format')
406
- api_key = authorization[7:]
407
- if api_key not in API_KEYS:
408
- logger.warning(f"Invalid API key attempted: {api_key} from IP: {client_ip}")
409
- raise HTTPException(status_code=401, detail='Invalid API key')
410
- return api_key
411
-
412
  # FastAPI app setup
413
  app = FastAPI()
414
 
@@ -449,7 +374,6 @@ class Message(BaseModel):
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
@@ -459,17 +383,6 @@ class ChatRequest(BaseModel):
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)):
475
  client_ip = req.client.host
@@ -485,63 +398,35 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
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()),
528
- "model": request.model,
529
- "choices": [
530
- {
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))
@@ -582,7 +467,3 @@ async def http_exception_handler(request: Request, exc: HTTPException):
582
  }
583
  },
584
  )
585
-
586
- if __name__ == "__main__":
587
- import uvicorn
588
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
9
  import time
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
 
38
  CLEANUP_INTERVAL = 60 # seconds
39
  RATE_LIMIT_WINDOW = 60 # seconds
40
 
 
 
 
 
 
 
 
41
  class Blackbox:
42
  label = "Blackbox AI"
43
  url = "https://www.blackbox.ai"
 
183
 
184
  @staticmethod
185
  def clean_response(text: str) -> str:
186
+ pattern = r'^\$\@\$v=undefined-rv1\$\@\$'
187
  cleaned_text = re.sub(pattern, '', text)
188
  return cleaned_text
189
 
 
195
  proxy: Optional[str] = None,
196
  websearch: bool = False,
197
  **kwargs
198
+ ) -> AsyncGenerator[Union[str, Dict[str, Any]], None]:
199
  """
200
  Creates an asynchronous generator for streaming responses from Blackbox AI.
 
 
 
 
 
 
 
 
 
 
201
  """
202
  model = cls.get_model(model)
203
 
 
216
  content = message.get('content', '')
217
  if role and content:
218
  formatted_prompt += f"{role}: {content}\n"
219
+
220
  if prefix:
221
  formatted_prompt = f"{prefix} {formatted_prompt}".strip()
222
 
 
277
  "userSelectedModel": cls.userSelectedModel.get(model, model)
278
  }
279
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  async with ClientSession(headers=common_headers) as session:
281
  try:
282
  async with session.post(
 
286
  proxy=proxy
287
  ) as response_api_chat:
288
  response_api_chat.raise_for_status()
289
+ text = await response_api_chat.text()
290
+ cleaned_response = cls.clean_response(text)
291
+
292
+ if model in cls.image_models:
293
+ match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_response)
294
+ if match:
295
+ image_url = match.group(1)
296
+ yield {"type": "image", "url": image_url, "alt": "Generated Image"}
297
+ else:
298
+ yield cleaned_response
299
+ else:
300
+ if websearch:
301
+ match = re.search(r'\$~~~\$(.*?)\$~~~\$', cleaned_response, re.DOTALL)
302
+ if match:
303
+ source_part = match.group(1).strip()
304
+ answer_part = cleaned_response[match.end():].strip()
305
+ try:
306
+ sources = json.loads(source_part)
307
+ source_formatted = "**Source:**\n"
308
+ for item in sources:
309
+ title = item.get('title', 'No Title')
310
+ link = item.get('link', '#')
311
+ position = item.get('position', '')
312
+ source_formatted += f"{position}. [{title}]({link})\n"
313
+ final_response = f"{answer_part}\n\n{source_formatted}"
314
+ except json.JSONDecodeError:
315
+ final_response = f"{answer_part}\n\nSource information is unavailable."
316
+ else:
317
+ final_response = cleaned_response
318
+ else:
319
+ if '$~~~$' in cleaned_response:
320
+ final_response = cleaned_response.split('$~~~$')[0].strip()
321
+ else:
322
+ final_response = cleaned_response
323
+
324
+ yield final_response
325
  except ClientResponseError as e:
326
  error_text = f"Error {e.status}: {e.message}"
327
  try:
 
334
  except Exception as e:
335
  yield f"Unexpected error during /api/chat request: {str(e)}"
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  # FastAPI app setup
338
  app = FastAPI()
339
 
 
374
  class ChatRequest(BaseModel):
375
  model: str
376
  messages: List[Message]
 
377
  temperature: Optional[float] = 1.0
378
  top_p: Optional[float] = 1.0
379
  n: Optional[int] = 1
 
383
  logit_bias: Optional[Dict[str, float]] = None
384
  user: Optional[str] = None
385
 
 
 
 
 
 
 
 
 
 
 
 
386
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
387
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
388
  client_ip = req.client.host
 
398
  raise HTTPException(status_code=400, detail="Requested model is not available.")
399
 
400
  # Process the request with actual message content, but don't log it
401
+ response_content = await Blackbox.create_async_generator(
402
  model=request.model,
403
  messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
404
  temperature=request.temperature,
405
  max_tokens=request.max_tokens
406
  )
407
 
408
+ logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
409
+ return {
410
+ "id": f"chatcmpl-{uuid.uuid4()}",
411
+ "object": "chat.completion",
412
+ "created": int(datetime.now().timestamp()),
413
+ "model": request.model,
414
+ "choices": [
415
+ {
416
+ "index": 0,
417
+ "message": {
418
+ "role": "assistant",
419
+ "content": response_content
420
+ },
421
+ "finish_reason": "stop"
422
+ }
423
+ ],
424
+ "usage": {
425
+ "prompt_tokens": sum(len(msg.content.split()) for msg in request.messages),
426
+ "completion_tokens": len(response_content.split()),
427
+ "total_tokens": sum(len(msg.content.split()) for msg in request.messages) + len(response_content.split())
428
+ },
429
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  except ModelNotWorkingException as e:
431
  logger.warning(f"Model not working: {e} | IP: {client_ip}")
432
  raise HTTPException(status_code=503, detail=str(e))
 
467
  }
468
  },
469
  )