smgc commited on
Commit
5aad566
1 Parent(s): 378cf75

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -48
app.py CHANGED
@@ -9,27 +9,16 @@ import logging
9
  from threading import Event
10
  import re
11
 
12
- # 创建 Flask 应用
13
  app = Flask(__name__)
 
14
 
15
- # 自定义日志格式
16
- log_format = "%(asctime)s - %(levelname)s - %(name)s - %(message)s"
17
- logging.basicConfig(level=logging.INFO, format=log_format)
18
-
19
- # 创建不同的日志记录器
20
- app_logger = logging.getLogger('app')
21
- sio_logger = logging.getLogger('socketio.client')
22
- engineio_logger = logging.getLogger('engineio.client')
23
-
24
- # 调整日志级别,隐藏过多的调试信息
25
- sio_logger.setLevel(logging.WARNING)
26
- engineio_logger.setLevel(logging.WARNING)
27
-
28
- # 从环境变量中获取 API 密钥
29
  API_KEY = os.environ.get('PPLX_KEY')
30
 
31
  # 代理设置
32
  proxy_url = os.environ.get('PROXY_URL')
 
 
33
  if proxy_url:
34
  proxies = {
35
  'http': proxy_url,
@@ -40,7 +29,7 @@ if proxy_url:
40
  else:
41
  transport = None
42
 
43
- sio = socketio.Client(http_session=transport, logger=sio_logger, engineio_logger=engineio_logger)
44
 
45
  # 连接选项
46
  connect_opts = {
@@ -60,7 +49,7 @@ sio_opts = {
60
 
61
  def log_request(ip, route, status):
62
  timestamp = datetime.now().isoformat()
63
- app_logger.info(f"{timestamp} - {ip} - {route} - {status}")
64
 
65
  def validate_api_key():
66
  api_key = request.headers.get('x-api-key')
@@ -77,10 +66,13 @@ def normalize_content(content):
77
  if isinstance(content, str):
78
  return content
79
  elif isinstance(content, dict):
 
80
  return json.dumps(content, ensure_ascii=False)
81
  elif isinstance(content, list):
 
82
  return " ".join([normalize_content(item) for item in content])
83
  else:
 
84
  return ""
85
 
86
  def calculate_tokens(text):
@@ -89,9 +81,12 @@ def calculate_tokens(text):
89
  - 对于英文和有空格的文本,使用空格分词。
90
  - 对于中文等没有空格的文本,使用字符级分词。
91
  """
 
92
  if re.search(r'[^\x00-\x7F]', text):
 
93
  return len(text)
94
  else:
 
95
  tokens = text.split()
96
  return len(tokens)
97
 
@@ -125,19 +120,25 @@ def messages():
125
 
126
  try:
127
  json_body = request.json
128
- model = json_body.get('model', 'claude-3-opus-20240229')
129
- stream = json_body.get('stream', True)
130
 
 
131
  previous_messages = "\n\n".join([normalize_content(msg['content']) for msg in json_body['messages']])
 
 
132
  input_tokens = calculate_tokens(previous_messages)
133
 
134
  msg_id = str(uuid.uuid4())
135
  response_event = Event()
136
  response_text = []
 
137
 
138
  if not stream:
 
139
  return handle_non_stream(previous_messages, msg_id, model, input_tokens)
140
 
 
141
  log_request(request.remote_addr, request.path, 200)
142
 
143
  def generate():
@@ -148,17 +149,17 @@ def messages():
148
  "type": "message",
149
  "role": "assistant",
150
  "content": [],
151
- "model": model,
152
  "stop_reason": None,
153
  "stop_sequence": None,
154
- "usage": {"input_tokens": input_tokens, "output_tokens": 1},
155
  },
156
  })
157
  yield create_event("content_block_start", {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}})
158
  yield create_event("ping", {"type": "ping"})
159
 
160
  def on_connect():
161
- app_logger.info("Connected to Perplexity AI")
162
  emit_data = {
163
  "version": "2.9",
164
  "source": "default",
@@ -176,27 +177,41 @@ def messages():
176
  sio.emit('perplexity_ask', (previous_messages, emit_data))
177
 
178
  def on_query_progress(data):
179
- nonlocal response_text
180
  if 'text' in data:
181
  text = json.loads(data['text'])
182
  chunk = text['chunks'][-1] if text['chunks'] else None
183
  if chunk:
184
  response_text.append(chunk)
 
 
 
 
 
 
 
 
 
185
 
 
186
  if data.get('final', False):
187
  response_event.set()
188
 
 
 
 
189
  def on_disconnect():
190
- app_logger.info("Disconnected from Perplexity AI")
191
  response_event.set()
192
 
193
  def on_connect_error(data):
194
- app_logger.error(f"Connection error: {data}")
195
  response_text.append(f"Error connecting to Perplexity AI: {data}")
196
  response_event.set()
197
 
198
  sio.on('connect', on_connect)
199
  sio.on('query_progress', on_query_progress)
 
200
  sio.on('disconnect', on_disconnect)
201
  sio.on('connect_error', on_connect_error)
202
 
@@ -207,8 +222,6 @@ def messages():
207
  sio.sleep(0.1)
208
  while response_text:
209
  chunk = response_text.pop(0)
210
- # 记录每一块返回的内容
211
- app_logger.info(f"Returning chunk to client: {chunk}")
212
  yield create_event("content_block_delta", {
213
  "type": "content_block_delta",
214
  "index": 0,
@@ -216,7 +229,7 @@ def messages():
216
  })
217
 
218
  except Exception as e:
219
- app_logger.error(f"Error during socket connection: {str(e)}")
220
  yield create_event("content_block_delta", {
221
  "type": "content_block_delta",
222
  "index": 0,
@@ -226,30 +239,32 @@ def messages():
226
  if sio.connected:
227
  sio.disconnect()
228
 
229
- output_tokens = calculate_tokens(''.join(response_text))
230
-
231
  yield create_event("content_block_stop", {"type": "content_block_stop", "index": 0})
232
  yield create_event("message_delta", {
233
  "type": "message_delta",
234
  "delta": {"stop_reason": "end_turn", "stop_sequence": None},
235
- "usage": {"input_tokens": input_tokens, "output_tokens": output_tokens},
236
  })
237
- yield create_event("message_stop", {"type": "message_stop"})
238
 
239
  return Response(generate(), content_type='text/event-stream')
240
 
241
  except Exception as e:
242
- app_logger.error(f"Request error: {str(e)}")
243
  log_request(request.remote_addr, request.path, 400)
244
  return jsonify({"error": str(e)}), 400
245
 
246
  def handle_non_stream(previous_messages, msg_id, model, input_tokens):
 
 
 
247
  try:
248
  response_event = Event()
249
  response_text = []
250
 
251
  def on_connect():
252
- app_logger.info("Connected to Perplexity AI")
253
  emit_data = {
254
  "version": "2.9",
255
  "source": "default",
@@ -274,15 +289,16 @@ def handle_non_stream(previous_messages, msg_id, model, input_tokens):
274
  if chunk:
275
  response_text.append(chunk)
276
 
 
277
  if data.get('final', False):
278
  response_event.set()
279
 
280
  def on_disconnect():
281
- app_logger.info("Disconnected from Perplexity AI")
282
  response_event.set()
283
 
284
  def on_connect_error(data):
285
- app_logger.error(f"Connection error: {data}")
286
  response_text.append(f"Error connecting to Perplexity AI: {data}")
287
  response_event.set()
288
 
@@ -293,29 +309,30 @@ def handle_non_stream(previous_messages, msg_id, model, input_tokens):
293
 
294
  sio.connect('wss://www.perplexity.ai/', **connect_opts, headers=sio_opts['extraHeaders'])
295
 
 
296
  response_event.wait(timeout=30)
 
 
297
  output_tokens = calculate_tokens(''.join(response_text))
298
 
 
299
  full_response = {
300
- "content": [{"text": ''.join(response_text), "type": "text"}],
301
  "id": msg_id,
302
- "model": model,
303
  "role": "assistant",
304
  "stop_reason": "end_turn",
305
  "stop_sequence": None,
306
  "type": "message",
307
  "usage": {
308
- "input_tokens": input_tokens,
309
- "output_tokens": output_tokens,
310
  },
311
  }
312
-
313
- # 记录完整的响应内容
314
- app_logger.info(f"Returning full response to client: {full_response}")
315
  return Response(json.dumps(full_response, ensure_ascii=False), content_type='application/json')
316
 
317
  except Exception as e:
318
- app_logger.error(f"Error during socket connection: {str(e)}")
319
  return jsonify({"error": str(e)}), 500
320
  finally:
321
  if sio.connected:
@@ -328,18 +345,18 @@ def not_found(error):
328
 
329
  @app.errorhandler(500)
330
  def server_error(error):
331
- app_logger.error(f"Server error: {str(error)}")
332
  log_request(request.remote_addr, request.path, 500)
333
  return "Something broke!", 500
334
 
335
  def create_event(event, data):
336
  if isinstance(data, dict):
337
- data = json.dumps(data, ensure_ascii=False)
338
  return f"event: {event}\ndata: {data}\n\n"
339
 
340
  if __name__ == '__main__':
341
  port = int(os.environ.get('PORT', 8081))
342
- app_logger.info(f"Perplexity proxy listening on port {port}")
343
  if not API_KEY:
344
- app_logger.warning("Warning: PPLX_KEY environment variable is not set. API key validation will fail.")
345
  app.run(host='0.0.0.0', port=port)
 
9
  from threading import Event
10
  import re
11
 
 
12
  app = Flask(__name__)
13
+ logging.basicConfig(level=logging.INFO)
14
 
15
+ # 从环境变量中获取API密钥
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  API_KEY = os.environ.get('PPLX_KEY')
17
 
18
  # 代理设置
19
  proxy_url = os.environ.get('PROXY_URL')
20
+
21
+ # 设置代理
22
  if proxy_url:
23
  proxies = {
24
  'http': proxy_url,
 
29
  else:
30
  transport = None
31
 
32
+ sio = socketio.Client(http_session=transport, logger=True, engineio_logger=True)
33
 
34
  # 连接选项
35
  connect_opts = {
 
49
 
50
  def log_request(ip, route, status):
51
  timestamp = datetime.now().isoformat()
52
+ logging.info(f"{timestamp} - {ip} - {route} - {status}")
53
 
54
  def validate_api_key():
55
  api_key = request.headers.get('x-api-key')
 
66
  if isinstance(content, str):
67
  return content
68
  elif isinstance(content, dict):
69
+ # 将字典转化为 JSON 字符串
70
  return json.dumps(content, ensure_ascii=False)
71
  elif isinstance(content, list):
72
+ # 对于列表,递归处理每个元素
73
  return " ".join([normalize_content(item) for item in content])
74
  else:
75
+ # 如果是其他类型,返回空字符串
76
  return ""
77
 
78
  def calculate_tokens(text):
 
81
  - 对于英文和有空格的文本,使用空格分词。
82
  - 对于中文等没有空格的文本,使用字符级分词。
83
  """
84
+ # 首先判断文本是否包含大量非 ASCII 字符(如中文)
85
  if re.search(r'[^\x00-\x7F]', text):
86
+ # 如果包含非 ASCII 字符,使用字符级分词
87
  return len(text)
88
  else:
89
+ # 否则使用空格分词
90
  tokens = text.split()
91
  return len(tokens)
92
 
 
120
 
121
  try:
122
  json_body = request.json
123
+ model = json_body.get('model', 'claude-3-opus-20240229') # 动态获取模型,默认 claude-3-opus-20240229
124
+ stream = json_body.get('stream', True) # 默认为True
125
 
126
+ # 使用 normalize_content 递归处理 msg['content']
127
  previous_messages = "\n\n".join([normalize_content(msg['content']) for msg in json_body['messages']])
128
+
129
+ # 动态计算输入的 token 数量
130
  input_tokens = calculate_tokens(previous_messages)
131
 
132
  msg_id = str(uuid.uuid4())
133
  response_event = Event()
134
  response_text = []
135
+ output_tokens = 0 # 初始化 output_tokens
136
 
137
  if not stream:
138
+ # 处理 stream 为 false 的情况
139
  return handle_non_stream(previous_messages, msg_id, model, input_tokens)
140
 
141
+ # 记录日志:此时请求上下文仍然有效
142
  log_request(request.remote_addr, request.path, 200)
143
 
144
  def generate():
 
149
  "type": "message",
150
  "role": "assistant",
151
  "content": [],
152
+ "model": model, # 动态模型
153
  "stop_reason": None,
154
  "stop_sequence": None,
155
+ "usage": {"input_tokens": input_tokens, "output_tokens": 0}, # 动态 input_tokens,初始化 output_tokens
156
  },
157
  })
