smgc commited on
Commit
a503c73
1 Parent(s): 2a6dcad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +192 -164
app.py CHANGED
@@ -1,202 +1,230 @@
1
  import os
2
- import json
3
- import uuid
4
  import asyncio
 
 
5
 
6
- from aiohttp import web
 
7
  from socketio import AsyncClient
8
- from aiohttp_socks import ProxyConnector
9
 
10
- # 从环境变量中获取 API 密钥和 Cookie
11
  API_KEY = os.environ.get("PPLX_KEY")
12
  PPLX_COOKIE = os.environ.get("PPLX_COOKIE")
13
  USER_AGENT = os.environ.get("USER_AGENT")
 
 
 
 
14
 
15
- # 代理设置(如果需要)
16
- PROXY_URL = os.environ.get("PROXY_URL") # 例如: socks5://user:pass@host:port
17
- connector = ProxyConnector.from_url(PROXY_URL) if PROXY_URL else None
18
-
19
- # Socket.IO 客户端选项
20
- opts = {
21
- "auth": {"jwt": "anonymous-ask-user"},
22
- "reconnection": False,
23
- "transports": ["websocket"],
24
- "path": "/socket.io",
25
- "hostname": "www.perplexity.ai",
26
- "secure": True,
27
- "port": "443",
28
- "extra_headers": {
29
- "Cookie": PPLX_COOKIE,
30
- "User-Agent": USER_AGENT,
31
- "Accept": "*/*",
32
- "priority": "u=1, i",
33
- "Referer": "https://www.perplexity.ai/",
34
- },
35
- }
36
-
37
- # 创建 aiohttp 应用
38
- app = web.Application()
39
-
40
- # API 密钥验证中间件
41
- @web.middleware
42
- async def validate_api_key(request, handler):
43
  api_key = request.headers.get("x-api-key")
44
  if api_key != API_KEY:
45
  log_request(request, 401)
46
- return web.json_response({"error": "无效的 API 密钥"}, status=401)
47
- return await handler(request)
 
 
 
 
48
 
49
  # 日志记录函数
50
- def log_request(request, status):
51
- timestamp = datetime.now().isoformat()
52
- ip = request.remote or request.transport.get_extra_info("peername")[0]
53
- route = request.path
54
- print(f"{timestamp} - {ip} - {route} - {status}")
55
 
56
 
57
  # 根路由处理
58
- async def root_handler(request):
 
59
  log_request(request, 200)
60
- return web.json_response(
61
- {
62
- "message": "欢迎使用 Perplexity AI 代理 API",
63
- "endpoints": {
64
- "/ai/v1/messages": {
65
- "method": "POST",
66
- "description": "向 AI 发送消息",
67
- "headers": {
68
- "x-api-key": "你的 API 密钥(必需)",
69
- "Content-Type": "application/json",
70
- },
71
- "body": {
72
- "messages": "消息对象数组",
73
- "stream": "布尔值(true 表示流式响应)",
74
- # 其他可能的参数...
75
- },
76
- }
77
- },
78
- }
79
- )
80
-
81
- # 创建事件流数据
82
- def create_event(event, data):
83
- if isinstance(data, dict):
84
- data = json.dumps(data)
85
- return f"event: {event}\ndata: {data}\n\n"
86
-
87
-
88
- # AI 消息处理
89
- @web.post("/ai/v1/messages")
90
- @validate_api_key
91
- async def ai_messages_handler(request):
92
  try:
93
  json_body = await request.json()
94
- if json_body.get("stream") is False:
95
  log_request(request, 200)
