Update main.py
Browse files
main.py
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
import os
|
2 |
import re
|
3 |
import random
|
@@ -12,11 +14,13 @@ from typing import List, Dict, Any, Optional, AsyncGenerator, Union
|
|
12 |
|
13 |
from datetime import datetime
|
14 |
|
15 |
-
from aiohttp import ClientSession, ClientTimeout, ClientError
|
16 |
from fastapi import FastAPI, HTTPException, Request, Depends, Header
|
17 |
from fastapi.responses import StreamingResponse, JSONResponse, RedirectResponse
|
18 |
from pydantic import BaseModel
|
19 |
|
|
|
|
|
20 |
# Configure logging
|
21 |
logging.basicConfig(
|
22 |
level=logging.INFO,
|
@@ -37,6 +41,7 @@ if not API_KEYS or API_KEYS == ['']:
|
|
37 |
# Process available models
|
38 |
if AVAILABLE_MODELS:
|
39 |
AVAILABLE_MODELS = [model.strip() for model in AVAILABLE_MODELS.split(',') if model.strip()]
|
|
|
40 |
else:
|
41 |
AVAILABLE_MODELS = [] # If empty, all models are available
|
42 |
|
@@ -96,361 +101,33 @@ class ModelNotWorkingException(Exception):
|
|
96 |
self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
|
97 |
super().__init__(self.message)
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
return "data:image/png;base64,..." # Replace with actual base64 data
|
108 |
-
|
109 |
-
class Blackbox:
|
110 |
-
url = "https://www.blackbox.ai"
|
111 |
-
api_endpoint = "https://www.blackbox.ai/api/chat"
|
112 |
-
working = True
|
113 |
-
supports_stream = True
|
114 |
-
supports_system_message = True
|
115 |
-
supports_message_history = True
|
116 |
-
|
117 |
-
default_model = 'blackboxai'
|
118 |
-
image_models = ['ImageGeneration']
|
119 |
-
models = [
|
120 |
-
default_model,
|
121 |
-
'blackboxai-pro',
|
122 |
-
*image_models,
|
123 |
-
"llama-3.1-8b",
|
124 |
-
'llama-3.1-70b',
|
125 |
-
'llama-3.1-405b',
|
126 |
-
'gpt-4o',
|
127 |
-
'gemini-pro',
|
128 |
-
'gemini-1.5-flash',
|
129 |
-
'claude-sonnet-3.5',
|
130 |
-
'PythonAgent',
|
131 |
-
'JavaAgent',
|
132 |
-
'JavaScriptAgent',
|
133 |
-
'HTMLAgent',
|
134 |
-
'GoogleCloudAgent',
|
135 |
-
'AndroidDeveloper',
|
136 |
-
'SwiftDeveloper',
|
137 |
-
'Next.jsAgent',
|
138 |
-
'MongoDBAgent',
|
139 |
-
'PyTorchAgent',
|
140 |
-
'ReactAgent',
|
141 |
-
'XcodeAgent',
|
142 |
-
'AngularJSAgent',
|
143 |
-
]
|
144 |
-
|
145 |
-
# Filter models based on AVAILABLE_MODELS
|
146 |
-
if AVAILABLE_MODELS:
|
147 |
-
models = [model for model in models if model in AVAILABLE_MODELS]
|
148 |
-
|
149 |
-
agentMode = {
|
150 |
-
'ImageGeneration': {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"},
|
151 |
-
'Niansuh': {'mode': True, 'id': "NiansuhAIk1HgESy", 'name': "Niansuh"},
|
152 |
-
}
|
153 |
-
trendingAgentMode = {
|
154 |
-
"blackboxai": {},
|
155 |
-
"gemini-1.5-flash": {'mode': True, 'id': 'Gemini'},
|
156 |
-
"llama-3.1-8b": {'mode': True, 'id': "llama-3.1-8b"},
|
157 |
-
'llama-3.1-70b': {'mode': True, 'id': "llama-3.1-70b"},
|
158 |
-
'llama-3.1-405b': {'mode': True, 'id': "llama-3.1-405b"},
|
159 |
-
'blackboxai-pro': {'mode': True, 'id': "BLACKBOXAI-PRO"},
|
160 |
-
'PythonAgent': {'mode': True, 'id': "Python Agent"},
|
161 |
-
'JavaAgent': {'mode': True, 'id': "Java Agent"},
|
162 |
-
'JavaScriptAgent': {'mode': True, 'id': "JavaScript Agent"},
|
163 |
-
'HTMLAgent': {'mode': True, 'id': "HTML Agent"},
|
164 |
-
'GoogleCloudAgent': {'mode': True, 'id': "Google Cloud Agent"},
|
165 |
-
'AndroidDeveloper': {'mode': True, 'id': "Android Developer"},
|
166 |
-
'SwiftDeveloper': {'mode': True, 'id': "Swift Developer"},
|
167 |
-
'Next.jsAgent': {'mode': True, 'id': "Next.js Agent"},
|
168 |
-
'MongoDBAgent': {'mode': True, 'id': "MongoDB Agent"},
|
169 |
-
'PyTorchAgent': {'mode': True, 'id': "PyTorch Agent"},
|
170 |
-
'ReactAgent': {'mode': True, 'id': "React Agent"},
|
171 |
-
'XcodeAgent': {'mode': True, 'id': "Xcode Agent"},
|
172 |
-
'AngularJSAgent': {'mode': True, 'id': "AngularJS Agent"},
|
173 |
-
}
|
174 |
-
|
175 |
-
userSelectedModel = {
|
176 |
-
"gpt-4o": "gpt-4o",
|
177 |
-
"gemini-pro": "gemini-pro",
|
178 |
-
'claude-sonnet-3.5': "claude-sonnet-3.5",
|
179 |
-
}
|
180 |
-
|
181 |
-
model_prefixes = {
|
182 |
-
'gpt-4o': '@GPT-4o',
|
183 |
-
'gemini-pro': '@Gemini-PRO',
|
184 |
-
'claude-sonnet-3.5': '@Claude-Sonnet-3.5',
|
185 |
-
'PythonAgent': '@Python Agent',
|
186 |
-
'JavaAgent': '@Java Agent',
|
187 |
-
'JavaScriptAgent': '@JavaScript Agent',
|
188 |
-
'HTMLAgent': '@HTML Agent',
|
189 |
-
'GoogleCloudAgent': '@Google Cloud Agent',
|
190 |
-
'AndroidDeveloper': '@Android Developer',
|
191 |
-
'SwiftDeveloper': '@Swift Developer',
|
192 |
-
'Next.jsAgent': '@Next.js Agent',
|
193 |
-
'MongoDBAgent': '@MongoDB Agent',
|
194 |
-
'PyTorchAgent': '@PyTorch Agent',
|
195 |
-
'ReactAgent': '@React Agent',
|
196 |
-
'XcodeAgent': '@Xcode Agent',
|
197 |
-
'AngularJSAgent': '@AngularJS Agent',
|
198 |
-
'blackboxai-pro': '@BLACKBOXAI-PRO',
|
199 |
-
'ImageGeneration': '@Image Generation',
|
200 |
-
'Niansuh': '@Niansuh',
|
201 |
-
}
|
202 |
-
|
203 |
-
model_referers = {
|
204 |
-
"blackboxai": "/?model=blackboxai",
|
205 |
-
"gpt-4o": "/?model=gpt-4o",
|
206 |
-
"gemini-pro": "/?model=gemini-pro",
|
207 |
-
"claude-sonnet-3.5": "/?model=claude-sonnet-3.5"
|
208 |
-
}
|
209 |
-
|
210 |
-
model_aliases = {
|
211 |
-
"gemini-flash": "gemini-1.5-flash",
|
212 |
-
"claude-3.5-sonnet": "claude-sonnet-3.5",
|
213 |
-
"flux": "ImageGeneration",
|
214 |
-
"niansuh": "Niansuh",
|
215 |
-
}
|
216 |
-
|
217 |
-
@classmethod
|
218 |
-
def get_model(cls, model: str) -> str:
|
219 |
-
if model in cls.models:
|
220 |
-
return model
|
221 |
-
elif model in cls.model_aliases:
|
222 |
-
return cls.model_aliases[model]
|
223 |
-
else:
|
224 |
-
return cls.default_model
|
225 |
-
|
226 |
-
@staticmethod
|
227 |
-
def generate_random_string(length: int = 7) -> str:
|
228 |
-
characters = string.ascii_letters + string.digits
|
229 |
-
return ''.join(random.choices(characters, k=length))
|
230 |
-
|
231 |
-
@staticmethod
|
232 |
-
def generate_next_action() -> str:
|
233 |
-
return uuid.uuid4().hex
|
234 |
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
|
|
|
|
|
|
239 |
{
|
240 |
-
"
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
None,
|
251 |
-
None,
|
252 |
-
True
|
253 |
-
]
|
254 |
-
return json.dumps(router_state)
|
255 |
-
|
256 |
-
@staticmethod
|
257 |
-
def clean_response(text: str) -> str:
|
258 |
-
pattern = r'^\$\@\$v=undefined-rv1\$\@\$'
|
259 |
-
cleaned_text = re.sub(pattern, '', text)
|
260 |
-
return cleaned_text
|
261 |
-
|
262 |
-
@classmethod
|
263 |
-
async def create_async_generator(
|
264 |
-
cls,
|
265 |
-
model: str,
|
266 |
-
messages: List[Dict[str, str]],
|
267 |
-
proxy: Optional[str] = None,
|
268 |
-
websearch: bool = False,
|
269 |
-
**kwargs
|
270 |
-
) -> AsyncGenerator[Union[str, ImageResponseData], None]:
|
271 |
-
"""
|
272 |
-
Creates an asynchronous generator for streaming responses from Blackbox AI.
|
273 |
-
|
274 |
-
Parameters:
|
275 |
-
model (str): Model to use for generating responses.
|
276 |
-
messages (List[Dict[str, str]]): Message history.
|
277 |
-
proxy (Optional[str]): Proxy URL, if needed.
|
278 |
-
websearch (bool): Enables or disables web search mode.
|
279 |
-
**kwargs: Additional keyword arguments.
|
280 |
-
|
281 |
-
Yields:
|
282 |
-
Union[str, ImageResponseData]: Segments of the generated response or ImageResponseData objects.
|
283 |
-
"""
|
284 |
-
model = cls.get_model(model)
|
285 |
-
|
286 |
-
chat_id = cls.generate_random_string()
|
287 |
-
next_action = cls.generate_next_action()
|
288 |
-
next_router_state_tree = cls.generate_next_router_state_tree()
|
289 |
-
|
290 |
-
agent_mode = cls.agentMode.get(model, {})
|
291 |
-
trending_agent_mode = cls.trendingAgentMode.get(model, {})
|
292 |
-
|
293 |
-
prefix = cls.model_prefixes.get(model, "")
|
294 |
-
|
295 |
-
formatted_prompt = ""
|
296 |
-
for message in messages:
|
297 |
-
role = message.get('role', '').capitalize()
|
298 |
-
content = message.get('content', '')
|
299 |
-
if role and content:
|
300 |
-
formatted_prompt += f"{role}: {content}\n"
|
301 |
-
|
302 |
-
if prefix:
|
303 |
-
formatted_prompt = f"{prefix} {formatted_prompt}".strip()
|
304 |
-
|
305 |
-
referer_path = cls.model_referers.get(model, f"/?model={model}")
|
306 |
-
referer_url = f"{cls.url}{referer_path}"
|
307 |
-
|
308 |
-
common_headers = {
|
309 |
-
'accept': '*/*',
|
310 |
-
'accept-language': 'en-US,en;q=0.9',
|
311 |
-
'cache-control': 'no-cache',
|
312 |
-
'origin': cls.url,
|
313 |
-
'pragma': 'no-cache',
|
314 |
-
'priority': 'u=1, i',
|
315 |
-
'sec-ch-ua': '"Chromium";v="129", "Not=A?Brand";v="8"',
|
316 |
-
'sec-ch-ua-mobile': '?0',
|
317 |
-
'sec-ch-ua-platform': '"Linux"',
|
318 |
-
'sec-fetch-dest': 'empty',
|
319 |
-
'sec-fetch-mode': 'cors',
|
320 |
-
'sec-fetch-site': 'same-origin',
|
321 |
-
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) '
|
322 |
-
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
323 |
-
'Chrome/129.0.0.0 Safari/537.36'
|
324 |
-
}
|
325 |
-
|
326 |
-
headers_api_chat = {
|
327 |
-
'Content-Type': 'application/json',
|
328 |
-
'Referer': referer_url
|
329 |
-
}
|
330 |
-
headers_api_chat_combined = {**common_headers, **headers_api_chat}
|
331 |
-
|
332 |
-
payload_api_chat = {
|
333 |
-
"messages": [
|
334 |
-
{
|
335 |
-
"id": chat_id,
|
336 |
-
"content": formatted_prompt,
|
337 |
-
"role": "user"
|
338 |
-
}
|
339 |
-
],
|
340 |
-
"id": chat_id,
|
341 |
-
"previewToken": None,
|
342 |
-
"userId": None,
|
343 |
-
"codeModelMode": True,
|
344 |
-
"agentMode": agent_mode,
|
345 |
-
"trendingAgentMode": trending_agent_mode,
|
346 |
-
"isMicMode": False,
|
347 |
-
"userSystemPrompt": None,
|
348 |
-
"maxTokens": 1024,
|
349 |
-
"playgroundTopP": 0.9,
|
350 |
-
"playgroundTemperature": 0.5,
|
351 |
-
"isChromeExt": False,
|
352 |
-
"githubToken": None,
|
353 |
-
"clickedAnswer2": False,
|
354 |
-
"clickedAnswer3": False,
|
355 |
-
"clickedForceWebSearch": False,
|
356 |
-
"visitFromDelta": False,
|
357 |
-
"mobileClient": False,
|
358 |
-
"webSearchMode": websearch,
|
359 |
-
"userSelectedModel": cls.userSelectedModel.get(model, model)
|
360 |
-
}
|
361 |
-
|
362 |
-
headers_chat = {
|
363 |
-
'Accept': 'text/x-component',
|
364 |
-
'Content-Type': 'text/plain;charset=UTF-8',
|
365 |
-
'Referer': f'{cls.url}/chat/{chat_id}?model={model}',
|
366 |
-
'next-action': next_action,
|
367 |
-
'next-router-state-tree': next_router_state_tree,
|
368 |
-
'next-url': '/'
|
369 |
-
}
|
370 |
-
headers_chat_combined = {**common_headers, **headers_chat}
|
371 |
-
|
372 |
-
data_chat = '[]'
|
373 |
-
|
374 |
-
async with ClientSession(headers=common_headers) as session:
|
375 |
-
try:
|
376 |
-
async with session.post(
|
377 |
-
cls.api_endpoint,
|
378 |
-
headers=headers_api_chat_combined,
|
379 |
-
json=payload_api_chat,
|
380 |
-
proxy=proxy
|
381 |
-
) as response_api_chat:
|
382 |
-
response_api_chat.raise_for_status()
|
383 |
-
text = await response_api_chat.text()
|
384 |
-
cleaned_response = cls.clean_response(text)
|
385 |
-
|
386 |
-
if model in cls.image_models:
|
387 |
-
match = re.search(r'!\[.*?\]\((https?://[^\)]+)\)', cleaned_response)
|
388 |
-
if match:
|
389 |
-
image_url = match.group(1)
|
390 |
-
image_response = ImageResponseData(images=image_url, alt="Generated Image")
|
391 |
-
yield image_response
|
392 |
-
else:
|
393 |
-
yield cleaned_response
|
394 |
-
else:
|
395 |
-
if websearch:
|
396 |
-
match = re.search(r'\$~~~\$(.*?)\$~~~\$', cleaned_response, re.DOTALL)
|
397 |
-
if match:
|
398 |
-
source_part = match.group(1).strip()
|
399 |
-
answer_part = cleaned_response[match.end():].strip()
|
400 |
-
try:
|
401 |
-
sources = json.loads(source_part)
|
402 |
-
source_formatted = "**Sources:**\n"
|
403 |
-
for item in sources[:5]:
|
404 |
-
title = item.get('title', 'No Title')
|
405 |
-
link = item.get('link', '#')
|
406 |
-
position = item.get('position', '')
|
407 |
-
source_formatted += f"- [{title}]({link})\n"
|
408 |
-
final_response = f"{answer_part}\n\n{source_formatted}"
|
409 |
-
except json.JSONDecodeError:
|
410 |
-
final_response = f"{answer_part}\n\nSource information is unavailable."
|
411 |
-
else:
|
412 |
-
final_response = cleaned_response
|
413 |
-
else:
|
414 |
-
if '$~~~$' in cleaned_response:
|
415 |
-
final_response = cleaned_response.split('$~~~$')[0].strip()
|
416 |
-
else:
|
417 |
-
final_response = cleaned_response
|
418 |
-
|
419 |
-
yield final_response
|
420 |
-
except ClientResponseError as e:
|
421 |
-
error_text = f"Error {e.status}: {e.message}"
|
422 |
-
try:
|
423 |
-
error_response = await e.response.text()
|
424 |
-
cleaned_error = cls.clean_response(error_response)
|
425 |
-
error_text += f" - {cleaned_error}"
|
426 |
-
except Exception:
|
427 |
-
pass
|
428 |
-
yield error_text
|
429 |
-
except Exception as e:
|
430 |
-
yield f"Unexpected error during /api/chat request: {str(e)}"
|
431 |
-
|
432 |
-
chat_url = f'{cls.url}/chat/{chat_id}?model={model}'
|
433 |
-
|
434 |
-
try:
|
435 |
-
async with session.post(
|
436 |
-
chat_url,
|
437 |
-
headers=headers_chat_combined,
|
438 |
-
data=data_chat,
|
439 |
-
proxy=proxy
|
440 |
-
) as response_chat:
|
441 |
-
response_chat.raise_for_status()
|
442 |
-
# No action needed based on the original code
|
443 |
-
except ClientResponseError as e:
|
444 |
-
error_text = f"Error {e.status}: {e.message}"
|
445 |
-
try:
|
446 |
-
error_response = await e.response.text()
|
447 |
-
cleaned_error = cls.clean_response(error_response)
|
448 |
-
error_text += f" - {cleaned_error}"
|
449 |
-
except Exception:
|
450 |
-
pass
|
451 |
-
yield error_text
|
452 |
-
except Exception as e:
|
453 |
-
yield f"Unexpected error during /chat/{chat_id} request: {str(e)}"
|
454 |
|
455 |
# FastAPI app setup
|
456 |
app = FastAPI()
|
@@ -502,39 +179,11 @@ class ChatRequest(BaseModel):
|
|
502 |
frequency_penalty: Optional[float] = 0.0
|
503 |
logit_bias: Optional[Dict[str, float]] = None
|
504 |
user: Optional[str] = None
|
505 |
-
|
506 |
|
507 |
class TokenizerRequest(BaseModel):
|
508 |
text: str
|
509 |
|
510 |
-
def calculate_estimated_cost(prompt_tokens: int, completion_tokens: int) -> float:
|
511 |
-
"""
|
512 |
-
Calculate the estimated cost based on the number of tokens.
|
513 |
-
Replace the pricing below with your actual pricing model.
|
514 |
-
"""
|
515 |
-
# Example pricing: $0.00000268 per token
|
516 |
-
cost_per_token = 0.00000268
|
517 |
-
return round((prompt_tokens + completion_tokens) * cost_per_token, 8)
|
518 |
-
|
519 |
-
def create_response(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
|
520 |
-
return {
|
521 |
-
"id": f"chatcmpl-{uuid.uuid4()}",
|
522 |
-
"object": "chat.completion",
|
523 |
-
"created": int(datetime.now().timestamp()),
|
524 |
-
"model": model,
|
525 |
-
"choices": [
|
526 |
-
{
|
527 |
-
"index": 0,
|
528 |
-
"message": {
|
529 |
-
"role": "assistant",
|
530 |
-
"content": content
|
531 |
-
},
|
532 |
-
"finish_reason": finish_reason
|
533 |
-
}
|
534 |
-
],
|
535 |
-
"usage": None, # To be filled in non-streaming responses
|
536 |
-
}
|
537 |
-
|
538 |
@app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
|
539 |
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
540 |
client_ip = req.client.host
|
@@ -552,55 +201,41 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
552 |
# Process the request with actual message content, but don't log it
|
553 |
async_generator = Blackbox.create_async_generator(
|
554 |
model=request.model,
|
555 |
-
messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
|
556 |
-
websearch=request.
|
557 |
)
|
558 |
|
559 |
if request.stream:
|
560 |
async def generate():
|
561 |
try:
|
562 |
assistant_content = ""
|
563 |
-
for chunk in async_generator:
|
564 |
-
if isinstance(chunk,
|
565 |
# Handle image responses if necessary
|
566 |
image_markdown = f"\n"
|
567 |
assistant_content += image_markdown
|
568 |
-
response_chunk =
|
569 |
-
"id": f"chatcmpl-{uuid.uuid4()}",
|
570 |
-
"object": "chat.completion.chunk", # Change to 'chat.completion.chunk'
|
571 |
-
"created": int(datetime.now().timestamp()),
|
572 |
-
"model": request.model,
|
573 |
-
"choices": [
|
574 |
-
{
|
575 |
-
"index": 0,
|
576 |
-
"delta": {"role": "assistant", "content": " "}, # Initial space or any starter
|
577 |
-
"finish_reason": None
|
578 |
-
}
|
579 |
-
]
|
580 |
-
}
|
581 |
-
yield f"data: {json.dumps(response_chunk)}\n\n"
|
582 |
-
response_chunk["choices"][0]["delta"]["content"] = image_markdown.strip()
|
583 |
-
yield f"data: {json.dumps(response_chunk)}\n\n"
|
584 |
else:
|
585 |
assistant_content += chunk
|
586 |
# Yield the chunk as a partial choice
|
587 |
response_chunk = {
|
588 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
589 |
-
"object": "chat.completion.chunk",
|
590 |
"created": int(datetime.now().timestamp()),
|
591 |
"model": request.model,
|
592 |
"choices": [
|
593 |
{
|
594 |
"index": 0,
|
595 |
-
"delta": {"
|
596 |
-
"finish_reason": None
|
597 |
}
|
598 |
-
]
|
|
|
599 |
}
|
600 |
-
|
601 |
|
602 |
# After all chunks are sent, send the final message with finish_reason
|
603 |
-
prompt_tokens = sum(len(msg
|
604 |
completion_tokens = len(assistant_content.split())
|
605 |
total_tokens = prompt_tokens + completion_tokens
|
606 |
estimated_cost = calculate_estimated_cost(prompt_tokens, completion_tokens)
|
@@ -637,11 +272,11 @@ async def chat_completions(request: ChatRequest, req: Request, api_key: str = De
|
|
637 |
error_response = {"error": str(e)}
|
638 |
yield f"data: {json.dumps(error_response)}\n\n"
|
639 |
|
640 |
-
return StreamingResponse(generate(), media_type="text/event-stream")
|
641 |
else:
|
642 |
response_content = ""
|
643 |
async for chunk in async_generator:
|
644 |
-
if isinstance(chunk,
|
645 |
response_content += f"\n"
|
646 |
else:
|
647 |
response_content += chunk
|
|
|
1 |
+
# main.py
|
2 |
+
|
3 |
import os
|
4 |
import re
|
5 |
import random
|
|
|
14 |
|
15 |
from datetime import datetime
|
16 |
|
17 |
+
from aiohttp import ClientSession, ClientTimeout, ClientError
|
18 |
from fastapi import FastAPI, HTTPException, Request, Depends, Header
|
19 |
from fastapi.responses import StreamingResponse, JSONResponse, RedirectResponse
|
20 |
from pydantic import BaseModel
|
21 |
|
22 |
+
from blackbox import Blackbox, ImageResponse # Import the new Blackbox class
|
23 |
+
|
24 |
# Configure logging
|
25 |
logging.basicConfig(
|
26 |
level=logging.INFO,
|
|
|
41 |
# Process available models
|
42 |
if AVAILABLE_MODELS:
|
43 |
AVAILABLE_MODELS = [model.strip() for model in AVAILABLE_MODELS.split(',') if model.strip()]
|
44 |
+
Blackbox.models = [model for model in Blackbox.models if model in AVAILABLE_MODELS]
|
45 |
else:
|
46 |
AVAILABLE_MODELS = [] # If empty, all models are available
|
47 |
|
|
|
101 |
self.message = f"The model '{model}' is currently not working. Please try another model or wait for it to be fixed."
|
102 |
super().__init__(self.message)
|
103 |
|
104 |
+
def calculate_estimated_cost(prompt_tokens: int, completion_tokens: int) -> float:
|
105 |
+
"""
|
106 |
+
Calculate the estimated cost based on the number of tokens.
|
107 |
+
Replace the pricing below with your actual pricing model.
|
108 |
+
"""
|
109 |
+
# Example pricing: $0.00000268 per token
|
110 |
+
cost_per_token = 0.00000268
|
111 |
+
return round((prompt_tokens + completion_tokens) * cost_per_token, 8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
|
113 |
+
def create_response(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
|
114 |
+
return {
|
115 |
+
"id": f"chatcmpl-{uuid.uuid4()}",
|
116 |
+
"object": "chat.completion",
|
117 |
+
"created": int(datetime.now().timestamp()),
|
118 |
+
"model": model,
|
119 |
+
"choices": [
|
120 |
{
|
121 |
+
"index": 0,
|
122 |
+
"message": {
|
123 |
+
"role": "assistant",
|
124 |
+
"content": content
|
125 |
+
},
|
126 |
+
"finish_reason": finish_reason
|
127 |
+
}
|
128 |
+
],
|
129 |
+
"usage": None, # To be filled in non-streaming responses
|
130 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
|
132 |
# FastAPI app setup
|
133 |
app = FastAPI()
|
|
|
179 |
frequency_penalty: Optional[float] = 0.0
|
180 |
logit_bias: Optional[Dict[str, float]] = None
|
181 |
user: Optional[str] = None
|
182 |
+
webSearchMode: Optional[bool] = False # Custom parameter
|
183 |
|
184 |
class TokenizerRequest(BaseModel):
|
185 |
text: str
|
186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
@app.post("/v1/chat/completions", dependencies=[Depends(rate_limiter_per_ip)])
|
188 |
async def chat_completions(request: ChatRequest, req: Request, api_key: str = Depends(get_api_key)):
|
189 |
client_ip = req.client.host
|
|
|
201 |
# Process the request with actual message content, but don't log it
|
202 |
async_generator = Blackbox.create_async_generator(
|
203 |
model=request.model,
|
204 |
+
messages=[{"role": msg.role, "content": msg.content} for msg in request.messages],
|
205 |
+
websearch=request.webSearchMode
|
206 |
)
|
207 |
|
208 |
if request.stream:
|
209 |
async def generate():
|
210 |
try:
|
211 |
assistant_content = ""
|
212 |
+
async for chunk in async_generator:
|
213 |
+
if isinstance(chunk, ImageResponse):
|
214 |
# Handle image responses if necessary
|
215 |
image_markdown = f"\n"
|
216 |
assistant_content += image_markdown
|
217 |
+
response_chunk = create_response(image_markdown, request.model, finish_reason=None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
else:
|
219 |
assistant_content += chunk
|
220 |
# Yield the chunk as a partial choice
|
221 |
response_chunk = {
|
222 |
"id": f"chatcmpl-{uuid.uuid4()}",
|
223 |
+
"object": "chat.completion.chunk",
|
224 |
"created": int(datetime.now().timestamp()),
|
225 |
"model": request.model,
|
226 |
"choices": [
|
227 |
{
|
228 |
"index": 0,
|
229 |
+
"delta": {"content": chunk, "role": "assistant"},
|
230 |
+
"finish_reason": None,
|
231 |
}
|
232 |
+
],
|
233 |
+
"usage": None, # Usage can be updated if you track tokens in real-time
|
234 |
}
|
235 |
+
yield f"data: {json.dumps(response_chunk)}\n\n"
|
236 |
|
237 |
# After all chunks are sent, send the final message with finish_reason
|
238 |
+
prompt_tokens = sum(len(msg['content'].split()) for msg in request.messages)
|
239 |
completion_tokens = len(assistant_content.split())
|
240 |
total_tokens = prompt_tokens + completion_tokens
|
241 |
estimated_cost = calculate_estimated_cost(prompt_tokens, completion_tokens)
|
|
|
272 |
error_response = {"error": str(e)}
|
273 |
yield f"data: {json.dumps(error_response)}\n\n"
|
274 |
|
275 |
+
return StreamingResponse(generate(), media_type="text/event-stream")
|
276 |
else:
|
277 |
response_content = ""
|
278 |
async for chunk in async_generator:
|
279 |
+
if isinstance(chunk, ImageResponse):
|
280 |
response_content += f"\n"
|
281 |
else:
|
282 |
response_content += chunk
|