smgc commited on
Commit
096dbd4
1 Parent(s): 40fd95a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -189
app.py CHANGED
@@ -1,46 +1,64 @@
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": {
@@ -48,183 +66,183 @@ async def root(request: Request):
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
-
 
1
  import os
 
 
2
  import json
3
+ import uuid
4
+ import asyncio
5
+ import aiohttp
6
+ from aiohttp import web
7
+ from datetime import datetime
8
+ import socketio
9
+
10
+ # 从环境变量中获取API密钥
11
+ API_KEY = os.getenv('PPLX_KEY')
12
+ PROXY = os.getenv('HTTP_PROXY') # 新增:从环境变量获取代理设置
13
+
14
+ sio = socketio.AsyncClient()
15
+
16
+ # 配置选项
17
+ opts = {
18
+ "auth": {
19
+ "jwt": "anonymous-ask-user",
20
+ },
21
+ "reconnection": False,
22
+ "transports": ["websocket"],
23
+ "path": "/socket.io",
24
+ "hostname": "www.perplexity.ai",
25
+ "secure": True,
26
+ "port": "443",
27
+ "extraHeaders": {
28
+ "Cookie": os.getenv('PPLX_COOKIE'),
29
+ "User-Agent": os.getenv('USER_AGENT'),
30
+ "Accept": "*/*",
31
+ "priority": "u=1, i",
32
+ "Referer": "https://www.perplexity.ai/",
33
+ }
34
+ }
35
 
36
+ # API密钥验证中间件
37
+ @web.middleware
38
+ async def validate_api_key(request, handler):
39
+ api_key = request.headers.get('x-api-key')
 
 
 
 
 
 
 
 
 
 
 
 
40
  if api_key != API_KEY:
41
  log_request(request, 401)
42
+ return web.json_response({"error": "Invalid API key"}, status=401)
43
+ return await handler(request)
 
 
 
 
44
 
45
  # 日志记录函数
46
+ def log_request(request, status):
47
+ timestamp = datetime.now().isoformat()
48
+ ip = request.remote
49
+ route = request.path
50
+ print(f"{timestamp} - {ip} - {route} - {status}")
51
+
52
+ # 创建事件流工具函数
53
+ def create_event(event, data):
54
+ if isinstance(data, dict):
55
+ data = json.dumps(data)
56
+ return f"event: {event}\ndata: {data}\n\n"
57
 
58
+ # 路由处理函数
59
+ async def handle_root(request):
 
60
  log_request(request, 200)
61
+ return web.json_response({
62
  "message": "Welcome to the Perplexity AI Proxy API",
63
  "endpoints": {
64
  "/ai/v1/messages": {
 
66
  "description": "Send a message to the AI",
67
  "headers": {
68
  "x-api-key": "Your API key (required)",
69
+ "Content-Type": "application/json"
70
  },
71
  "body": {
72
  "messages": "Array of message objects",
73
  "stream": "Boolean (true for streaming response)",
74
+ }
 
75
  }
76
+ }
77
+ })
78
 
79
+ async def handle_messages(request):
 
 
 
80
  try:
81
  json_body = await request.json()
82
+ if not json_body.get('stream', True):
83
  log_request(request, 200)
84
+ return web.json_response({
85
  "id": str(uuid.uuid4()),
86
  "content": [
87
  {"text": "Please turn on streaming."},
88
+ {"id": "string", "name": "string", "input": {}}
89
  ],
90
  "model": "string",
91
  "stop_reason": "end_turn",
92
  "stop_sequence": "string",
93
+ "usage": {"input_tokens": 0, "output_tokens": 0}
94
+ })
95
+ else:
96
+ response = web.StreamResponse()
97
+ response.headers['Content-Type'] = 'text/event-stream;charset=utf-8'
98
+ await response.prepare(request)
99
+
100
+ # 处理消息
101
+ user_message = [{"question": "", "answer": ""}]
102
+ last_update = True
103
+ if 'system' in json_body:
104
+ json_body['messages'].insert(0, {"role": "system", "content": json_body['system']})
105
+
106
+ for msg in json_body['messages']:
107
+ if msg['role'] in ['system', 'user']:
108
+ if last_update:
109
+ user_message[-1]['question'] += msg['content'] + "\n"
110
+ elif user_message[-1]['question'] == "":
111
+ user_message[-1]['question'] += msg['content'] + "\n"
112
+ else:
113
+ user_message.append({"question": msg['content'] + "\n", "answer": ""})
114
+ last_update = True
115
+ elif msg['role'] == 'assistant':
116
+ if not last_update:
117
+ user_message[-1]['answer'] += msg['content'] + "\n"
118
+ elif user_message[-1]['answer'] == "":
119
+ user_message[-1]['answer'] += msg['content'] + "\n"
120
+ else:
121
+ user_message.append({"question": "", "answer": msg['content'] + "\n"})
122
+ last_update = False
123
+
124
+ previous_messages = "\n\n".join(msg['content'] for msg in json_body['messages'])
125
+ msg_id = str(uuid.uuid4())
126
+
127
+ await response.write(create_event("message_start", {
128
+ "type": "message_start",
129
+ "message": {
130
+ "id": msg_id,
131
+ "type": "message",
132
+ "role": "assistant",
133
+ "content": [],
134
+ "model": "claude-3-opus-20240229",
135
+ "stop_reason": None,
136
+ "stop_sequence": None,
137
+ "usage": {"input_tokens": 8, "output_tokens": 1},
138
+ }
139
+ }).encode())
140
+
141
+ await response.write(create_event("content_block_start", {
142
+ "type": "content_block_start",
143
+ "index": 0,
144
+ "content_block": {"type": "text", "text": ""}
145
+ }).encode())
146
+
147
+ await response.write(create_event("ping", {"type": "ping"}).encode())
148
+
149
+ # 设置代理
150
+ connector = None
151
+ if PROXY:
152
+ connector = aiohttp.TCPConnector(ssl=False, proxy=PROXY)
153
+
154
+ async with aiohttp.ClientSession(connector=connector) as session:
155
+ await sio.connect('wss://www.perplexity.ai/', **opts, http_session=session)
156
+
157
+ @sio.on('connect')
158
+ async def on_connect():
159
+ print(" > [Connected]")
160
  try:
