Niansuh commited on
Commit
90a29cf
·
verified ·
1 Parent(s): b2adf82

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +115 -141
main.py CHANGED
@@ -10,7 +10,7 @@ import logging
10
  import asyncio
11
  import time
12
  from collections import defaultdict
13
- from typing import List, Dict, Any, Optional, AsyncGenerator, Union
14
 
15
  from datetime import datetime
16
 
@@ -145,10 +145,10 @@ class ImageResponseCustom:
145
  self.url = url
146
  self.alt = alt
147
 
148
- # Blackbox AI Integration
149
  class Blackbox:
150
  url = "https://www.blackbox.ai"
151
- api_endpoint = "https://www.blackbox.ai/api/chat"
152
  working = True
153
  supports_stream = True
154
  supports_system_message = True
@@ -266,7 +266,7 @@ class Blackbox:
266
  async def create_async_generator(
267
  cls,
268
  model: str,
269
- messages: List[Dict[str, str]],
270
  proxy: Optional[str] = None,
271
  image: Optional[str] = None,
272
  image_name: Optional[str] = None,
@@ -283,7 +283,7 @@ class Blackbox:
283
  if not cls.working or model not in cls.models:
284
  logger.error(f"Model {model} is not working or not supported.")
285
  raise ModelNotWorkingException(model)
286
-
287
  headers = {
288
  "accept": "*/*",
289
  "accept-language": "en-US,en;q=0.9",
@@ -307,7 +307,7 @@ class Blackbox:
307
  if not messages[0]['content'].startswith(prefix):
308
  logger.debug(f"Adding prefix '{prefix}' to the first message.")
309
  messages[0]['content'] = f"{prefix} {messages[0]['content']}"
310
-
311
  random_id = ''.join(random.choices(string.ascii_letters + string.digits, k=7))
312
  messages[-1]['id'] = random_id
313
  messages[-1]['role'] = 'user'
@@ -323,7 +323,7 @@ class Blackbox:
323
  }
324
  messages[-1]['content'] = 'FILE:BB\n$#$\n\n$#$\n' + messages[-1]['content']
325
  logger.debug("Image data added to the message.")
326
-
327
  data = {
328
  "messages": messages,
329
  "id": random_id,
@@ -454,14 +454,36 @@ async def security_middleware(request: Request, call_next):
454
  return response
455
 
456
  # Request Models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  class Message(BaseModel):
458
  role: str
459
- content: str
460
 
461
- @validator('content')
462
- def content_must_be_string(cls, v):
463
- if not isinstance(v, str):
464
- raise ValueError('content must be a string')
465
  return v
466
 
467
  class ChatRequest(BaseModel):
@@ -478,7 +500,6 @@ class ChatRequest(BaseModel):
478
  logit_bias: Optional[Dict[str, float]] = None
479
  user: Optional[str] = None
480
  webSearchMode: Optional[bool] = False # Custom parameter
481
- image: Optional[str] = None # Base64-encoded image
482
 
483
  class TokenizerRequest(BaseModel):
484
  text: str
@@ -511,6 +532,41 @@ def create_response(content: str, model: str, finish_reason: Optional[str] = Non
511
  "usage": None, # To be filled in non-streaming responses
512
  }
513
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
515
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
516
  client_ip = req.client.host
@@ -525,134 +581,52 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
525
  logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
526
  raise HTTPException(status_code=400, detail="Requested model is not available.")
527
 
528
- # Process the image if provided
529
- image_data = None
530
- image_name = None
531
- if request.image:
532
- try:
533
- # Validate and process the base64 image
534
- image_data = to_data_uri(request.image)
535
- image_name = "uploaded_image"
536
- logger.info(f"Image data received and processed from IP: {client_ip}")
537
- except Exception as e:
538
- logger.error(f"Image processing failed: {e}")
539
- raise HTTPException(status_code=400, detail="Invalid image data provided.")
540
-
541
- # Process the request with actual message content, but don't log it
542
- async_generator = Blackbox.create_async_generator(
543
- model=request.model,
544
- messages=[{"role": msg.role, "content": msg.content} for msg in request.messages], # Actual message content used here
545
- proxy=None,
546
- image=image_data,
547
- image_name=image_name,
548
- webSearchMode=request.webSearchMode
549
- )
550
-
551
- if request.stream:
552
- async def generate():
553
- try:
554
- assistant_content = ""
555
- async for chunk in async_generator:
556
- if isinstance(chunk, ImageResponseCustom):
557
- # Handle image responses if necessary
558
- image_markdown = f"![image]({chunk.url})\n"
559
- assistant_content += image_markdown
560
- response_chunk = create_response(image_markdown, request.model, finish_reason=None)
561
- else:
562
- assistant_content += chunk
563
- # Yield the chunk as a partial choice
564
- response_chunk = {
565
- "id": f"chatcmpl-{uuid.uuid4()}",
566
- "object": "chat.completion.chunk",
567
- "created": int(datetime.now().timestamp()),
568
- "model": request.model,
569
- "choices": [
570
- {
571
- "index": 0,
572
- "delta": {"content": chunk, "role": "assistant"},
573
- "finish_reason": None,
574
- }
575
- ],
576
- "usage": None, # Usage can be updated if you track tokens in real-time
577
- }
578
- yield f"data: {json.dumps(response_chunk)}\n\n"
579
-
580
- # After all chunks are sent, send the final message with finish_reason
581
- prompt_tokens = sum(len(msg.content.split()) for msg in request.messages)
582
- completion_tokens = len(assistant_content.split())
583
- total_tokens = prompt_tokens + completion_tokens
584
- estimated_cost = calculate_estimated_cost(prompt_tokens, completion_tokens)
585
-
586
- final_response = {
587
- "id": f"chatcmpl-{uuid.uuid4()}",
588
- "object": "chat.completion",
589
- "created": int(datetime.now().timestamp()),
590
- "model": request.model,
591
- "choices": [
592
- {
593
- "message": {
594
- "role": "assistant",
595
- "content": assistant_content
596
- },
597
- "finish_reason": "stop",
598
- "index": 0
599
- }
600
- ],
601
- "usage": {
602
- "prompt_tokens": prompt_tokens,
603
- "completion_tokens": completion_tokens,
604
- "total_tokens": total_tokens,
605
- "estimated_cost": estimated_cost
606
- },
607
- }
608
- yield f"data: {json.dumps(final_response)}\n\n"
609
- yield "data: [DONE]\n\n"
610
- except HTTPException as he:
611
- error_response = {"error": he.detail}
612
- yield f"data: {json.dumps(error_response)}\n\n"
613
- except Exception as e:
614
- logger.exception(f"Error during streaming response generation from IP: {client_ip}.")
615
- error_response = {"error": str(e)}
616
- yield f"data: {json.dumps(error_response)}\n\n"
617
-
618
- return StreamingResponse(generate(), media_type="text/event-stream")
619
- else:
620
- response_content = ""
621
- async for chunk in async_generator:
622
- if isinstance(chunk, ImageResponseCustom):
623
- response_content += f"![image]({chunk.url})\n"
624
- else:
625
- response_content += chunk
626
-
627
- prompt_tokens = sum(len(msg.content.split()) for msg in request.messages)
628
- completion_tokens = len(response_content.split())
629
- total_tokens = prompt_tokens + completion_tokens
630
- estimated_cost = calculate_estimated_cost(prompt_tokens, completion_tokens)
631
-
632
- logger.info(f"Completed non-streaming response generation for API key: {api_key} | IP: {client_ip}")
633
-
634
- return {
635
- "id": f"chatcmpl-{uuid.uuid4()}",
636
- "object": "chat.completion",
637
- "created": int(datetime.now().timestamp()),
638
- "model": request.model,
639
- "choices": [
640
- {
641
- "message": {
642
- "role": "assistant",
643
- "content": response_content
644
- },
645
- "finish_reason": "stop",
646
- "index": 0
647
- }
648
- ],
649
- "usage": {
650
- "prompt_tokens": prompt_tokens,
651
- "completion_tokens": completion_tokens,
652
- "total_tokens": total_tokens,
653
- "estimated_cost": estimated_cost
654
- },
655
- }
656
  except ModelNotWorkingException as e:
657
  logger.warning(f"Model not working: {e} | IP: {client_ip}")
658
  raise HTTPException(status_code=503, detail=str(e))
 
10
  import asyncio
11
  import time
12
  from collections import defaultdict
13
+ from typing import List, Dict, Any, Optional, AsyncGenerator, Union, Tuple
14
 
15
  from datetime import datetime
16
 
 
145
  self.url = url
146
  self.alt = alt
147
 
148
+ # Blackbox AI Integration (Placeholder for actual implementation)
149
  class Blackbox:
150
  url = "https://www.blackbox.ai"
151
+ api_endpoint = "https://www.blackbox.ai/api/chat" # Placeholder endpoint
152
  working = True
153
  supports_stream = True
154
  supports_system_message = True
 
266
  async def create_async_generator(
267
  cls,
268
  model: str,
269
+ messages: List[Dict[str, Any]],
270
  proxy: Optional[str] = None,
271
  image: Optional[str] = None,
272
  image_name: Optional[str] = None,
 
283
  if not cls.working or model not in cls.models:
284
  logger.error(f"Model {model} is not working or not supported.")
285
  raise ModelNotWorkingException(model)
286
+
287
  headers = {
288
  "accept": "*/*",
289
  "accept-language": "en-US,en;q=0.9",
 
307
  if not messages[0]['content'].startswith(prefix):
308
  logger.debug(f"Adding prefix '{prefix}' to the first message.")
309
  messages[0]['content'] = f"{prefix} {messages[0]['content']}"
310
+
311
  random_id = ''.join(random.choices(string.ascii_letters + string.digits, k=7))
312
  messages[-1]['id'] = random_id
313
  messages[-1]['role'] = 'user'
 
323
  }
324
  messages[-1]['content'] = 'FILE:BB\n$#$\n\n$#$\n' + messages[-1]['content']
325
  logger.debug("Image data added to the message.")
326
+
327
  data = {
328
  "messages": messages,
329
  "id": random_id,
 
454
  return response
455
 
456
  # Request Models
457
+ class TextContent(BaseModel):
458
+ type: str = "text"
459
+ text: str
460
+
461
+ @validator('type')
462
+ def type_must_be_text(cls, v):
463
+ if v != "text":
464
+ raise ValueError("Type must be 'text'")
465
+ return v
466
+
467
+ class ImageContent(BaseModel):
468
+ type: str = "image_url"
469
+ image_url: Dict[str, str]
470
+
471
+ @validator('type')
472
+ def type_must_be_image_url(cls, v):
473
+ if v != "image_url":
474
+ raise ValueError("Type must be 'image_url'")
475
+ return v
476
+
477
+ ContentItem = Union[TextContent, ImageContent]
478
+
479
  class Message(BaseModel):
480
  role: str
481
+ content: List[ContentItem]
482
 
483
+ @validator('role')
484
+ def role_must_be_valid(cls, v):
485
+ if v not in {"system", "user", "assistant"}:
486
+ raise ValueError("Role must be 'system', 'user', or 'assistant'")
487
  return v
488
 
489
  class ChatRequest(BaseModel):
 
500
  logit_bias: Optional[Dict[str, float]] = None
501
  user: Optional[str] = None
502
  webSearchMode: Optional[bool] = False # Custom parameter
 
503
 
504
  class TokenizerRequest(BaseModel):
505
  text: str
 
532
  "usage": None, # To be filled in non-streaming responses
533
  }
534
 
535
+ def extract_image_from_content(content: str) -> Optional[Tuple[str, str]]:
536
+ """
537
+ Extracts the first image from the content string.
538
+ Returns a tuple of (alt_text, image_data_uri) if found, else None.
539
+ """
540
+ # Regex to match markdown image syntax: ![Alt Text](image_url)
541
+ match = re.search(r'!\[([^\]]*)\]\((data:image/\w+;base64,[^\)]+)\)', content)
542
+ if match:
543
+ alt_text = match.group(1)
544
+ image_data_uri = match.group(2)
545
+ return alt_text, image_data_uri
546
+ return None
547
+
548
+ def extract_all_images_from_content(content: str) -> List[Tuple[str, str]]:
549
+ """
550
+ Extracts all images from the content string.
551
+ Returns a list of tuples containing (alt_text, image_data_uri).
552
+ """
553
+ # Regex to match markdown image syntax: ![Alt Text](image_url)
554
+ matches = re.findall(r'!\[([^\]]*)\]\((data:image/\w+;base64,[^\)]+)\)', content)
555
+ return matches if matches else []
556
+
557
+ async def analyze_image(image_data_uri: str) -> str:
558
+ """
559
+ Placeholder function to analyze the image.
560
+ Replace this with actual image analysis logic or API calls.
561
+ """
562
+ # Extract base64 data
563
+ image_data = image_data_uri.split(",")[1]
564
+ # Decode and process the image as needed
565
+ # For example, send it to an external API
566
+ # Here, we'll return a dummy response
567
+ await asyncio.sleep(1) # Simulate processing delay
568
+ return "Image analysis result: The image depicts a beautiful sunset over the mountains."
569
+
570
  @app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
571
  async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
572
  client_ip = req.client.host
 
581
  logger.warning(f"Attempt to use unavailable model: {request.model} from IP: {client_ip}")
582
  raise HTTPException(status_code=400, detail="Requested model is not available.")
583
 
584
+ # Initialize response content
585
+ assistant_content = ""
586
+
587
+ # Iterate through messages to find and process images
588
+ for msg in request.messages:
589
+ if msg.role == "user":
590
+ # Extract all images from the message content
591
+ images = extract_all_images_from_content(" ".join([item.text if item.type == "text" else item.image_url['url'] for item in msg.content]))
592
+ for alt_text, image_data_uri in images:
593
+ # Analyze the image
594
+ analysis_result = await analyze_image(image_data_uri)
595
+ assistant_content += analysis_result + "\n"
596
+
597
+ # Example response content
598
+ assistant_content += "Based on the image you provided, here are the insights..."
599
+
600
+ # Calculate token usage (simple approximation)
601
+ prompt_tokens = sum(len(" ".join([item.text if item.type == "text" else item.image_url['url'] for item in msg.content]).split()) for msg in request.messages)
602
+ completion_tokens = len(assistant_content.split())
603
+ total_tokens = prompt_tokens + completion_tokens
604
+ estimated_cost = calculate_estimated_cost(prompt_tokens, completion_tokens)
605
+
606
+ logger.info(f"Completed response generation for API key: {api_key} | IP: {client_ip}")
607
+
608
+ return {
609
+ "id": f"chatcmpl-{uuid.uuid4()}",
610
+ "object": "chat.completion",
611
+ "created": int(datetime.now().timestamp()),
612
+ "model": request.model,
613
+ "choices": [
614
+ {
615
+ "message": {
616
+ "role": "assistant",
617
+ "content": assistant_content.strip()
618
+ },
619
+ "finish_reason": "stop",
620
+ "index": 0
621
+ }
622
+ ],
623
+ "usage": {
624
+ "prompt_tokens": prompt_tokens,
625
+ "completion_tokens": completion_tokens,
626
+ "total_tokens": total_tokens,
627
+ "estimated_cost": estimated_cost
628
+ },
629
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  except ModelNotWorkingException as e:
631
  logger.warning(f"Model not working: {e} | IP: {client_ip}")
632
  raise HTTPException(status_code=503, detail=str(e))