Update app.py
Browse files
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 |
-
|
10 |
-
from
|
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.
|
20 |
-
|
21 |
-
#
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
'extraHeaders': {
|
45 |
-
'Cookie': os.
|
46 |
-
'User-Agent': os.
|
47 |
'Accept': '*/*',
|
48 |
'priority': 'u=1, i',
|
49 |
'Referer': 'https://www.perplexity.ai/',
|
50 |
-
}
|
51 |
}
|
52 |
|
53 |
-
|
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(
|
60 |
-
return jsonify({
|
61 |
return None
|
62 |
|
63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
def root():
|
65 |
-
log_request(
|
66 |
return jsonify({
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
},
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
79 |
}
|
80 |
}
|
81 |
}
|
82 |
})
|
83 |
|
|
|
84 |
@app.route('/ai/v1/messages', methods=['POST'])
|
85 |
-
def
|
86 |
-
|
87 |
-
if
|
88 |
-
return
|
89 |
|
|
|
90 |
try:
|
91 |
-
json_body =
|
92 |
-
if
|
93 |
-
log_request(
|
94 |
return jsonify({
|
95 |
-
|
96 |
-
|
97 |
-
{
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
],
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
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 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
})
|
|
|
|
|
169 |
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
except Exception as e:
|
220 |
-
logging.error(
|
221 |
-
|
222 |
-
return jsonify({"error": str(e)}), 400
|
223 |
|
|
|
224 |
@app.errorhandler(404)
|
225 |
-
def not_found(
|
226 |
-
log_request(
|
227 |
-
return
|
228 |
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
|
|
|
235 |
def create_event(event, data):
|
236 |
if isinstance(data, dict):
|
237 |
data = json.dumps(data)
|
238 |
-
return f
|
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'
|
|
|
|
|
|
|
|
|
|
|
|
|
|