Niansuh commited on
Commit
43cdeef
·
verified ·
1 Parent(s): 0f2dcc6

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +114 -160
main.py CHANGED
@@ -7,99 +7,102 @@ from typing import Any, Dict, List, Optional
7
 
8
  import httpx
9
  import uvicorn
10
- from fastapi import FastAPI, HTTPException, Depends, Request, status
 
11
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
12
  from pydantic import BaseModel
13
  from starlette.middleware.cors import CORSMiddleware
14
  from starlette.responses import StreamingResponse, Response
15
 
16
- from dotenv import load_dotenv
17
-
18
- # Retry Mechanism Libraries
19
- from tenacity import retry, wait_exponential, stop_after_attempt, retry_if_exception_type
20
-
21
- # Initialize Logging
22
  logging.basicConfig(
23
  level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
24
  )
25
  logger = logging.getLogger(__name__)
26
 
27
- # Load Environment Variables for Sensitive Information
28
  load_dotenv()
29
-
30
  app = FastAPI()
31
-
32
- # Configuration Constants (Hardcoded as per requirements)
33
- APP_NAME = "ChitChat_Chrome_Ext"
34
- APP_VERSION = "4.28.0"
35
- ORIGIN_URL = "chrome-extension://difoiogjjojoaoomphldepapgpbgkhkb/standalone.html?from=sidebar"
36
- ORIGIN_TITLE = "Sider"
37
- TZ_NAME = "Asia/Karachi"
38
- PROMPT_TEMPLATE_KEY = "artifacts"
39
- PROMPT_TEMPLATE_LANG = "original"
40
- TOOLS_AUTO = ["search", "text_to_image", "data_analysis"]
41
-
42
- # Security Configuration
43
- APP_SECRET = os.getenv("APP_SECRET", "666")
44
- ACCESS_TOKEN = os.getenv("SD_ACCESS_TOKEN", "")
45
- if not ACCESS_TOKEN:
46
- logger.error("SD_ACCESS_TOKEN is not set in the environment variables.")
47
- raise RuntimeError("SD_ACCESS_TOKEN is required but not set.")
48
-
49
- # Outgoing Request Headers (As per provided sample)
50
- OUTGOING_HEADERS = {
51
  'accept': '*/*',
52
- 'accept-encoding': 'gzip, deflate, br, zstd',
53
- 'accept-language': 'en-US,en;q=0.9',
54
  'authorization': f'Bearer {ACCESS_TOKEN}',
55
- 'content-type': 'application/json',
56
- 'origin': 'chrome-extension://difoiogjjojoaoomphldepapgpbgkhkb',
 
57
  'priority': 'u=1, i',
58
  'sec-fetch-dest': 'empty',
59
  'sec-fetch-mode': 'cors',
60
  'sec-fetch-site': 'none',
61
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
62
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
63
- 'Chrome/130.0.0.0 Safari/537.36',
64
  }
65
 
66
- # Updated ALLOWED_MODELS List (No Duplicates)
67
  ALLOWED_MODELS = [
68
- {"id": "claude-3.5-sonnet", "name": "Claude 3.5 Sonnet"},
69
- {"id": "sider", "name": "Sider"},
70
- {"id": "gpt-4o-mini", "name": "GPT-4o Mini"},
71
- {"id": "claude-3-haiku", "name": "Claude 3 Haiku"},
72
- {"id": "claude-3.5-haiku", "name": "Claude 3.5 Haiku"},
73
- {"id": "gemini-1.5-flash", "name": "Gemini 1.5 Flash"},
74
- {"id": "llama-3", "name": "Llama 3"},
75
- {"id": "gpt-4o", "name": "GPT-4o"},
76
- {"id": "gemini-1.5-pro", "name": "Gemini 1.5 Pro"},
77
- {"id": "llama-3.1-405b", "name": "Llama 3.1 405b"},
78
  ]
79
-
80
  # Configure CORS
81
  app.add_middleware(
82
  CORSMiddleware,
83
- allow_origins=["*"], # ⚠️ IMPORTANT: Restrict this to specific origins in production for security
84
  allow_credentials=True,
85
- allow_methods=["*"], # Allow all HTTP methods
86
  allow_headers=["*"], # Allow all headers
87
  )
