AIMaster7 commited on
Commit
4ef6033
Β·
verified Β·
1 Parent(s): cc19654

Create main.py

Browse files
Files changed (1) hide show
  1. main.py +291 -0
main.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import os
5
+ import random
6
+ import string
7
+ import time
8
+ import uuid
9
+ from http import HTTPStatus
10
+ from typing import AsyncGenerator, Dict, List, Any
11
+
12
+ import aiohttp
13
+ from fastapi import FastAPI, Request, HTTPException
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import StreamingResponse
16
+ from tenacity import (
17
+ retry,
18
+ stop_after_attempt,
19
+ wait_exponential,
20
+ retry_if_exception_type,
21
+ RetryError,
22
+ )
23
+
24
+ # ─── Logging ───
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format="%(asctime)s %(levelname)s %(message)s",
28
+ datefmt="%H:%M:%S",
29
+ )
30
+ logger = logging.getLogger("proxy")
31
+
32
+ # ─── Config ───
33
+ BLACKBOX_URL = "https://www.blackbox.ai/api/chat"
34
+ CONNECTION_LIMIT = 200
35
+ CONNECTION_LIMIT_PER_HOST = 50
36
+ REQUEST_TIMEOUT = 300
37
+ WORKER_COUNT = 10
38
+
39
+ # ─── Static Headers ───
40
+ HEADERS = {
41
+ "accept": "*/*",
42
+ "content-type": "application/json",
43
+ "origin": "https://www.blackbox.ai",
44
+ "referer": "https://www.blackbox.ai/",
45
+ "user-agent": (
46
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
47
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
48
+ "Chrome/136.0.0.0 Safari/537.36"
49
+ ),
50
+ }
51
+
52
+ # ─── FastAPI Setup ───
53
+ app = FastAPI()
54
+ app.add_middleware(
55
+ CORSMiddleware,
56
+ allow_origins=["*"],
57
+ allow_credentials=True,
58
+ allow_methods=["*"],
59
+ allow_headers=["*"],
60
+ )
61
+
62
+ HTTP_SESSION: aiohttp.ClientSession = None
63
+ REQUEST_QUEUE: asyncio.Queue = asyncio.Queue()
64
+ WORKER_TASKS: List[asyncio.Task] = []
65
+
66
+ # ─── Retryable Error ───
67
+ class RetryableStatusError(Exception):
68
+ def __init__(self, status: int, text: str):
69
+ self.status = status
70
+ self.text = text
71
+ super().__init__(f"status={status} body={text[:100]}...")
72
+
73
+ RETRYABLE_STATUSES = {400, 429, 500, 502, 503, 504}
74
+
75
+ # ─── Random Data ───
76
+ _ascii = string.ascii_letters + string.digits
77
+ def _rand(n, pool=_ascii): return "".join(random.choice(pool) for _ in range(n))
78
+ def random_email(): return _rand(12) + "@gmail.com"
79
+ def random_id(): return _rand(21, string.digits)
80
+ def random_customer_id(): return "cus_" + _rand(12)
81
+
82
+ # ─── Payload Generator ───
83
+ def build_payload(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
84
+ return {
85
+ "messages": messages,
86
+ "id": _rand(8),
87
+ "agentMode": {},
88
+ "codeModelMode": True,
89
+ "trendingAgentMode": {},
90
+ "isMicMode": False,
91
+ "userSystemPrompt": None,
92
+ "maxTokens": 1024,
93
+ "playgroundTopP": None,
94
+ "playgroundTemperature": None,
95
+ "isChromeExt": False,
96
+ "githubToken": "",
97
+ "clickedAnswer2": False,
98
+ "clickedAnswer3": False,
99
+ "clickedForceWebSearch": False,
100
+ "visitFromDelta": False,
101
+ "isMemoryEnabled": False,
102
+ "mobileClient": False,
103
+ "userSelectedModel": None,
104
+ "validated": str(uuid.uuid4()),
105
+ "imageGenerationMode": False,
106
+ "webSearchModePrompt": False,
107
+ "deepSearchMode": True,
108
+ "domains": None,
109
+ "vscodeClient": False,
110
+ "codeInterpreterMode": False,
111
+ "customProfile": {
112
+ "name": "",
113
+ "occupation": "",
114
+ "traits": [],
115
+ "additionalInfo": "",
116
+ "enableNewChats": False
117
+ },
118
+ "session": {
119
+ "user": {
120
+ "name": "S.C gaming",
121
+ "email": random_email(),
122
+ "image": "https://lh3.googleusercontent.com/a/default",
123
+ "id": random_id()
124
+ },
125
+ "expires": "2025-12-31T23:59:59Z",
126
+ "isNewUser": False
127
+ },
128
+ "isPremium": True,
129
+ "subscriptionCache": {
130
+ "status": "PREMIUM",
131
+ "customerId": random_customer_id(),
132
+ "expiryTimestamp": int(time.time()) + 30 * 86400,
133
+ "lastChecked": int(time.time()),
134
+ "isTrialSubscription": False
135
+ },
136
+ "beastMode": False,
137
+ "reasoningMode": False,
138
+ "designerMode": False
139
+ }
140
+
141
+ # ─── Retry Wrapper ───
142
+ def log_retry(retry_state):
143
+ rid = retry_state.kwargs.get("request_id", "unknown")
144
+ attempt = retry_state.attempt_number
145
+ err = retry_state.outcome.exception()
146
+ logger.warning("[%s] retry %s/10 due to %s", rid, attempt, err)
147
+
148
+ @retry(
149
+ stop=stop_after_attempt(10),
150
+ wait=wait_exponential(min=1, max=10),
151
+ retry=retry_if_exception_type(
152
+ (aiohttp.ClientConnectionError, aiohttp.ClientResponseError, asyncio.TimeoutError, RetryableStatusError)
153
+ ),
154
+ before_sleep=log_retry
155
+ )
156
+ async def get_blackbox_response(*, data, stream: bool, request_id: str) -> AsyncGenerator[str, None]:
157
+ assert HTTP_SESSION
158
+ async with HTTP_SESSION.post(BLACKBOX_URL, json=data, headers=HEADERS, timeout=REQUEST_TIMEOUT) as resp:
159
+ if resp.status != 200:
160
+ body = await resp.text()
161
+ logger.error("[%s] Upstream %s error: %s", request_id, BLACKBOX_URL, resp.status)
162
+ if resp.status in RETRYABLE_STATUSES:
163
+ raise RetryableStatusError(resp.status, body)
164
+ raise HTTPException(status_code=502, detail=f"Upstream error {resp.status}")
165
+ if stream:
166
+ async for chunk in resp.content.iter_any():
167
+ if chunk:
168
+ yield chunk.decode("utf-8", "ignore")
169
+ else:
170
+ yield await resp.text()
171
+
172
+ # ─── Worker Thread ───
173
+ async def _worker():
174
+ while True:
175
+ try:
176
+ data, request_id, out_q = await REQUEST_QUEUE.get()
177
+ try:
178
+ async for piece in get_blackbox_response(data=data, stream=False, request_id=request_id):
179
+ await out_q.put(piece)
180
+ except Exception as e:
181
+ await out_q.put(f"Error:{e}")
182
+ finally:
183
+ await out_q.put(None)
184
+ REQUEST_QUEUE.task_done()
185
+ except asyncio.CancelledError:
186
+ break
187
+
188
+ # ─── Middleware ───
189
+ @app.middleware("http")
190
+ async def add_request_id(request: Request, call_next):
191
+ request.state.request_id = rid = str(uuid.uuid4())
192
+ logger.info("[%s] %s %s", rid, request.method, request.url.path)
193
+ start = time.perf_counter()
194
+ resp = await call_next(request)
195
+ logger.info("[%s] finished in %.2fs", rid, time.perf_counter() - start)
196
+ return resp
197
+
198
+ # ─── Root & Health ───
199
+ @app.get("/")
200
+ async def root():
201
+ return {"message": "API is running"}
202
+
203
+ @app.get("/health")
204
+ async def health():
205
+ return {"status": "ok"}
206
+
207
+ # ─── Chat Completion ───
208
+ @app.post("/v1/chat/completions")
209
+ async def chat_completions(request: Request):
210
+ rid = request.state.request_id
211
+ try:
212
+ body = await request.json()
213
+ messages = body.get("messages", [])
214
+ if not messages:
215
+ raise HTTPException(status_code=400, detail="Missing 'messages'")
216
+ stream = body.get("stream", False)
217
+
218
+ payload = build_payload(messages)
219
+
220
+ if not stream:
221
+ q: asyncio.Queue = asyncio.Queue()
222
+ await REQUEST_QUEUE.put((payload, rid, q))
223
+ chunks: List[str] = []
224
+ while True:
225
+ part = await q.get()
226
+ if part is None:
227
+ break
228
+ if isinstance(part, str) and part.startswith("Error:"):
229
+ raise HTTPException(status_code=502, detail=part)
230
+ chunks.append(part)
231
+ answer = "".join(chunks) or "No response."
232
+ return {
233
+ "id": str(uuid.uuid4()),
234
+ "object": "chat.completion",
235
+ "created": int(time.time()),
236
+ "model": "DeepResearch",
237
+ "choices": [
238
+ {
239
+ "index": 0,
240
+ "message": {"role": "assistant", "content": answer},
241
+ "finish_reason": "stop",
242
+ }
243
+ ],
244
+ }
245
+
246
+ async def event_stream():
247
+ try:
248
+ async for chunk in get_blackbox_response(data=payload, stream=True, request_id=rid):
249
+ msg = {
250
+ "id": str(uuid.uuid4()),
251
+ "object": "chat.completion.chunk",
252
+ "created": int(time.time()),
253
+ "model": "DeepResearch",
254
+ "choices": [{"index": 0, "delta": {"content": chunk}}],
255
+ }
256
+ yield f"data: {json.dumps(msg)}\n\n"
257
+ yield "data: [DONE]\n\n"
258
+ except Exception as e:
259
+ logger.error("[%s] stream error: %s", rid, e)
260
+ raise HTTPException(status_code=500, detail="streaming error")
261
+
262
+ return StreamingResponse(event_stream(), media_type="text/event-stream")
263
+
264
+ except json.JSONDecodeError:
265
+ raise HTTPException(status_code=400, detail="Invalid JSON")
266
+ except RetryError as re:
267
+ logger.error("[%s] retries failed: %s", rid, re)
268
+ raise HTTPException(status_code=502, detail="Blackbox upstream failed")
269
+ except Exception as e:
270
+ logger.exception("[%s] error", rid)
271
+ raise HTTPException(status_code=500, detail="Internal proxy error")
272
+
273
+ # ─── Startup & Shutdown ───
274
+ @app.on_event("startup")
275
+ async def startup():
276
+ global HTTP_SESSION, WORKER_TASKS
277
+ HTTP_SESSION = aiohttp.ClientSession(
278
+ connector=aiohttp.TCPConnector(limit=CONNECTION_LIMIT, limit_per_host=CONNECTION_LIMIT_PER_HOST),
279
+ timeout=aiohttp.ClientTimeout(total=REQUEST_TIMEOUT),
280
+ )
281
+ WORKER_TASKS = [asyncio.create_task(_worker()) for _ in range(WORKER_COUNT)]
282
+ logger.info("Started %d workers", WORKER_COUNT)
283
+
284
+ @app.on_event("shutdown")
285
+ async def shutdown():
286
+ for t in WORKER_TASKS:
287
+ t.cancel()
288
+ await asyncio.gather(*WORKER_TASKS, return_exceptions=True)
289
+ if HTTP_SESSION:
290
+ await HTTP_SESSION.close()
291
+ logger.info("Shutdown complete")