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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -274
app.py CHANGED
@@ -1,305 +1,202 @@
1
- from flask import Flask, request, jsonify, Response
2
- from flask_socketio import SocketIO, emit
3
- from uuid import uuid4
4
  import os
5
  import json
6
- import logging
7
- import requests
8
- from datetime import datetime
9
- from gevent import monkey
10
- monkey.patch_all()
11
-
12
- app = Flask(__name__)
13
- socketio = SocketIO(app, async_mode='gevent')
14
-
15
- # 从环境变量中获取API密钥
16
- API_KEY = os.getenv('PPLX_KEY')
17
-
18
- # 配置代理
19
- class ProxyAgent:
20
- def __init__(self):
21
- self.proxy = os.getenv('HTTP_PROXY') or os.getenv('HTTPS_PROXY')
22
-
23
- def get_session(self):
24
- session = requests.Session()
25
- if self.proxy:
26
- session.proxies = {
27
- 'http': self.proxy,
28
- 'https': self.proxy
29
- }
30
- return session
31
-
32
- agent = ProxyAgent()
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  opts = {
35
- 'agent': agent,
36
- 'auth': {
37
- 'jwt': 'anonymous-ask-user',
38
- },
39
- 'reconnection': False,
40
- 'transports': ['websocket'],
41
- 'path': '/socket.io',
42
- 'hostname': 'www.perplexity.ai',
43
- 'secure': True,
44
- 'port': '443',
45
- 'extraHeaders': {
46
- 'Cookie': os.getenv('PPLX_COOKIE'),
47
- 'User-Agent': os.getenv('USER_AGENT'),
48
- 'Accept': '*/*',
49
- 'priority': 'u=1, i',
50
- 'Referer': 'https://www.perplexity.ai/',
51
  },
52
  }
53
 
54
- # 添加中间件来验证API密钥
55
- def validate_api_key():
56
- api_key = request.headers.get('x-api-key')
 
 
 
 
57
  if api_key != API_KEY:
58
  log_request(request, 401)
59
- return jsonify({'error': 'Invalid API key'}), 401
60
- return None
61
 
62
  # 日志记录函数
63
- def log_request(req, status):
64
  timestamp = datetime.now().isoformat()
65
- ip = req.remote_addr
66
- route = req.path
67
- logging.info(f'{timestamp} - {ip} - {route} - {status}')
 
68
 
69
  # 根路由处理
70
- @app.route('/', methods=['GET'])
71
- def root():
72
  log_request(request, 200)
73
- return jsonify({
74
- 'message': 'Welcome to the Perplexity AI Proxy API',
75
- 'endpoints': {
76
- '/ai/v1/messages': {
77
- 'method': 'POST',
78
- 'description': 'Send a message to the AI',
79
- 'headers': {
80
- 'x-api-key': 'Your API key (required)',
81
- 'Content-Type': 'application/json'
82
- },
83
- 'body': {
84
- 'messages': 'Array of message objects',
85
- 'stream': 'Boolean (true for streaming response)',
86
- # 其他可能的参数...
 
 
87
  }
88
- }
89
  }
90
- })
91
 
92
- # 在路由中使用API密钥验证中间件
93
- @app.route('/ai/v1/messages', methods=['POST'])
94
- def ai_messages():
95
- validation_error = validate_api_key()
96
- if validation_error:
97
- return validation_error
98
 
99
- raw_body = request.data.decode('utf-8')
 
 
 
 
100
  try:
101
- json_body = json.loads(raw_body)
102
- if json_body.get('stream') is False:
103
  log_request(request, 200)