88
-
89
- # Security Dependency
90
  security = HTTPBearer()
91
 
92
- # Pydantic Models
93
  class Message(BaseModel):
94
  role: str
95
  content: str
96
 
 
97
  class ChatRequest(BaseModel):
98
  model: str
99
  messages: List[Message]
100
  stream: Optional[bool] = False
101
 
102
- # Utility Functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  def create_chat_completion_data(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
104
  return {
105
  "id": f"chatcmpl-{uuid.uuid4()}",
@@ -116,35 +119,13 @@ def create_chat_completion_data(content: str, model: str, finish_reason: Optiona
116
  "usage": None,
117
  }
118
 
 
119
  def verify_app_secret(credentials: HTTPAuthorizationCredentials = Depends(security)):
120
  if credentials.credentials != APP_SECRET:
121
- logger.warning(f"Invalid APP_SECRET provided: {credentials.credentials}")
122
  raise HTTPException(status_code=403, detail="Invalid APP_SECRET")
123
- logger.info("APP_SECRET verified successfully.")
124
  return credentials.credentials
125
 
126
- # Retry Configuration using Tenacity
127
- def is_retryable_exception(exception):
128
- return isinstance(exception, httpx.HTTPStatusError) and exception.response.status_code == 429
129
-
130
- @retry(
131
- retry=retry_if_exception_type(httpx.HTTPStatusError) & is_retryable_exception,
132
- wait=wait_exponential(multiplier=1, min=2, max=10),
133
- stop=stop_after_attempt(5),
134
- reraise=True
135
- )
136
- async def send_request_with_retry(json_data: Dict[str, Any]) -> httpx.Response:
137
- async with httpx.AsyncClient() as client:
138
- response = await client.post(
139
- 'https://sider.ai/api/v3/completion/text', # Updated endpoint
140
- headers=OUTGOING_HEADERS,
141
- json=json_data,
142
- timeout=120.0
143
- )
144
- response.raise_for_status()
145
- return response
146
 
147
- # CORS Preflight Options Endpoint
148
  @app.options("/hf/v1/chat/completions")
149
  async def chat_completions_options():
150
  return Response(
@@ -156,130 +137,102 @@ async def chat_completions_options():
156
  },
157
  )
158
 
159
- # List Available Models
 
 
 
 
160
  @app.get("/hf/v1/models")
161
  async def list_models():
162
  return {"object": "list", "data": ALLOWED_MODELS}
163
 
164
- # Chat Completions Endpoint
165
  @app.post("/hf/v1/chat/completions")
166
  async def chat_completions(
167
- request: ChatRequest, app_secret: str = Depends(verify_app_secret), req: Request = None
168
  ):
169
- client_ip = req.client.host if req else "unknown"
170
- logger.info(f"Received chat completion request from {client_ip} for model: {request.model}")
171
 
172
- # Validate Selected Model
173
  if request.model not in [model['id'] for model in ALLOWED_MODELS]:
174
- allowed = ', '.join(model['id'] for model in ALLOWED_MODELS)
175
- logger.error(f"Model '{request.model}' is not allowed.")
176
  raise HTTPException(
177
  status_code=400,
178
- detail=f"Model '{request.model}' is not allowed. Allowed models are: {allowed}",
179
  )
 
 
 
180
 