158
  yield create_event("content_block_start", {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}})
159
  yield create_event("ping", {"type": "ping"})
160
 
161
  def on_connect():
162
+ logging.info("Connected to Perplexity AI")
163
  emit_data = {
164
  "version": "2.9",
165
  "source": "default",
 
177
  sio.emit('perplexity_ask', (previous_messages, emit_data))
178
 
179
  def on_query_progress(data):
180
+ nonlocal response_text, output_tokens
181
  if 'text' in data:
182
  text = json.loads(data['text'])
183
  chunk = text['chunks'][-1] if text['chunks'] else None
184
  if chunk:
185
  response_text.append(chunk)
186
+ # 动态计算当前块的 token 数量并累积到 output_tokens
187
+ output_tokens += calculate_tokens(chunk)
188
+
189
+ # 发送 message_delta 事件,包含当前累积的 output_tokens
190
+ yield create_event("message_delta", {
191
+ "type": "message_delta",
192
+ "delta": {"stop_reason": None, "stop_sequence": None},
193
+ "usage": {"input_tokens": input_tokens, "output_tokens": output_tokens}, # 累积 output_tokens
194
+ })
195
 
196
+ # 检查是否是最终响应
197
  if data.get('final', False):
198
  response_event.set()
199
 
200
+ def on_query_complete(data):
201
+ response_event.set()
202
+
203
  def on_disconnect():
204
+ logging.info("Disconnected from Perplexity AI")
205
  response_event.set()
206
 
207
  def on_connect_error(data):
208
+ logging.error(f"Connection error: {data}")
209
  response_text.append(f"Error connecting to Perplexity AI: {data}")
210
  response_event.set()
211
 
212
  sio.on('connect', on_connect)
213
  sio.on('query_progress', on_query_progress)
214
+ sio.on('query_complete', on_query_complete)
215
  sio.on('disconnect', on_disconnect)
216
  sio.on('connect_error', on_connect_error)
217
 
 
222
  sio.sleep(0.1)
223
  while response_text:
224
  chunk = response_text.pop(0)
 
 
225
  yield create_event("content_block_delta", {
226
  "type": "content_block_delta",
227
  "index": 0,
 
229
  })
230
 
231
  except Exception as e:
232
+ logging.error(f"Error during socket connection: {str(e)}")
233
  yield create_event("content_block_delta", {
234
  "type": "content_block_delta",
235
  "index": 0,
 
239
  if sio.connected:
240
  sio.disconnect()
241
 
242
+ # 发送最终的 message_stop 事件,包含最终的 output_tokens
 
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": {"input_tokens": input_tokens, "output_tokens": output_tokens}, # 最终 output_tokens
248
  })
249
+ yield create_event("message_stop", {"type": "message_stop"}) # 确保发送 message_stop 事件
250
 
251
  return Response(generate(), content_type='text/event-stream')
252
 
253
  except Exception as e:
254
+ logging.error(f"Request error: {str(e)}")
255
  log_request(request.remote_addr, request.path, 400)
256
  return jsonify({"error": str(e)}), 400
257
 
258
  def handle_non_stream(previous_messages, msg_id, model, input_tokens):
259
+ """
260
+ 处理 stream 为 false 的情况,返回完整的响应。
261
+ """
262
  try:
263
  response_event = Event()
264
  response_text = []
265
 
266
  def on_connect():
267
+ logging.info("Connected to Perplexity AI")
268
  emit_data = {
269
  "version": "2.9",
270
  "source": "default",
 
289
  if chunk:
290
  response_text.append(chunk)
291
 
292
+ # 检查是否是最终响应
293
  if data.get('final', False):
294
  response_event.set()
295
 
296
  def on_disconnect():
297
+ logging.info("Disconnected from Perplexity AI")
298
  response_event.set()
299
 
300
  def on_connect_error(data):
301
+ logging.error(f"Connection error: {data}")
302
  response_text.append(f"Error connecting to Perplexity AI: {data}")
303
  response_event.set()
304
 
 
309
 
310
  sio.connect('wss://www.perplexity.ai/', **connect_opts, headers=sio_opts['extraHeaders'])
311
 
312
+ # 等待响应完成
313
  response_event.wait(timeout=30)
314
+
315
+ # 动态计算输出的 token 数量
316
  output_tokens = calculate_tokens(''.join(response_text))
317
 
318
+ # 生成完整的响应
319
  full_response = {
320
+ "content": [{"text": ''.join(response_text), "type": "text"}], # 合并所有文本块
321
  "id": msg_id,
322
+ "model": model, # 动态模型
323
  "role": "assistant",
324
  "stop_reason": "end_turn",
325
  "stop_sequence": None,
326
  "type": "message",
327
  "usage": {
328
+ "input_tokens": input_tokens, # 动态 input_tokens
329
+ "output_tokens": output_tokens, # 动态 output_tokens
330
  },
331
  }
 
 
 
332
  return Response(json.dumps(full_response, ensure_ascii=False), content_type='application/json')
333
 
334
  except Exception as e:
335
+ logging.error(f"Error during socket connection: {str(e)}")
336
  return jsonify({"error": str(e)}), 500
337
  finally:
338
  if sio.connected:
 
345
 
346
  @app.errorhandler(500)
347
  def server_error(error):
348
+ logging.error(f"Server error: {str(error)}")
349
  log_request(request.remote_addr, request.path, 500)
350
  return "Something broke!", 500
351
 
352
  def create_event(event, data):
353
  if isinstance(data, dict):
354
+ data = json.dumps(data, ensure_ascii=False) # 确保中文不会被转义
355
  return f"event: {event}\ndata: {data}\n\n"
356
 
357
  if __name__ == '__main__':
358
  port = int(os.environ.get('PORT', 8081))
359
+ logging.info(f"Perplexity proxy listening on port {port}")
360
  if not API_KEY:
361
+ logging.warning("Warning: PPLX_KEY environment variable is not set. API key validation will fail.")
362
  app.run(host='0.0.0.0', port=port)