Update api/utils.py
Browse files- api/utils.py +272 -133
api/utils.py
CHANGED
@@ -5,7 +5,7 @@ import re
|
|
5 |
import string
|
6 |
import time
|
7 |
import uuid
|
8 |
-
from datetime import datetime, timezone
|
9 |
from typing import Any, Dict, List, Optional
|
10 |
|
11 |
import boto3
|
@@ -52,15 +52,15 @@ BLOCKED_MESSAGE = (
|
|
52 |
)
|
53 |
|
54 |
# ---------------------------------------------
|
55 |
-
#
|
56 |
# ---------------------------------------------
|
57 |
def get_random_name_email_customer():
|
58 |
first_names = ["Aliace", "B21ob", "Car232ol", "Daavid", "Evewwlyn", "Fraank", "Grssace", "Hefctor", "Ivgy", "Jackdie"]
|
59 |
-
last_names
|
60 |
|
61 |
random_name = f"{random.choice(first_names)} {random.choice(last_names)}"
|
62 |
email_username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
|
63 |
-
random_email = f"
|
64 |
suffix_length = len("Rldf7IKdNhdhiw")
|
65 |
suffix_chars = string.ascii_letters + string.digits
|
66 |
random_suffix = ''.join(random.choice(suffix_chars) for _ in range(suffix_length))
|
@@ -68,38 +68,9 @@ def get_random_name_email_customer():
|
|
68 |
|
69 |
return random_name, random_email, random_customer_id
|
70 |
|
71 |
-
def generate_session(email: str, id_length: int = 21, days_ahead: int = 365) -> dict:
|
72 |
-
numeric_id = ''.join(random.choice('0123456789') for _ in range(id_length))
|
73 |
-
future_date = datetime.now(timezone.utc) + timedelta(days=days_ahead)
|
74 |
-
expiry = future_date.isoformat(timespec='milliseconds').replace('+00:00', 'Z')
|
75 |
-
|
76 |
-
chars = string.ascii_letters + string.digits + "-"
|
77 |
-
random_img_id = ''.join(random.choice(chars) for _ in range(48))
|
78 |
-
image_url = f"https://lh3.googleusercontent.com/a/ACg8oc{random_img_id}=s96-c"
|
79 |
-
|
80 |
-
return {
|
81 |
-
"user": {
|
82 |
-
"name": "SNAPZION",
|
83 |
-
"email": email,
|
84 |
-
"image": image_url,
|
85 |
-
"id": numeric_id
|
86 |
-
},
|
87 |
-
"expires": expiry,
|
88 |
-
"isNewUser": False
|
89 |
-
}
|
90 |
-
|
91 |
-
def generate_session_data() -> dict:
|
92 |
-
_, email, _ = get_random_name_email_customer()
|
93 |
-
session_data = generate_session(email)
|
94 |
-
logger.info(f"Using generated session with email {email}")
|
95 |
-
return session_data
|
96 |
-
|
97 |
-
# ---------------------------------------------
|
98 |
-
# HELPER FUNCTIONS
|
99 |
-
# ---------------------------------------------
|
100 |
def generate_system_fingerprint() -> str:
|
101 |
-
|
102 |
-
short_hash = hashlib.md5(
|
103 |
return f"fp_{short_hash}"
|
104 |
|
105 |
def get_last_user_prompt(messages: List[Any]) -> str:
|
@@ -115,36 +86,43 @@ def get_last_user_prompt(messages: List[Any]) -> str:
|
|
115 |
|
116 |
def upload_replaced_urls_to_r2(urls: List[str], alt_text: str = "") -> None:
|
117 |
if not urls:
|
118 |
-
logger.info("No replaced or final URLs to store.")
|
119 |
return
|
120 |
-
|
|
|
121 |
try:
|
122 |
-
|
123 |
-
|
|
|
124 |
except s3.exceptions.NoSuchKey:
|
125 |
-
|
126 |
except Exception as e:
|
127 |
-
logger.error(f"Error reading
|
128 |
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
131 |
|
132 |
try:
|
133 |
s3.put_object(
|
134 |
Bucket=R2_BUCKET_NAME,
|
135 |
Key=R2_REPLACED_URLS_KEY,
|
136 |
-
Body=
|
137 |
ContentType="text/plain",
|
138 |
)
|
139 |
-
logger.info(f"Appended {len(urls)} URLs to
|
140 |
except Exception as e:
|
141 |
-
logger.error(f"
|
142 |
|
143 |
def calculate_tokens(text: str, model: str) -> int:
|
144 |
try:
|
145 |
-
|
146 |
-
|
147 |
-
|
|
|
|
|
148 |
return len(text.split())
|
149 |
|
150 |
def create_chat_completion_data(
|
@@ -166,20 +144,22 @@ def create_chat_completion_data(
|
|
166 |
}
|
167 |
return {
|
168 |
"id": request_id,
|
169 |
-
"object": "chat.completion.chunk",
|
170 |
"created": timestamp,
|
171 |
"model": model,
|
172 |
"system_fingerprint": system_fingerprint,
|
173 |
-
"choices": [{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
"usage": usage,
|
175 |
}
|
176 |
|
177 |
def message_to_dict(message, model_prefix: Optional[str] = None):
|
178 |
-
"""
|
179 |
-
Convert a ChatRequest message to a dict for the request payload.
|
180 |
-
Supports up to three images with type-based structure and sends multiple formats.
|
181 |
-
Prepends model_prefix to text content if specified.
|
182 |
-
"""
|
183 |
content = ""
|
184 |
images_data = []
|
185 |
image_urls = []
|
@@ -189,11 +169,11 @@ def message_to_dict(message, model_prefix: Optional[str] = None):
|
|
189 |
if item.get("type") == "text":
|
190 |
content = item.get("text", "").strip()
|
191 |
elif item.get("type") == "image_url" and len(images_data) < 3:
|
192 |
-
|
193 |
-
if
|
194 |
-
|
195 |
-
images_data.append({"filePath":
|
196 |
-
image_urls.append({"image_url": {"url":
|
197 |
elif isinstance(message.content, str):
|
198 |
content = message.content.strip()
|
199 |
|
@@ -208,14 +188,14 @@ def message_to_dict(message, model_prefix: Optional[str] = None):
|
|
208 |
"title": "snapshot",
|
209 |
"imagesData": images_data
|
210 |
}
|
211 |
-
|
212 |
-
|
213 |
-
base.setdefault("content", []).append(extra)
|
214 |
|
215 |
return base
|
216 |
|
217 |
def strip_model_prefix(content: str, model_prefix: Optional[str] = None) -> str:
|
218 |
if model_prefix and content.startswith(model_prefix):
|
|
|
219 |
return content[len(model_prefix):].strip()
|
220 |
return content
|
221 |
|
@@ -223,23 +203,29 @@ def strip_model_prefix(content: str, model_prefix: Optional[str] = None) -> str:
|
|
223 |
# STREAMING RESPONSE HANDLER
|
224 |
# ---------------------------------------------
|
225 |
async def process_streaming_response(request: ChatRequest):
|
226 |
-
|
|
|
|
|
|
|
|
|
|
|
227 |
request_id = f"chatcmpl-{uuid.uuid4()}"
|
228 |
-
logger.info(f"Processing (stream) {request_id} - Model: {request.model}")
|
229 |
|
230 |
agent_mode = AGENT_MODE.get(request.model, {})
|
231 |
-
|
232 |
-
|
233 |
|
234 |
-
|
235 |
if request.model == "o1-preview":
|
236 |
await asyncio.sleep(random.randint(1, 60))
|
237 |
|
238 |
-
|
239 |
-
if not
|
|
|
240 |
raise HTTPException(status_code=500, detail="Missing h-value.")
|
241 |
|
242 |
-
|
243 |
|
244 |
json_data = {
|
245 |
"agentMode": agent_mode,
|
@@ -259,41 +245,75 @@ async def process_streaming_response(request: ChatRequest):
|
|
259 |
"isPremium": True,
|
260 |
"isMemoryEnabled": False,
|
261 |
"maxTokens": request.max_tokens,
|
262 |
-
"messages":
|
263 |
"mobileClient": False,
|
264 |
"playgroundTemperature": request.temperature,
|
265 |
"playgroundTopP": request.top_p,
|
266 |
"previewToken": None,
|
267 |
-
"trendingAgentMode":
|
268 |
"userId": None,
|
269 |
"userSelectedModel": MODEL_MAPPING.get(request.model, request.model),
|
270 |
"userSystemPrompt": None,
|
271 |
-
"validated":
|
272 |
"visitFromDelta": False,
|
273 |
"webSearchModePrompt": False,
|
274 |
"vscodeClient": False,
|
275 |
"designerMode": False,
|
276 |
"workspaceId": "",
|
277 |
"beastMode": False,
|
278 |
-
"customProfile": {
|
279 |
-
|
280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
}
|
282 |
|
283 |
prompt_tokens = sum(
|
284 |
-
calculate_tokens(
|
285 |
-
|
286 |
-
for m in msgs
|
287 |
)
|
288 |
-
|
289 |
completion_tokens = 0
|
290 |
-
|
291 |
|
292 |
async with httpx.AsyncClient() as client:
|
293 |
try:
|
294 |
-
async with client.stream(
|
295 |
-
|
296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
if not chunk:
|
298 |
continue
|
299 |
if chunk.startswith("$@$v=undefined-rv1$@$"):
|
@@ -302,48 +322,92 @@ async def process_streaming_response(request: ChatRequest):
|
|
302 |
chunk = chunk.replace(BLOCKED_MESSAGE, "").strip()
|
303 |
if not chunk:
|
304 |
continue
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
yield "data: " + json.dumps(
|
312 |
-
create_chat_completion_data(
|
|
|
|
|
313 |
) + "\n\n"
|
314 |
|
315 |
-
|
316 |
yield "data: " + json.dumps(
|
317 |
-
create_chat_completion_data(
|
|
|
|
|
|
|
|
|
318 |
) + "\n\n"
|
319 |
yield "data: [DONE]\n\n"
|
320 |
|
321 |
-
except
|
322 |
-
logger.error(f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
|
324 |
-
upload_replaced_urls_to_r2(final_links, alt_text=get_last_user_prompt(request.messages))
|
325 |
|
326 |
# ---------------------------------------------
|
327 |
# NON-STREAMING RESPONSE HANDLER
|
328 |
# ---------------------------------------------
|
329 |
async def process_non_streaming_response(request: ChatRequest):
|
330 |
-
|
|
|
|
|
|
|
|
|
|
|
331 |
request_id = f"chatcmpl-{uuid.uuid4()}"
|
332 |
-
logger.info(f"Processing (non-stream) {request_id} - Model: {request.model}")
|
333 |
|
334 |
agent_mode = AGENT_MODE.get(request.model, {})
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
|
|
|
|
|
|
|
|
|
|
339 |
if request.model == "o1-preview":
|
340 |
await asyncio.sleep(random.randint(20, 60))
|
341 |
|
342 |
-
|
343 |
-
|
|
|
|
|
344 |
raise HTTPException(status_code=500, detail="Missing h-value.")
|
345 |
|
346 |
-
|
347 |
|
348 |
json_data = {
|
349 |
"agentMode": agent_mode,
|
@@ -363,66 +427,141 @@ async def process_non_streaming_response(request: ChatRequest):
|
|
363 |
"isPremium": True,
|
364 |
"isMemoryEnabled": False,
|
365 |
"maxTokens": request.max_tokens,
|
366 |
-
"messages":
|
367 |
"mobileClient": False,
|
368 |
"playgroundTemperature": request.temperature,
|
369 |
"playgroundTopP": request.top_p,
|
370 |
"previewToken": None,
|
371 |
-
"trendingAgentMode":
|
372 |
"userId": None,
|
373 |
"userSelectedModel": MODEL_MAPPING.get(request.model, request.model),
|
374 |
"userSystemPrompt": None,
|
375 |
-
"validated":
|
376 |
"visitFromDelta": False,
|
377 |
"webSearchModePrompt": False,
|
378 |
"vscodeClient": False,
|
379 |
"designerMode": False,
|
380 |
"workspaceId": "",
|
381 |
"beastMode": False,
|
382 |
-
"customProfile": {
|
383 |
-
|
384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
}
|
386 |
|
387 |
prompt_tokens = sum(
|
388 |
-
calculate_tokens(
|
389 |
-
|
390 |
-
for m in msgs
|
391 |
)
|
392 |
|
393 |
-
|
394 |
-
|
395 |
|
396 |
async with httpx.AsyncClient() as client:
|
397 |
try:
|
398 |
-
|
399 |
-
|
400 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
401 |
except Exception as e:
|
|
|
|
|
402 |
return {
|
403 |
"id": request_id,
|
404 |
"object": "chat.completion",
|
405 |
"created": int(datetime.now().timestamp()),
|
406 |
"model": request.model,
|
407 |
-
"system_fingerprint":
|
408 |
-
"choices": [{
|
409 |
-
|
|
|
|
|
|
|
|
|
410 |
}
|
411 |
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
417 |
|
418 |
-
|
|
|
419 |
|
420 |
return {
|
421 |
"id": request_id,
|
422 |
"object": "chat.completion",
|
423 |
"created": int(datetime.now().timestamp()),
|
424 |
"model": request.model,
|
425 |
-
"system_fingerprint":
|
426 |
-
"choices": [{
|
427 |
-
|
|
|
|
|
|
|
|
|
428 |
}
|
|
|
5 |
import string
|
6 |
import time
|
7 |
import uuid
|
8 |
+
from datetime import datetime, timezone
|
9 |
from typing import Any, Dict, List, Optional
|
10 |
|
11 |
import boto3
|
|
|
52 |
)
|
53 |
|
54 |
# ---------------------------------------------
|
55 |
+
# RANDOM USER-DATA GENERATION
|
56 |
# ---------------------------------------------
|
57 |
def get_random_name_email_customer():
|
58 |
first_names = ["Aliace", "B21ob", "Car232ol", "Daavid", "Evewwlyn", "Fraank", "Grssace", "Hefctor", "Ivgy", "Jackdie"]
|
59 |
+
last_names = ["Smilth", "Johnkson", "Dajvis", "Mihller", "Thomgpson", "Garwcia", "Broawn", "Wilfson", "Maartin", "Clarak"]
|
60 |
|
61 |
random_name = f"{random.choice(first_names)} {random.choice(last_names)}"
|
62 |
email_username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
|
63 |
+
random_email = f"{email_username}@gmail.com"
|
64 |
suffix_length = len("Rldf7IKdNhdhiw")
|
65 |
suffix_chars = string.ascii_letters + string.digits
|
66 |
random_suffix = ''.join(random.choice(suffix_chars) for _ in range(suffix_length))
|
|
|
68 |
|
69 |
return random_name, random_email, random_customer_id
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
def generate_system_fingerprint() -> str:
|
72 |
+
raw_data = f"{platform.node()}-{time.time()}-{uuid.uuid4()}"
|
73 |
+
short_hash = hashlib.md5(raw_data.encode()).hexdigest()[:12]
|
74 |
return f"fp_{short_hash}"
|
75 |
|
76 |
def get_last_user_prompt(messages: List[Any]) -> str:
|
|
|
86 |
|
87 |
def upload_replaced_urls_to_r2(urls: List[str], alt_text: str = "") -> None:
|
88 |
if not urls:
|
89 |
+
logger.info("No replaced or final Snapzion URLs to store. Skipping snapzion.txt update.")
|
90 |
return
|
91 |
+
|
92 |
+
existing_data = ""
|
93 |
try:
|
94 |
+
response = s3.get_object(Bucket=R2_BUCKET_NAME, Key=R2_REPLACED_URLS_KEY)
|
95 |
+
existing_data = response['Body'].read().decode('utf-8')
|
96 |
+
logger.info("Successfully read existing snapzion.txt from R2.")
|
97 |
except s3.exceptions.NoSuchKey:
|
98 |
+
logger.info("snapzion.txt does not exist yet. Will create a new one.")
|
99 |
except Exception as e:
|
100 |
+
logger.error(f"Error reading snapzion.txt from R2: {e}")
|
101 |
|
102 |
+
alt_text = alt_text.strip()
|
103 |
+
markdown_lines = [f"" for url in urls]
|
104 |
+
to_append = "\n".join(markdown_lines)
|
105 |
+
|
106 |
+
updated_content = (existing_data + "\n" + to_append) if existing_data.strip() else to_append
|
107 |
|
108 |
try:
|
109 |
s3.put_object(
|
110 |
Bucket=R2_BUCKET_NAME,
|
111 |
Key=R2_REPLACED_URLS_KEY,
|
112 |
+
Body=updated_content.encode("utf-8"),
|
113 |
ContentType="text/plain",
|
114 |
)
|
115 |
+
logger.info(f"Appended {len(urls)} new URLs to snapzion.txt in R2 (in Markdown format).")
|
116 |
except Exception as e:
|
117 |
+
logger.error(f"Failed to upload replaced URLs to R2: {e}")
|
118 |
|
119 |
def calculate_tokens(text: str, model: str) -> int:
|
120 |
try:
|
121 |
+
encoding = tiktoken.encoding_for_model(model)
|
122 |
+
tokens = encoding.encode(text)
|
123 |
+
return len(tokens)
|
124 |
+
except KeyError:
|
125 |
+
logger.warning(f"Model '{model}' not supported by tiktoken for token counting. Using a generic method.")
|
126 |
return len(text.split())
|
127 |
|
128 |
def create_chat_completion_data(
|
|
|
144 |
}
|
145 |
return {
|
146 |
"id": request_id,
|
147 |
+
"object": "chat.completion.chunk" if finish_reason is None else "chat.completion",
|
148 |
"created": timestamp,
|
149 |
"model": model,
|
150 |
"system_fingerprint": system_fingerprint,
|
151 |
+
"choices": [{
|
152 |
+
"index": 0,
|
153 |
+
"delta" if finish_reason is None else "message": {
|
154 |
+
"content": content,
|
155 |
+
"role": "assistant"
|
156 |
+
},
|
157 |
+
"finish_reason": finish_reason
|
158 |
+
}],
|
159 |
"usage": usage,
|
160 |
}
|
161 |
|
162 |
def message_to_dict(message, model_prefix: Optional[str] = None):
|
|
|
|
|
|
|
|
|
|
|
163 |
content = ""
|
164 |
images_data = []
|
165 |
image_urls = []
|
|
|
169 |
if item.get("type") == "text":
|
170 |
content = item.get("text", "").strip()
|
171 |
elif item.get("type") == "image_url" and len(images_data) < 3:
|
172 |
+
image_url = item["image_url"].get("url", "")
|
173 |
+
if image_url:
|
174 |
+
file_path = f"MultipleFiles/{uuid.uuid4().hex}.jpg"
|
175 |
+
images_data.append({"filePath": file_path, "contents": image_url})
|
176 |
+
image_urls.append({"image_url": {"url": image_url}})
|
177 |
elif isinstance(message.content, str):
|
178 |
content = message.content.strip()
|
179 |
|
|
|
188 |
"title": "snapshot",
|
189 |
"imagesData": images_data
|
190 |
}
|
191 |
+
for img in image_urls[1:]:
|
192 |
+
base["content"] = [base["content"], img]
|
|
|
193 |
|
194 |
return base
|
195 |
|
196 |
def strip_model_prefix(content: str, model_prefix: Optional[str] = None) -> str:
|
197 |
if model_prefix and content.startswith(model_prefix):
|
198 |
+
logger.debug(f"Stripping prefix '{model_prefix}' from content.")
|
199 |
return content[len(model_prefix):].strip()
|
200 |
return content
|
201 |
|
|
|
203 |
# STREAMING RESPONSE HANDLER
|
204 |
# ---------------------------------------------
|
205 |
async def process_streaming_response(request: ChatRequest):
|
206 |
+
# Generate a fixed-length session ID on each request
|
207 |
+
session_id = str(uuid.uuid4()) # 36-character UUID
|
208 |
+
is_new_user = False # set your own logic here
|
209 |
+
|
210 |
+
system_fingerprint = generate_system_fingerprint()
|
211 |
+
random_name, random_email, random_customer_id = get_random_name_email_customer()
|
212 |
request_id = f"chatcmpl-{uuid.uuid4()}"
|
213 |
+
logger.info(f"Processing request (stream) {request_id} - Model: {request.model}")
|
214 |
|
215 |
agent_mode = AGENT_MODE.get(request.model, {})
|
216 |
+
trending_agent_mode = TRENDING_AGENT_MODE.get(request.model, {})
|
217 |
+
model_prefix = MODEL_PREFIXES.get(request.model, "")
|
218 |
|
219 |
+
headers_api_chat = get_headers_api_chat(BASE_URL)
|
220 |
if request.model == "o1-preview":
|
221 |
await asyncio.sleep(random.randint(1, 60))
|
222 |
|
223 |
+
h_value = await getHid()
|
224 |
+
if not h_value:
|
225 |
+
logger.error("No h-value for validation.")
|
226 |
raise HTTPException(status_code=500, detail="Missing h-value.")
|
227 |
|
228 |
+
messages = [message_to_dict(msg, model_prefix=model_prefix) for msg in request.messages]
|
229 |
|
230 |
json_data = {
|
231 |
"agentMode": agent_mode,
|
|
|
245 |
"isPremium": True,
|
246 |
"isMemoryEnabled": False,
|
247 |
"maxTokens": request.max_tokens,
|
248 |
+
"messages": messages,
|
249 |
"mobileClient": False,
|
250 |
"playgroundTemperature": request.temperature,
|
251 |
"playgroundTopP": request.top_p,
|
252 |
"previewToken": None,
|
253 |
+
"trendingAgentMode": trending_agent_mode,
|
254 |
"userId": None,
|
255 |
"userSelectedModel": MODEL_MAPPING.get(request.model, request.model),
|
256 |
"userSystemPrompt": None,
|
257 |
+
"validated": h_value,
|
258 |
"visitFromDelta": False,
|
259 |
"webSearchModePrompt": False,
|
260 |
"vscodeClient": False,
|
261 |
"designerMode": False,
|
262 |
"workspaceId": "",
|
263 |
"beastMode": False,
|
264 |
+
"customProfile": {
|
265 |
+
"name": "",
|
266 |
+
"occupation": "",
|
267 |
+
"traits": [],
|
268 |
+
"additionalInfo": "",
|
269 |
+
"enableNewChats": False
|
270 |
+
},
|
271 |
+
"webSearchModeOption": {
|
272 |
+
"autoMode": False,
|
273 |
+
"webMode": False,
|
274 |
+
"offlineMode": True
|
275 |
+
},
|
276 |
+
# Insert the session with new id and isNewUser flag
|
277 |
+
"session": {
|
278 |
+
"user": {
|
279 |
+
"name": random_name,
|
280 |
+
"email": random_email,
|
281 |
+
"image": "https://lh3.googleusercontent.com/a/...=s96-c",
|
282 |
+
"subscriptionStatus": "PREMIUM"
|
283 |
+
},
|
284 |
+
"expires": datetime.now(timezone.utc)
|
285 |
+
.isoformat(timespec='milliseconds')
|
286 |
+
.replace('+00:00', 'Z'),
|
287 |
+
"subscriptionCache": {
|
288 |
+
"customerId": random_customer_id,
|
289 |
+
"status": "PREMIUM",
|
290 |
+
"isTrialSubscription": "False",
|
291 |
+
"expiryTimestamp": 1744652408,
|
292 |
+
"lastChecked": int(time.time() * 1000)
|
293 |
+
},
|
294 |
+
"id": session_id,
|
295 |
+
"isNewUser": is_new_user
|
296 |
+
}
|
297 |
}
|
298 |
|
299 |
prompt_tokens = sum(
|
300 |
+
calculate_tokens(msg["content"], request.model)
|
301 |
+
for msg in messages if "content" in msg
|
|
|
302 |
)
|
|
|
303 |
completion_tokens = 0
|
304 |
+
final_snapzion_links: List[str] = []
|
305 |
|
306 |
async with httpx.AsyncClient() as client:
|
307 |
try:
|
308 |
+
async with client.stream(
|
309 |
+
"POST", f"{BASE_URL}/api/chat",
|
310 |
+
headers=headers_api_chat,
|
311 |
+
json=json_data,
|
312 |
+
timeout=100
|
313 |
+
) as response:
|
314 |
+
response.raise_for_status()
|
315 |
+
async for chunk in response.aiter_text():
|
316 |
+
timestamp = int(datetime.now().timestamp())
|
317 |
if not chunk:
|
318 |
continue
|
319 |
if chunk.startswith("$@$v=undefined-rv1$@$"):
|
|
|
322 |
chunk = chunk.replace(BLOCKED_MESSAGE, "").strip()
|
323 |
if not chunk:
|
324 |
continue
|
325 |
+
if "https://storage.googleapis.com" in chunk:
|
326 |
+
chunk = chunk.replace("https://storage.googleapis.com", "https://cdn.snapzion.com")
|
327 |
+
snapzion_urls = re.findall(r"(https://cdn\.snapzion\.com[^\s\)]+)", chunk)
|
328 |
+
final_snapzion_links.extend(snapzion_urls)
|
329 |
+
cleaned = strip_model_prefix(chunk, model_prefix)
|
330 |
+
completion_tokens += calculate_tokens(cleaned, request.model)
|
331 |
yield "data: " + json.dumps(
|
332 |
+
create_chat_completion_data(cleaned, request.model, timestamp,
|
333 |
+
request_id, system_fingerprint,
|
334 |
+
prompt_tokens, completion_tokens)
|
335 |
) + "\n\n"
|
336 |
|
337 |
+
# send final stop event
|
338 |
yield "data: " + json.dumps(
|
339 |
+
create_chat_completion_data(
|
340 |
+
"", request.model, int(datetime.now().timestamp()),
|
341 |
+
request_id, system_fingerprint,
|
342 |
+
prompt_tokens, completion_tokens, "stop"
|
343 |
+
)
|
344 |
) + "\n\n"
|
345 |
yield "data: [DONE]\n\n"
|
346 |
|
347 |
+
except httpx.HTTPStatusError as e:
|
348 |
+
logger.error(f"HTTP error (stream) {request_id}: {e}")
|
349 |
+
error_message = f"HTTP error occurred: {e}"
|
350 |
+
try:
|
351 |
+
details = e.response.json()
|
352 |
+
error_message += f" Details: {details}"
|
353 |
+
except ValueError:
|
354 |
+
error_message += f" Response body: {e.response.text}"
|
355 |
+
yield "data: " + json.dumps(
|
356 |
+
create_chat_completion_data(error_message, request.model,
|
357 |
+
int(datetime.now().timestamp()),
|
358 |
+
request_id, system_fingerprint,
|
359 |
+
prompt_tokens, completion_tokens, "error")
|
360 |
+
) + "\n\n"
|
361 |
+
yield "data: [DONE]\n\n"
|
362 |
+
|
363 |
+
except (httpx.RequestError, Exception) as e:
|
364 |
+
logger.error(f"Request error (stream) {request_id}: {e}")
|
365 |
+
error_message = f"Request error occurred: {e}"
|
366 |
+
yield "data: " + json.dumps(
|
367 |
+
create_chat_completion_data(error_message, request.model,
|
368 |
+
int(datetime.now().timestamp()),
|
369 |
+
request_id, system_fingerprint,
|
370 |
+
prompt_tokens, completion_tokens, "error")
|
371 |
+
) + "\n\n"
|
372 |
+
yield "data: [DONE]\n\n"
|
373 |
+
|
374 |
+
last_user_prompt = get_last_user_prompt(request.messages)
|
375 |
+
upload_replaced_urls_to_r2(final_snapzion_links, alt_text=last_user_prompt)
|
376 |
|
|
|
377 |
|
378 |
# ---------------------------------------------
|
379 |
# NON-STREAMING RESPONSE HANDLER
|
380 |
# ---------------------------------------------
|
381 |
async def process_non_streaming_response(request: ChatRequest):
|
382 |
+
# Generate a fixed-length session ID on each request
|
383 |
+
session_id = str(uuid.uuid4())
|
384 |
+
is_new_user = False
|
385 |
+
|
386 |
+
system_fingerprint = generate_system_fingerprint()
|
387 |
+
random_name, random_email, random_customer_id = get_random_name_email_customer()
|
388 |
request_id = f"chatcmpl-{uuid.uuid4()}"
|
389 |
+
logger.info(f"Processing request (non-stream) {request_id} - Model: {request.model}")
|
390 |
|
391 |
agent_mode = AGENT_MODE.get(request.model, {})
|
392 |
+
trending_agent_mode = TRENDING_AGENT_MODE.get(request.model, {})
|
393 |
+
model_prefix = MODEL_PREFIXES.get(request.model, "")
|
394 |
+
|
395 |
+
headers_api_chat = get_headers_api_chat(BASE_URL)
|
396 |
+
headers_chat = get_headers_chat(
|
397 |
+
BASE_URL,
|
398 |
+
next_action=str(uuid.uuid4()),
|
399 |
+
next_router_state_tree=json.dumps([""])
|
400 |
+
)
|
401 |
if request.model == "o1-preview":
|
402 |
await asyncio.sleep(random.randint(20, 60))
|
403 |
|
404 |
+
# You could also use: h_value = await getHid()
|
405 |
+
h_value = "00f37b34-a166-4efb-bce5-1312d87f2f94"
|
406 |
+
if not h_value:
|
407 |
+
logger.error("Failed to retrieve h-value.")
|
408 |
raise HTTPException(status_code=500, detail="Missing h-value.")
|
409 |
|
410 |
+
messages = [message_to_dict(msg, model_prefix=model_prefix) for msg in request.messages]
|
411 |
|
412 |
json_data = {
|
413 |
"agentMode": agent_mode,
|
|
|
427 |
"isPremium": True,
|
428 |
"isMemoryEnabled": False,
|
429 |
"maxTokens": request.max_tokens,
|
430 |
+
"messages": messages,
|
431 |
"mobileClient": False,
|
432 |
"playgroundTemperature": request.temperature,
|
433 |
"playgroundTopP": request.top_p,
|
434 |
"previewToken": None,
|
435 |
+
"trendingAgentMode": trending_agent_mode,
|
436 |
"userId": None,
|
437 |
"userSelectedModel": MODEL_MAPPING.get(request.model, request.model),
|
438 |
"userSystemPrompt": None,
|
439 |
+
"validated": h_value,
|
440 |
"visitFromDelta": False,
|
441 |
"webSearchModePrompt": False,
|
442 |
"vscodeClient": False,
|
443 |
"designerMode": False,
|
444 |
"workspaceId": "",
|
445 |
"beastMode": False,
|
446 |
+
"customProfile": {
|
447 |
+
"name": "",
|
448 |
+
"occupation": "",
|
449 |
+
"traits": [],
|
450 |
+
"additionalInfo": "",
|
451 |
+
"enableNewChats": False
|
452 |
+
},
|
453 |
+
"webSearchModeOption": {
|
454 |
+
"autoMode": False,
|
455 |
+
"webMode": False,
|
456 |
+
"offlineMode": True
|
457 |
+
},
|
458 |
+
# Insert the session with new id and isNewUser flag
|
459 |
+
"session": {
|
460 |
+
"user": {
|
461 |
+
"name": random_name,
|
462 |
+
"email": random_email,
|
463 |
+
"image": "https://lh3.googleusercontent.com/a/...=s96-c",
|
464 |
+
"subscriptionStatus": "PREMIUM"
|
465 |
+
},
|
466 |
+
"expires": datetime.now(timezone.utc)
|
467 |
+
.isoformat(timespec='milliseconds')
|
468 |
+
.replace('+00:00', 'Z'),
|
469 |
+
"subscriptionCache": {
|
470 |
+
"customerId": random_customer_id,
|
471 |
+
"status": "PREMIUM",
|
472 |
+
"isTrialSubscription": "False",
|
473 |
+
"expiryTimestamp": 1744652408,
|
474 |
+
"lastChecked": int(time.time() * 1000)
|
475 |
+
},
|
476 |
+
"id": session_id,
|
477 |
+
"isNewUser": is_new_user
|
478 |
+
}
|
479 |
}
|
480 |
|
481 |
prompt_tokens = sum(
|
482 |
+
calculate_tokens(msg["content"], request.model)
|
483 |
+
for msg in messages if "content" in msg
|
|
|
484 |
)
|
485 |
|
486 |
+
full_response = ""
|
487 |
+
final_snapzion_links: List[str] = []
|
488 |
|
489 |
async with httpx.AsyncClient() as client:
|
490 |
try:
|
491 |
+
async with client.stream(
|
492 |
+
"POST", f"{BASE_URL}/api/chat",
|
493 |
+
headers=headers_api_chat,
|
494 |
+
json=json_data
|
495 |
+
) as response:
|
496 |
+
response.raise_for_status()
|
497 |
+
async for chunk in response.aiter_text():
|
498 |
+
full_response += chunk
|
499 |
+
except httpx.HTTPStatusError as e:
|
500 |
+
logger.error(f"HTTP error (non-stream) {request_id}: {e}")
|
501 |
+
error_message = f"HTTP error occurred: {e}"
|
502 |
+
try:
|
503 |
+
error_details = e.response.json()
|
504 |
+
error_message += f" Details: {error_details}"
|
505 |
+
except ValueError:
|
506 |
+
error_message += f" Response body: {e.response.text}"
|
507 |
+
return {
|
508 |
+
"id": request_id,
|
509 |
+
"object": "chat.completion",
|
510 |
+
"created": int(datetime.now().timestamp()),
|
511 |
+
"model": request.model,
|
512 |
+
"system_fingerprint": system_fingerprint,
|
513 |
+
"choices": [{
|
514 |
+
"index": 0,
|
515 |
+
"message": {"role": "assistant", "content": error_message},
|
516 |
+
"finish_reason": "error"
|
517 |
+
}],
|
518 |
+
"usage": {"prompt_tokens": prompt_tokens, "completion_tokens": 0, "total_tokens": prompt_tokens}
|
519 |
+
}
|
520 |
except Exception as e:
|
521 |
+
logger.error(f"Unexpected error (non-stream) {request_id}: {e}")
|
522 |
+
error_message = f"An unexpected error occurred: {e}"
|
523 |
return {
|
524 |
"id": request_id,
|
525 |
"object": "chat.completion",
|
526 |
"created": int(datetime.now().timestamp()),
|
527 |
"model": request.model,
|
528 |
+
"system_fingerprint": system_fingerprint,
|
529 |
+
"choices": [{
|
530 |
+
"index": 0,
|
531 |
+
"message": {"role": "assistant", "content": error_message},
|
532 |
+
"finish_reason": "error"
|
533 |
+
}],
|
534 |
+
"usage": {"prompt_tokens": prompt_tokens, "completion_tokens": 0, "total_tokens": prompt_tokens}
|
535 |
}
|
536 |
|
537 |
+
# Post-process response
|
538 |
+
if full_response.startswith("$@$v=undefined-rv1$@$"):
|
539 |
+
full_response = full_response[21:]
|
540 |
+
if BLOCKED_MESSAGE in full_response:
|
541 |
+
full_response = full_response.replace(BLOCKED_MESSAGE, "").strip()
|
542 |
+
if not full_response:
|
543 |
+
raise HTTPException(status_code=500, detail="Blocked message in response.")
|
544 |
+
if "https://storage.googleapis.com" in full_response:
|
545 |
+
full_response = full_response.replace("https://storage.googleapis.com", "https://cdn.snapzion.com")
|
546 |
+
snapzion_urls = re.findall(r"(https://cdn\.snapzion\.com[^\s\)]+)", full_response)
|
547 |
+
final_snapzion_links.extend(snapzion_urls)
|
548 |
+
|
549 |
+
cleaned = strip_model_prefix(full_response, model_prefix)
|
550 |
+
completion_tokens = calculate_tokens(cleaned, request.model)
|
551 |
|
552 |
+
last_user_prompt = get_last_user_prompt(request.messages)
|
553 |
+
upload_replaced_urls_to_r2(final_snapzion_links, alt_text=last_user_prompt)
|
554 |
|
555 |
return {
|
556 |
"id": request_id,
|
557 |
"object": "chat.completion",
|
558 |
"created": int(datetime.now().timestamp()),
|
559 |
"model": request.model,
|
560 |
+
"system_fingerprint": system_fingerprint,
|
561 |
+
"choices": [{
|
562 |
+
"index": 0,
|
563 |
+
"message": {"role": "assistant", "content": cleaned},
|
564 |
+
"finish_reason": "stop"
|
565 |
+
}],
|
566 |
+
"usage": {"prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": prompt_tokens + completion_tokens}
|
567 |
}
|