181
- logger.info(f"Using model: {request.model}")
182
-
183
- # Generate a unique CID for each request
184
- cid = str(uuid.uuid4()).replace("-", "").upper()[:12] # Example: C0MES13070J1
185
- logger.debug(f"Generated CID: {cid}")
186
-
187
- # Prepare JSON Payload for External API
188
- if not request.messages:
189
- prompt_text = "make a dog"
190
- else:
191
- prompt_text = "\n".join(
192
  [
193
  f"{'User' if msg.role == 'user' else 'Assistant'}: {msg.content}"
194
  for msg in request.messages
195
  ]
196
- )
197
-
198
- json_data = {
199
- 'prompt': prompt_text,
200
- 'stream': request.stream,
201
- 'app_name': APP_NAME,
202
- 'app_version': APP_VERSION,
203
- 'tz_name': TZ_NAME,
204
- 'cid': cid,
205
  'model': request.model,
206
  'search': False,
207
  'auto_search': False,
208
  'filter_search_history': False,
209
  'from': 'chat',
210
  'group_id': 'default',
211
- 'chat_models': [], # As per the sample payload
212
  'files': [],
213
  'prompt_template': {
214
- 'key': PROMPT_TEMPLATE_KEY,
215
  'attributes': {
216
- 'lang': PROMPT_TEMPLATE_LANG,
217
  },
218
  },
219
  'tools': {
220
- 'auto': TOOLS_AUTO,
 
 
 
 
221
  },
222
  'extra_info': {
223
- 'origin_url': ORIGIN_URL,
224
- 'origin_title': ORIGIN_TITLE,
225
  },
226
  }
227
 
228
- logger.debug(f"JSON Data Sent to External API: {json.dumps(json_data, indent=2)}")
229
-
230
- try:
231
- response = await send_request_with_retry(json_data)
232
- except httpx.HTTPStatusError as e:
233
- status_code = e.response.status_code
234
- if status_code == 429:
235
- retry_after = e.response.headers.get("Retry-After", "60")
236
- logger.warning(f"Rate limited by Sider AI. Retry after {retry_after} seconds.")
237
- raise HTTPException(
238
- status_code=429,
239
- detail=f"Rate limited by external service. Please retry after {retry_after} seconds."
240
- )
241
- else:
242
- logger.error(f"HTTP error occurred: {e} - Response: {e.response.text}")
243
- raise HTTPException(status_code=status_code, detail=str(e))
244
- except httpx.RequestError as e:
245
- logger.error(f"An error occurred while requesting: {e}")
246
- raise HTTPException(status_code=500, detail=str(e))
247
-
248
  async def generate():
249
- async for line in response.aiter_lines():
250
- if line and ("[DONE]" not in line):
251
- # Assuming the line starts with 'data: ' followed by JSON
252
- if line.startswith("data: "):
253
- json_line = line[6:]
254
- if json_line.startswith("{"):
255
- try:
256
- data = json.loads(json_line)
257
- content = data.get("data", {}).get("text", "")
258
- logger.debug(f"Received content: {content}")
259
- yield f"data: {json.dumps(create_chat_completion_data(content, request.model))}\n\n"
260
- except json.JSONDecodeError as e:
261
- logger.error(f"JSON decode error: {e} - Line: {json_line}")
262
- # Send the stop signal
263
- yield f"data: {json.dumps(create_chat_completion_data('', request.model, 'stop'))}\n\n"
264
- yield "data: [DONE]\n\n"
265
 
266
  if request.stream:
267
- logger.info("Streaming response initiated.")
268
  return StreamingResponse(generate(), media_type="text/event-stream")
269
  else:
270
- logger.info("Non-streaming response initiated.")
271
  full_response = ""
272
  async for chunk in generate():
273
  if chunk.startswith("data: ") and not chunk[6:].startswith("[DONE]"):
