Update app.py
Browse files
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
|
7 |
-
import
|
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 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
'Accept': '*/*',
|
49 |
-
'priority': 'u=1, i',
|
50 |
-
'Referer': 'https://www.perplexity.ai/',
|
51 |
},
|
52 |
}
|
53 |
|
54 |
-
#
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
57 |
if api_key != API_KEY:
|
58 |
log_request(request, 401)
|
59 |
-
return
|
60 |
-
return
|
61 |
|
62 |
# 日志记录函数
|
63 |
-
def log_request(
|
64 |
timestamp = datetime.now().isoformat()
|
65 |
-
ip =
|
66 |
-
route =
|
67 |
-
|
|
|
68 |
|
69 |
# 根路由处理
|
70 |
-
|
71 |
-
def root():
|
72 |
log_request(request, 200)
|
73 |
-
return
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
|
|
|
|
87 |
}
|
88 |
-
}
|
89 |
}
|
90 |
-
|
91 |
|
92 |
-
#
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
return validation_error
|
98 |
|
99 |
-
|
|
|
|
|
|
|
|
|
100 |
try:
|
101 |
-
json_body = json
|
102 |
-
if json_body.get(
|
103 |
log_request(request, 200)
|
104 |
-
return
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
{
|
111 |
-
|
112 |
-
|
113 |
-
|
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 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
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(
|
277 |
-
|
278 |
-
|
279 |
-
|
|
|
|
|
|
|
|
|
|
|
280 |
|
281 |
# 处理其他路由
|
282 |
-
|
283 |
-
def not_found(e):
|
284 |
log_request(request, 404)
|
285 |
-
return
|
286 |
|
287 |
# 错误处理中间件
|
288 |
-
@
|
289 |
-
def
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
if not API_KEY:
|
298 |
-
|
299 |
-
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|