smgc commited on
Commit
faf18e1
1 Parent(s): 5f6ea0f

Update app.py

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