161
+ response = await sio.call('perplexity_ask', previous_messages, {
162
+ "version": "2.9",
163
+ "source": "default",
164
+ "attachments": [],
165
+ "language": "en-GB",
166
+ "timezone": "Europe/London",
167
+ "search_focus": "writing",
168
+ "frontend_uuid": str(uuid.uuid4()),
169
+ "mode": "concise",
170
+ "is_related_query": False,
171
+ "is_default_related_query": False,
172
+ "visitor_id": str(uuid.uuid4()),
173
+ "frontend_context_uuid": str(uuid.uuid4()),
174
+ "prompt_source": "user",
175
+ "query_source": "home"
176
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  print(response)
178
+ await response.write(create_event("content_block_stop", {"type": "content_block_stop", "index": 0}).encode())
179
+ await response.write(create_event("message_delta", {
180
+ "type": "message_delta",
181
+ "delta": {"stop_reason": "end_turn", "stop_sequence": None},
182
+ "usage": {"output_tokens": 12}
183
+ }).encode())
184
+ await response.write(create_event("message_stop", {"type": "message_stop"}).encode())
185
+ await response.write_eof()
 
 
186
  log_request(request, 200)
187
  except Exception as e:
188
+ if str(e) != "socket has been disconnected":
189
+ print(e)
190
  log_request(request, 500)
191
+
192
+ @sio.event
193
+ async def query_progress(data):
194
+ if 'text' in data:
195
+ text = json.loads(data['text'])
196
+ chunk = text['chunks'][-1] if text['chunks'] else None
197
+ if chunk:
198
+ chunk_json = json.dumps({
199
+ "type": "content_block_delta",
200
+ "index": 0,
201
+ "delta": {"type": "text_delta", "text": chunk},
202
+ })
203
+ await response.write(create_event("content_block_delta", chunk_json).encode())
204
+
205
+ @sio.event
206
+ async def disconnect():
207
+ print(" > [Disconnected]")
208
+
209
+ @sio.event
210
+ async def connect_error(error):
211
+ error_message = "Failed to connect to Perplexity.ai 连接到Perplexity失败\nPlease refer to the log for more information 请查看日志以获取更多信息"
212
+ await response.write(create_event("content_block_delta", json.dumps({
213
+ "type": "content_block_delta",
214
+ "index": 0,
215
+ "delta": {"type": "text_delta", "text": error_message},
216
+ })).encode())
217
+ await response.write(create_event("content_block_stop", {"type": "content_block_stop", "index": 0}).encode())
218
+ await response.write(create_event("message_delta", {
219
+ "type": "message_delta",
220
+ "delta": {"stop_reason": "end_turn", "stop_sequence": None},
221
+ "usage": {"output_tokens": 12}
222
+ }).encode())
223
+ await response.write(create_event("message_stop", {"type": "message_stop"}).encode())
224
+ await response.write_eof()
225
+ print(error)
226
+ log_request(request, 500)
227
+
228
+ await sio.wait()
229
+
230
+ return response
231
+
232
  except Exception as e:
233
  print(e)
234
  log_request(request, 400)
235
+ return web.json_response({"error": str(e)}, status=400)
236
+
237
+ # 创建应用
238
+ app = web.Application(middlewares=[validate_api_key])
239
+ app.router.add_get('/', handle_root)
240
+ app.router.add_post('/ai/v1/messages', handle_messages)
241
+
242
+ # 启动服务器
243
+ if __name__ == '__main__':
244
+ port = int(os.getenv('PORT', 8081))
245
+ web.run_app(app, port=port)
246
+ print(f"Perplexity proxy listening on port {port}")
 
 
247
  if not API_KEY:
248
+ print("警告:PPLX_KEY 环境变量未设置。API 密钥验证将失败。")