274
- # Parse the JSON part after 'data: '
275
- try:
276
- data = json.loads(chunk[6:])
277
- if data["choices"][0]["delta"].get("content"):
278
- full_response += data["choices"][0]["delta"]["content"]
279
- except json.JSONDecodeError:
280
- logger.warning(f"Failed to decode JSON from chunk: {chunk}")
281
-
282
- # Final Response Structure
283
  return {
284
  "id": f"chatcmpl-{uuid.uuid4()}",
285
  "object": "chat.completion",
@@ -295,6 +248,7 @@ async def chat_completions(
295
  "usage": None,
296
  }
297
 
298
- # Entry Point
 
299
  if __name__ == "__main__":
300
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
7
 
8
  import httpx
9
  import uvicorn
10
+ from dotenv import load_dotenv
11
+ from fastapi import FastAPI, HTTPException, Depends
12
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
13
  from pydantic import BaseModel
14
  from starlette.middleware.cors import CORSMiddleware
15
  from starlette.responses import StreamingResponse, Response
16
 
 
 
 
 
 
 
17
  logging.basicConfig(
18
  level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
19
  )
20
  logger = logging.getLogger(__name__)
21
 
 
22
  load_dotenv()
 
23
  app = FastAPI()
24
+ BASE_URL = "https://aichatonlineorg.erweima.ai/aichatonline"
25
+ APP_SECRET = os.getenv("APP_SECRET","666")
26
+ ACCESS_TOKEN = os.getenv("SD_ACCESS_TOKEN","")
27
+ headers = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  'accept': '*/*',
29
+ 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
 
30
  'authorization': f'Bearer {ACCESS_TOKEN}',
31
+ 'cache-control': 'no-cache',
32
+ 'origin': 'chrome-extension://dhoenijjpgpeimemopealfcbiecgceod',
33
+ 'pragma': 'no-cache',
34
  'priority': 'u=1, i',
35
  'sec-fetch-dest': 'empty',
36
  'sec-fetch-mode': 'cors',
37
  'sec-fetch-site': 'none',
38
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
 
 
39
  }
40
 
 
41
  ALLOWED_MODELS = [
42
+ {"id": "claude-3.5-sonnet", "name": "claude-3.5-sonnet"},
43
+ {"id": "claude-3-opus", "name": "claude-3-opus"},
44
+ {"id": "gemini-1.5-pro", "name": "gemini-1.5-pro"},
45
+ {"id": "gpt-4o", "name": "gpt-4o"},
46
+ {"id": "o1-preview", "name": "o1-preview"},
47
+ {"id": "o1-mini", "name": "o1-mini"},
48
+ {"id": "gpt-4o-mini", "name": "gpt-4o-mini"},
 
 
 
49
  ]
 
50
  # Configure CORS
51
  app.add_middleware(
52
  CORSMiddleware,
53
+ allow_origins=["*"], # Allow all sources, you can restrict specific sources if needed
54
  allow_credentials=True,
55
+ allow_methods=["*"], # All methods allowed
56
  allow_headers=["*"], # Allow all headers
57
  )
 
 
58
  security = HTTPBearer()
59
 
60
+
61
  class Message(BaseModel):
62
  role: str
63
  content: str
64
 
65
+
66
  class ChatRequest(BaseModel):
67
  model: str
68
  messages: List[Message]
69
  stream: Optional[bool] = False
70
 
71
+
72
+ def simulate_data(content, model):
73
+ return {
74
+ "id": f"chatcmpl-{uuid.uuid4()}",
75
+ "object": "chat.completion.chunk",
76
+ "created": int(datetime.now().timestamp()),
77
+ "model": model,
78
+ "choices": [
79
+ {
80
+ "index": 0,
81
+ "delta": {"content": content, "role": "assistant"},
82
+ "finish_reason": None,
83
+ }
84
+ ],
85
+ "usage": None,
86
+ }
87
+
88
+
89
+ def stop_data(content, model):
90
+ return {
91
+ "id": f"chatcmpl-{uuid.uuid4()}",
92
+ "object": "chat.completion.chunk",
93
+ "created": int(datetime.now().timestamp()),
94
+ "model": model,
95
+ "choices": [
96
+ {
97
+ "index": 0,
98
+ "delta": {"content": content, "role": "assistant"},
99
+ "finish_reason": "stop",
100
+ }
101
+ ],
102
+ "usage": None,
103
+ }
104
+
105
+
106
  def create_chat_completion_data(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
107
  return {
108
  "id": f"chatcmpl-{uuid.uuid4()}",
 
119
  "usage": None,
120
  }
121
 
122
+
123
  def verify_app_secret(credentials: HTTPAuthorizationCredentials = Depends(security)):
124
  if credentials.credentials != APP_SECRET:
 
125
  raise HTTPException(status_code=403, detail="Invalid APP_SECRET")
 
126
  return credentials.credentials
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
 
129
  @app.options("/hf/v1/chat/completions")
130
  async def chat_completions_options():
131
  return Response(
 
137
  },
138
  )
139
 
140
+
141
+ def replace_escaped_newlines(input_string: str) -> str:
142
+ return input_string.replace("\\n", "\n")
143
+
144
+
145
  @app.get("/hf/v1/models")