96
- return web.json_response(
97
- {
98
- "id": str(uuid.uuid4()),
99
- "content": [
100
- {"text": "请打开流式传输。"},
101
- {"id": "string", "name": "string", "input": {}},
102
- ],
103
- "model": "string",
104
- "stop_reason": "end_turn",
105
- "stop_sequence": "string",
106
- "usage": {"input_tokens": 0, "output_tokens": 0},
107
- }
108
- )
109
- elif json_body.get("stream") is True:
110
- # 处理消息... (与 JavaScript 版本类似)
111
- previous_messages = "\n\n".join([msg.get("content", "") for msg in json_body.get("messages", [])])
112
- msgid = str(uuid.uuid4())
113
-
114
- response = web.StreamResponse(
115
- status=200,
116
- reason="OK",
117
- headers={"Content-Type": "text/event-stream;charset=utf-8"},
118
- )
119
- await response.prepare(request)
120
-
121
- await response.write(
122
- create_event(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  "message_start",
124
  {
125
  "type": "message_start",
126
  "message": {
127
  "id": msgid,
128
- # ... (其余部分与 JavaScript 版本类似)
 
 
 
 
 
 
129
  },
130
  },
131
- ).encode()
132
- )
133
-
134
- # 使用 AsyncClient 连接到 Socket.IO 服务器
135
- async with AsyncClient(connector=connector) as socket:
136
- await socket.connect("wss://www.perplexity.ai/", **opts)
137
-
138
- # 发送 perplexity_ask 事件 (需要根据Perplexity.ai的API调整)
139
- await socket.emit("perplexity_ask", previous_messages)
140
-
141
-
142
- # 处理 Socket.IO 事件... (与 JavaScript 版本类似)
143
-
144
- # ...处理 query_progress, disconnect, error, connect_error 等事件,并写入 response
145
-
146
-
147
- @socket.on("query_progress")
148
- async def on_query_progress(data):
149
- if data.get("text"):
150
- text = json.loads(data["text"])
151
- chunk = text["chunks"][-1] if text.get("chunks") else None
152
- if chunk:
153
- await response.write(create_event("content_block_delta", { # ...}).encode())
154
-
155
-
156
- # ... other event handlers
157
-
158
- return response
159
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  else:
161
- raise ValueError("无效的请求")
162
-
163
-
164
- except (json.JSONDecodeError, ValueError, Exception) as e:
165
- console.log(e) # debug
166
  log_request(request, 400)
167
- return web.json_response({"error": str(e)}, status=400)
168
 
169
 
 
 
 
 
 
 
170
 
171
- # 处理其他路由
172
- async def not_found_handler(request):
173
- log_request(request, 404)
174
- return web.Response(text="未找到", status=404)
175
 
176
- # 错误处理中间件
177
- @web.middleware
178
- async def error_middleware(request, handler):
179
- try:
180
- return await handler(request)
181
- except Exception as err:
182
- print(err, traceback.format_exc()) # debug
183
- log_request(request, 500)
184
- return web.Response(text="服务器错误", status=500)
185
-
186
- # 添加路由和中间件
187
- app.add_routes([web.get("/", root_handler), web.post("/ai/v1/messages", ai_messages_handler)])
188
- app.add_routes([web.static("/static", "static")])
189
- app.middleware.append(error_middleware)
190
- app.router.add_route("*", "/{path_info:.*}", not_found_handler)
191
-
192
- # 启动服务器
193
  if __name__ == "__main__":
194
-
195
- port = int(os.environ.get("PORT", 8081))
196
 
197
  if not API_KEY:
198
- print("警告:未设置 PPLX_KEY 环境变量。API 密钥验证将失败。")
199
-
200
- web.run_app(app, port=port)
201
-
202
 
 
1
  import os
 
 
2
  import asyncio
3
+ import uuid
4
+ import json
5
 
6
+ from fastapi import FastAPI, HTTPException, Request
7
+ from fastapi.responses import StreamingResponse
8
  from socketio import AsyncClient
 
9
 
10
+ # 从环境变量中获取 API 密钥
11
  API_KEY = os.environ.get("PPLX_KEY")
12
  PPLX_COOKIE = os.environ.get("PPLX_COOKIE")
13
  USER_AGENT = os.environ.get("USER_AGENT")
14
+ # 从环境变量中获取代理地址
15
+ PROXY_URL = os.environ.get("PROXY_URL")
16
+
17
+ app = FastAPI()
18
 
19
+ # 添加中间件来验证 API 密钥
20
+ async def validate_api_key(request: Request, call_next):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  api_key = request.headers.get("x-api-key")
22
  if api_key != API_KEY:
23
  log_request(request, 401)
24
+ raise HTTPException(status_code=401, detail="Invalid API key")
25
+ response = await call_next(request)
26
+ return response
27
+
28
+ app.middleware("http")(validate_api_key)
29
+
30
 
31
  # 日志记录函数
32
+ def log_request(request: Request, status_code: int):
33
+ timestamp = datetime.datetime.now().isoformat()
34
+ ip = request.client.host
35
+ route = request.url.path
36
+ print(f"{timestamp} - {ip} - {route} - {status_code}")
37
 
38
 
39
  # 根路由处理
40
+ @app.get("/")
41
+ async def root(request: Request):
42
  log_request(request, 200)
43
+ return {
44
+ "message": "Welcome to the Perplexity AI Proxy API",
45
+ "endpoints": {
46
+ "/ai/v1/messages": {
47
+ "method": "POST",
48
+ "description": "Send a message to the AI",
49
+ "headers": {
50
+ "x-api-key": "Your API key (required)",
51
+ "Content-Type": "application/json",
52
+ },
53
+ "body": {
54
+ "messages": "Array of message objects",
55
+ "stream": "Boolean (true for streaming response)",
56
+ # 其他可能的参数...
57
+ },
58
+ }
59
+ },
60
+ }
61
+
62
+
63
+ # AI 消息处理
64
+ @app.post("/ai/v1/messages")
65
+ async def handle_ai_message(request: Request):
 
 
 
 
 
 
 
 
 
66
  try:
67
  json_body = await request.json()
68
+ if not json_body.get("stream"):
69
  log_request(request, 200)
70
+ return {
71
+ "id": str(uuid.uuid4()),
72
+ "content": [
73
+ {"text": "Please turn on streaming."},
74
+ {"id": "string", "name": "string", "input": {}},
75
+ ],
76
+ "model": "string",
77
+ "stop_reason": "end_turn",
78
+ "stop_sequence": "string",
79
+ "usage": {"input_tokens": 0, "output_tokens": 0},
80
+ }
81
+ elif json_body.get("stream"):
82
+ async def event_stream(json_body):
83
+ # 计算用户消息长度
84
+ user_message = [{"question": "", "answer": ""}]
85
+ last_update = True
86
+ if json_body.get("system"):
87
+ # 把系统消息加入 messages 的首条
88
+ json_body["messages"].insert(0, {"role": "system", "content": json_body.get("system")})
89
+ for msg in json_body.get("messages", []):
90
+ if msg["role"] in ("system", "user"):
91
+ if last_update:
92
+ user_message[-1]["question"] += msg["content"] + "\n"
93
+ elif not user_message[-1]["question"]:
94
+ user_message[-1]["question"] += msg["content"] + "\n"
95
+ else:
96
+ user_message.append({"question": msg["content"] + "\n", "answer": ""})
97
+ last_update = True
98
+ elif msg["role"] == "assistant":
99
+ if not last_update:
100
+ user_message[-1]["answer"] += msg["content"] + "\n"
101
+ elif not user_message[-1]["answer"]:
102
+ user_message[-1]["answer"] += msg["content"] + "\n"
103
+ else:
104
+ user_message.append({"question": "", "answer": msg["content"] + "\n"})
105
+ last_update = False
106
+
107
+ # user message to plaintext
108
+ previous_messages = "\n\n".join([msg["content"] for msg in json_body.get("messages", [])])
109
+ msgid = str(uuid.uuid4())
110
+ # send message start
111
+ yield create_event(
112
  "message_start",
113
  {
114
  "type": "message_start",
115
  "message": {
116
  "id": msgid,
117
+ "type": "message",
118
+ "role": "assistant",
119
+ "content": [],
120
+ "model": "claude-3-opus-20240229",
121
+ "stop_reason": None,
122
+ "stop_sequence": None,
123
+ "usage": {"input_tokens": 8, "output_tokens": 1},
124
  },
125
  },
126
+ )
127
+ yield create_event(
128
+ "content_block_start",
129
+ {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}},
130
+ )
131
+ yield create_event("ping", {"type": "ping"})
132
+
133
+ # 设置代理
134
+ engineio_kwargs = {}
135
+ if PROXY_URL:
136
+ engineio_kwargs = {"http_proxy": PROXY_URL, "https_proxy": PROXY_URL}
137
+
138
+ # proxy response
139
+ async with AsyncClient(logger=True, engineio_logger=True, **engineio_kwargs) as socket:
140
+ try:
141
+ await socket.connect(
142
+ "https://www.perplexity.ai/",
143
+ headers={
144
+ "Cookie": PPLX_COOKIE,
145
+ "User-Agent": USER_AGENT,
146
+ "Accept": "*/*",
147
+ "priority": "u=1, i",
148
+ "Referer": "https://www.perplexity.ai/",
149
+ },
150
+ transports=["websocket"],
151
+ )
152
+ print(" > [Connected]")
153
+ await socket.emit(
154
+ "perplexity_ask",
155
+ previous_messages,
156
+ {
157
+ "version": "2.9",
158
+ "source": "default",
159
+ "attachments": [],
160
+ "language": "en-GB",
161
+ "timezone": "Europe/London",
162
+ "search_focus": "writing",
163
+ "frontend_uuid": str(uuid.uuid4()),
164
+ "mode": "concise",
165
+ "is_related_query": False,
166
+ "is_default_related_query": False,
167
+ "visitor_id": str(uuid.uuid4()),
168
+ "frontend_context_uuid": str(uuid.uuid4()),
169
+ "prompt_source": "user",
170
+ "query_source": "home",
171
+ },
172
+ )
173
+ response = await socket.wait()
174
+ print(response)
175
+ yield create_event("content_block_stop", {"type": "content_block_stop", "index": 0})
176
+ yield create_event(
177
+ "message_delta",
178
+ {
179
+ "type": "message_delta",
180
+ "delta": {"stop_reason": "end_turn", "stop_sequence": None},
181
+ "usage": {"output_tokens": 12},
182
+ },
183
+ )
184
+ yield create_event("message_stop", {"type": "message_stop"})
185
+ log_request(request, 200)
186
+ except Exception as e:
187
+ print(e)
188
+ log_request(request, 500)
189
+ finally:
190
+ await socket.disconnect()
191
+
192
+ @socket.on("query_progress")
193
+ async def on_query_progress(data):
194
+ if data.get("text"):
195
+ text = json.loads(data["text"])
196
+ chunk = text["chunks"][-1] if text.get("chunks") else None
197
+ if chunk:
198
+ yield create_event(
199
+ "content_block_delta",
200
+ {
201
+ "type": "content_block_delta",
202
+ "index": 0,
203
+ "delta": {"type": "text_delta", "text": chunk},
204
+ },
205
+ )
206
+
207
+ return StreamingResponse(event_stream(json_body), media_type="text/event-stream;charset=utf-8")
208
  else:
209
+ raise HTTPException(status_code=400, detail="Invalid request")
210
+ except Exception as e:
211
+ print(e)
 
 
212
  log_request(request, 400)
213
+ raise HTTPException(status_code=400, detail=str(e))
214
 
215
 
216
+ # eventStream util
217
+ def create_event(event: str, data: dict):
218
+ # if data is object, stringify it
219
+ if isinstance(data, dict):
220
+ data = json.dumps(data)
221
+ return f"event: {event}\ndata: {data}\n\n"
222
 
 
 
 
 
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  if __name__ == "__main__":
225
+ import uvicorn
 
226
 
227
  if not API_KEY:
228
+ print("Warning: PPLX_KEY environment variable is not set. API key validation will fail.")
229
+ uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8081)))
 
 
230