104
- return jsonify({
105
- 'id': str(uuid4()),
106
- 'content': [
107
- {
108
- 'text': 'Please turn on streaming.',
109
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  {
111
- 'id': 'string',
112
- 'name': 'string',
113
- 'input': {},
114
- },
115
- ],
116
- 'model': 'string',
117
- 'stop_reason': 'end_turn',
118
- 'stop_sequence': 'string',
119
- 'usage': {
120
- 'input_tokens': 0,
121
- 'output_tokens': 0,
122
- },
123
- })
124
- elif json_body.get('stream') is True:
125
- def generate():
126
- try:
127
- # 计算用户消息长度
128
- user_message = [{'question': '', 'answer': ''}]
129
- last_update = True
130
- if json_body.get('system'):
131
- json_body['messages'].insert(0, {'role': 'system', 'content': json_body['system']})
132
- for msg in json_body['messages']:
133
- if msg['role'] in ['system', 'user']:
134
- if last_update:
135
- user_message[-1]['question'] += msg['content'] + '\n'
136
- elif user_message[-1]['question'] == '':
137
- user_message[-1]['question'] += msg['content'] + '\n'
138
- else:
139
- user_message.append({'question': msg['content'] + '\n', 'answer': ''})
140
- last_update = True
141
- elif msg['role'] == 'assistant':
142
- if not last_update:
143
- user_message[-1]['answer'] += msg['content'] + '\n'
144
- elif user_message[-1]['answer'] == '':
145
- user_message[-1]['answer'] += msg['content'] + '\n'
146
- else:
147
- user_message.append({'question': '', 'answer': msg['content'] + '\n'})
148
- last_update = False
149
-
150
- previous_messages = '\n\n'.join(msg['content'] for msg in json_body['messages'])
151
- msg_id = str(uuid4())
152
-
153
- yield create_event('message_start', {
154
- 'type': 'message_start',
155
- 'message': {
156
- 'id': msg_id,
157
- 'type': 'message',
158
- 'role': 'assistant',
159
- 'content': [],
160
- 'model': 'claude-3-opus-20240229',
161
- 'stop_reason': None,
162
- 'stop_sequence': None,
163
- 'usage': {'input_tokens': 8, 'output_tokens': 1},
164
  },
165
- })
166
- yield create_event('content_block_start', {'type': 'content_block_start', 'index': 0, 'content_block': {'type': 'text', 'text': ''}})
167
- yield create_event('ping', {'type': 'ping'})
168
-
169
- # 代理响应
170
- socket = socketio.connect('wss://www.perplexity.ai/', **opts)
171
-
172
- @socket.on('connect')
173
- def on_connect():
174
- logging.info(' > [Connected]')
175
- socket.emit('perplexity_ask', previous_messages, {
176
- 'version': '2.9',
177
- 'source': 'default',
178
- 'attachments': [],
179
- 'language': 'en-GB',
180
- 'timezone': 'Europe/London',
181
- 'search_focus': 'writing',
182
- 'frontend_uuid': str(uuid4()),
183
- 'mode': 'concise',
184
- 'is_related_query': False,
185
- 'is_default_related_query': False,
186
- 'visitor_id': str(uuid4()),
187
- 'frontend_context_uuid': str(uuid4()),
188
- 'prompt_source': 'user',
189
- 'query_source': 'home'
190
- }, callback=on_response)
191
-
192
- def on_response(response):
193
- logging.info(response)
194
- yield create_event('content_block_stop', {'type': 'content_block_stop', 'index': 0})
195
- yield create_event('message_delta', {
196
- 'type': 'message_delta',
197
- 'delta': {'stop_reason': 'end_turn', 'stop_sequence': None},
198
- 'usage': {'output_tokens': 12},
199
- })
200
- yield create_event('message_stop', {'type': 'message_stop'})
201
- log_request(request, 200)
202
-
203
- @socket.on('query_progress')
204
- def on_query_progress(data):
205
- if data.get('text'):
206
- text = json.loads(data['text'])
207
- chunk = text['chunks'][-1]
208
- if chunk:
209
- yield create_event('content_block_delta', {
210
- 'type': 'content_block_delta',
211
- 'index': 0,
212
- 'delta': {'type': 'text_delta', 'text': chunk},
213
- })
214
-
215
- @socket.on('disconnect')
216
- def on_disconnect():
217
- logging.info(' > [Disconnected]')
218
-
219
- @socket.on('error')
220
- def on_error(error):
221
- yield create_event('content_block_delta', {
222
- 'type': 'content_block_delta',
223
- 'index': 0,
224
- 'delta': {'type': 'text_delta', 'text': 'Error occured while fetching output 输出时出现错误\nPlease refer to the log for more information 请查看日志以获取更多信息'},
225
- })
226
- yield create_event('content_block_stop', {'type': 'content_block_stop', 'index': 0})
227
- yield create_event('message_delta', {
228
- 'type': 'message_delta',
229
- 'delta': {'stop_reason': 'end_turn', 'stop_sequence': None},
230
- 'usage': {'output_tokens': 12},
231
- })
232
- yield create_event('message_stop', {'type': 'message_stop'})
233
- logging.error(error)
234
- log_request(request, 500)
235
-
236
- @socket.on('connect_error')
237
- def on_connect_error(error):
238
- yield create_event('content_block_delta', {
239
- 'type': 'content_block_delta',
240
- 'index': 0,
241
- 'delta': {'type': 'text_delta', 'text': 'Failed to connect to the Perplexity.ai 连接到Perplexity失败\nPlease refer to the log for more information 请查看日志以获取更多信息'},
242
- })
243
- yield create_event('content_block_stop', {'type': 'content_block_stop', 'index': 0})
244
- yield create_event('message_delta', {
245
- 'type': 'message_delta',
246
- 'delta': {'stop_reason': 'end_turn', 'stop_sequence': None},
247
- 'usage': {'output_tokens': 12},
248
- })
249
- yield create_event('message_stop', {'type': 'message_stop'})
250
- logging.error(error)
251
- log_request(request, 500)
252
-
253
- @socket.on('close')
254
- def on_close():
255
- logging.info(' > [Client closed]')
256
- socket.disconnect()
257
-
258
- except Exception as e:
259
- logging.error(e)
260
- yield create_event('content_block_delta', {
261
- 'type': 'content_block_delta',
262
- 'index': 0,
263
- 'delta': {'type': 'text_delta', 'text': 'Error occured while fetching output 输出时出现错误\nPlease refer to the log for more information 请查看日志以获取更多信息'},
264
- })
265
- yield create_event('content_block_stop', {'type': 'content_block_stop', 'index': 0})
266
- yield create_event('message_delta', {
267
- 'type': 'message_delta',
268
- 'delta': {'stop_reason': 'end_turn', 'stop_sequence': None},
269
- 'usage': {'output_tokens': 12},
270
- })
271
- yield create_event('message_stop', {'type': 'message_stop'})
272
- log_request(request, 500)
273
-
274
- return Response(generate(), mimetype='text/event-stream')
275
  else:
276
- raise ValueError('Invalid request')
277
- except Exception as e:
278
- logging.error(e)
279
- return jsonify({'error': str(e)}), 400
 
 
 
 
 
280
 
281
  # 处理其他路由
282
- @app.errorhandler(404)
283
- def not_found(e):
284
  log_request(request, 404)
285
- return 'Not Found', 404
286
 
287
  # 错误处理中间件
288
- @app.errorhandler(500)
289
- def internal_server_error(e):
290
- logging.error(e)
291
- log_request(request, 500)
292
- return 'Something broke!', 500
293
-
294
- if __name__ == '__main__':
295
- port = int(os.getenv('PORT', 8081))
296
- logging.info(f'Perplexity proxy listening on port {port}')
 
 
 
 
 
 
 
 
 
 
 
297
  if not API_KEY:
298
- logging.warning('Warning: PPLX_KEY environment variable is not set. API key validation will fail.')
299
- socketio.run(app, port=port, allow_unsafe_werkzeug=True)
 
 
300
 
301
- # eventStream util
302
- def create_event(event, data):
303
- if isinstance(data, dict):
304
- data = json.dumps(data)
305
- return f'event: {event}\ndata: {data}\n\n'
 
 
 
 
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