146
  async def list_models():
147
  return {"object": "list", "data": ALLOWED_MODELS}
148
 
149
+
150
  @app.post("/hf/v1/chat/completions")
151
  async def chat_completions(
152
+ request: ChatRequest, app_secret: str = Depends(verify_app_secret)
153
  ):
154
+ logger.info(f"Received chat completion request for model: {request.model}")
 
155
 
 
156
  if request.model not in [model['id'] for model in ALLOWED_MODELS]:
 
 
157
  raise HTTPException(
158
  status_code=400,
159
+ detail=f"Model {request.model} is not allowed. Allowed models are: {', '.join(model['id'] for model in ALLOWED_MODELS)}",
160
  )
161
+ # Generate a UUID
162
+ original_uuid = uuid.uuid4()
163
+ uuid_str = str(original_uuid).replace("-", "")
164
 
165
+ # Using the OpenAI API
166
+ json_data = {
167
+ 'prompt': "\n".join(
 
 
 
 
 
 
 
 
168
  [
169
  f"{'User' if msg.role == 'user' else 'Assistant'}: {msg.content}"
170
  for msg in request.messages
171
  ]
172
+ ),
173
+ 'stream': True,
174
+ 'app_name': 'ChitChat_Edge_Ext',
175
+ 'app_version': '4.26.1',
176
+ 'tz_name': 'Asia/Karachi',
177
+ 'cid': '',
 
 
 
178
  'model': request.model,
179
  'search': False,
180
  'auto_search': False,
181
  'filter_search_history': False,
182
  'from': 'chat',
183
  'group_id': 'default',
184
+ 'chat_models': [],
185
  'files': [],
186
  'prompt_template': {
187
+ 'key': '',
188
  'attributes': {
189
+ 'lang': 'original',
190
  },
191
  },
192
  'tools': {
193
+ 'auto': [
194
+ 'search',
195
+ 'text_to_image',
196
+ 'data_analysis',
197
+ ],
198
  },
199
  'extra_info': {
200
+ 'origin_url': '',
201
+ 'origin_title': '',
202
  },
203
  }
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  async def generate():
206
+ async with httpx.AsyncClient() as client:
207
+ try:
208
+ async with client.stream('POST', 'https://sider.ai/api/v3/completion/text', headers=headers, json=json_data, timeout=120.0) as response:
209
+ response.raise_for_status()
210
+ async for line in response.aiter_lines():
211
+ if line and ("[DONE]" not in line):
212
+ content = json.loads(line[5:])["data"]
213
+ yield f"data: {json.dumps(create_chat_completion_data(content.get('text',''), request.model))}\n\n"
214
+ yield f"data: {json.dumps(create_chat_completion_data('', request.model, 'stop'))}\n\n"
215
+ yield "data: [DONE]\n\n"
216
+ except httpx.HTTPStatusError as e:
217
+ logger.error(f"HTTP error occurred: {e}")
218
+ raise HTTPException(status_code=e.response.status_code, detail=str(e))
219
+ except httpx.RequestError as e:
220
+ logger.error(f"An error occurred while requesting: {e}")
221
+ raise HTTPException(status_code=500, detail=str(e))
222
 
223
  if request.stream:
224
+ logger.info("Streaming response")
225
  return StreamingResponse(generate(), media_type="text/event-stream")
226
  else:
227
+ logger.info("Non-streaming response")
228
  full_response = ""
229
  async for chunk in generate():
230
  if chunk.startswith("data: ") and not chunk[6:].startswith("[DONE]"):
231
+ # print(chunk)
232
+ data = json.loads(chunk[6:])
233
+ if data["choices"][0]["delta"].get("content"):
234
+ full_response += data["choices"][0]["delta"]["content"]
235
+
 
 
 
 
236
  return {
237
  "id": f"chatcmpl-{uuid.uuid4()}",
238
  "object": "chat.completion",
 
248
  "usage": None,
249
  }
250
 
251
+
252
+
253
  if __name__ == "__main__":
254
  uvicorn.run(app, host="0.0.0.0", port=7860)