dan92 commited on
Commit
773eac5
·
verified ·
1 Parent(s): a45396a

Upload 2 files

Browse files
Files changed (2) hide show
  1. Dockerfile +1 -1
  2. app.py +166 -193
Dockerfile CHANGED
@@ -23,4 +23,4 @@ ENV PYTHONUNBUFFERED=1
23
  EXPOSE 3000
24
 
25
  # 使用 gunicorn 作为生产级 WSGI 服务器
26
- CMD ["gunicorn", "--bind", "0.0.0.0:3000", "--workers", "4", "--timeout", "120", "app:app"]
 
23
  EXPOSE 3000
24
 
25
  # 使用 gunicorn 作为生产级 WSGI 服务器
26
+ CMD ["gunicorn", "--bind", "0.0.0.0:3000", "--workers", "4", "app:app"]
app.py CHANGED
@@ -107,7 +107,7 @@ def create_custom_session():
107
  return session
108
 
109
  # 添加速率限制相关的常量
110
- AUTH_RETRY_DELAY = 60 # 认证试延迟(秒)
111
  AUTH_BACKOFF_FACTOR = 2 # 退避因子
112
  AUTH_MAX_RETRIES = 3 # 最大重试次数
113
  AUTH_CHECK_INTERVAL = 300 # 健康检查间隔(秒)
@@ -307,42 +307,34 @@ class MultiAuthManager:
307
  def __init__(self, credentials):
308
  self.auth_managers = [AuthManager(email, password) for email, password in credentials]
309
  self.current_index = 0
310
- self.last_success_index = 0 # 记录上一次成功的账号索引
311
- self._last_rotation_date = None # 使用None作为初始值
312
- self._lock = threading.Lock() # 添加线程锁
 
 
 
313
 
314
  def get_next_auth_manager(self, model):
315
- """改进的账号选择逻辑,从上次成功的账号开始尝试"""
316
- with self._lock: # 使用线程锁保护共享状态
317
- try:
318
- current_date = datetime.now().date()
319
-
320
- # 初始化或检查日期变更
321
- if self._last_rotation_date is None or current_date > self._last_rotation_date:
322
- self.current_index = 0
323
- self.last_success_index = 0
324
- self._last_rotation_date = current_date
325
- # 重置所有账号的模型状态
326
- for auth_manager in self.auth_managers:
327
- auth_manager.reset_model_status()
328
- return self.auth_managers[0] if self.auth_managers else None
329
-
330
- # 从上次成功的账号开始尝试
331
- self.current_index = self.last_success_index
332
- if 0 <= self.current_index < len(self.auth_managers):
333
- auth_manager = self.auth_managers[self.current_index]
334
- if auth_manager.is_model_available(model) and auth_manager._should_attempt_auth():
335
- return auth_manager
336
- return None
337
- except Exception as e:
338
- logger.error(f"Error in get_next_auth_manager: {str(e)}")
339
- return None
340
 
341
  def ensure_valid_token(self, model):
342
- auth_manager = self.get_next_auth_manager(model)
343
- if auth_manager and auth_manager.ensure_valid_token():
344
- self.last_success_index = self.current_index # 更新最后成功的账号索引
345
- return auth_manager
346
  return None
347
 
348
  def reset_all_model_status(self):
@@ -453,7 +445,7 @@ def create_openai_chunk(content, model, finish_reason=None, usage=None):
453
  "delta": {"content": content} if content else {},
454
  "logprobs": None,
455
  "finish_reason": finish_reason,
456
- # 添加上下文关信息
457
  "context_preserved": True
458
  }
459
  ]
@@ -480,30 +472,22 @@ def stream_notdiamond_response(response, model):
480
  buffer = ""
481
  full_content = ""
482
 
483
- for chunk in response.iter_lines():
484
  if chunk:
485
  try:
486
- chunk_str = chunk.decode('utf-8')
487
- # 跳过SSE前缀
488
- if chunk_str.startswith('data: '):
489
- chunk_str = chunk_str[6:]
490
- elif chunk_str == 'data: [DONE]':
491
- continue
492
-
493
- # 尝试解析JSON
494
- try:
495
- chunk_data = json.loads(chunk_str)
496
- content = chunk_data.get('choices', [{}])[0].get('delta', {}).get('content', '')
497
- if content:
498
- full_content += content
499
- chunk_data = create_openai_chunk(content, model)
500
- yield chunk_data
501
- except json.JSONDecodeError:
502
- # 如果不是JSON格式,直接作为内容处理
503
- if chunk_str.strip():
504
- full_content += chunk_str
505
- chunk_data = create_openai_chunk(chunk_str, model)
506
- yield chunk_data
507
 
508
  except Exception as e:
509
  logger.error(f"Error processing chunk: {e}")
@@ -512,71 +496,20 @@ def stream_notdiamond_response(response, model):
512
  # 发送完成标记
513
  final_chunk = create_openai_chunk('', model, 'stop')
514
  if 'choices' in final_chunk and final_chunk['choices']:
515
- final_chunk['choices'][0]['context'] = full_content
516
  yield final_chunk
517
 
518
- def generate_stream_response(response, model, prompt_tokens):
519
- """生成流式 HTTP 响应,确保完整的上下文。"""
520
- total_completion_tokens = 0
521
- full_content = ""
522
-
523
- for chunk in stream_notdiamond_response(response, model):
524
- content = chunk['choices'][0]['delta'].get('content', '')
525
- if content:
526
- full_content += content
527
- total_completion_tokens = count_tokens(full_content, model)
528
-
529
- chunk['usage'] = {
530
- "prompt_tokens": prompt_tokens,
531
- "completion_tokens": total_completion_tokens,
532
- "total_tokens": prompt_tokens + total_completion_tokens
533
- }
534
-
535
- # 确保每个块都包含完整的上下文
536
- chunk['choices'][0]['context'] = full_content
537
-
538
- yield f"data: {json.dumps(chunk)}\n\n"
539
-
540
- # 发送最终的完成标记
541
- final_chunk = create_openai_chunk('', model, 'stop')
542
- final_chunk['choices'][0]['context'] = full_content
543
- final_chunk['usage'] = {
544
- "prompt_tokens": prompt_tokens,
545
- "completion_tokens": total_completion_tokens,
546
- "total_tokens": prompt_tokens + total_completion_tokens
547
- }
548
- yield f"data: {json.dumps(final_chunk)}\n\n"
549
- yield "data: [DONE]\n\n"
550
-
551
  def handle_non_stream_response(response, model, prompt_tokens):
552
- """改进的非流式响应处理,确保完整的上下文。"""
553
  full_content = ""
 
554
 
555
  try:
556
- for chunk in response.iter_lines():
557
  if chunk:
558
- try:
559
- chunk_str = chunk.decode('utf-8')
560
- # 跳过SSE前缀
561
- if chunk_str.startswith('data: '):
562
- chunk_str = chunk_str[6:]
563
- elif chunk_str == 'data: [DONE]':
564
- continue
565
-
566
- # 尝试解析JSON
567
- try:
568
- chunk_data = json.loads(chunk_str)
569
- content = chunk_data.get('choices', [{}])[0].get('delta', {}).get('content', '')
570
- if content:
571
- full_content += content
572
- except json.JSONDecodeError:
573
- # 如果不是JSON格式,直接作为内容处理
574
- if chunk_str.strip():
575
- full_content += chunk_str
576
-
577
- except Exception as e:
578
- logger.error(f"Error processing chunk in non-stream response: {e}")
579
- continue
580
 
581
  completion_tokens = count_tokens(full_content, model)
582
  total_tokens = prompt_tokens + completion_tokens
@@ -593,7 +526,8 @@ def handle_non_stream_response(response, model, prompt_tokens):
593
  "index": 0,
594
  "message": {
595
  "role": "assistant",
596
- "content": full_content
 
597
  },
598
  "finish_reason": "stop"
599
  }
@@ -611,6 +545,24 @@ def handle_non_stream_response(response, model, prompt_tokens):
611
  logger.error(f"Error processing non-stream response: {e}")
612
  raise
613
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  def get_auth_credentials():
615
  """从API获取认证凭据"""
616
  try:
@@ -831,7 +783,6 @@ def make_request(payload, auth_manager, model_id):
831
  global multi_auth_manager
832
  max_retries = 3
833
  retry_delay = 1
834
- request_timeout = 10 # 减少单次请求超时时间
835
 
836
  logger.info(f"尝试发送请求,模型:{model_id}")
837
 
@@ -851,92 +802,114 @@ def make_request(payload, auth_manager, model_id):
851
  # 记录已尝试的账号
852
  tried_accounts = set()
853
 
854
- while True: # 持续尝试,直到成功或确定所有账号都不可用
855
- try:
856
- auth_manager = multi_auth_manager.get_next_auth_manager(model_id)
857
- if not auth_manager:
858
- # 检查是否所有账号都已尝试
859
- if len(tried_accounts) >= len(multi_auth_manager.auth_managers):
860
- logger.error("所有账号都已尝试且不可用")
861
- # 尝试注册新账号
862
- successful_accounts = register_bot.register_and_verify(5)
863
- if successful_accounts:
864
- credentials = [(account['email'], account['password']) for account in successful_accounts]
865
- multi_auth_manager = MultiAuthManager(credentials)
866
- tried_accounts.clear() # 清空已尝试账号列表
867
- continue # 继续尝试新注册的账号
868
- else:
869
- raise Exception("所有账号均不可用,且注册新账号失败")
870
- continue # 如果还有未尝试的账号,继续循环
 
 
 
 
 
 
871
 
872
- # 如果这个账号已经尝试过,跳过
873
- if auth_manager._email in tried_accounts:
874
- continue
 
 
 
 
 
 
 
 
 
 
 
 
875
 
876
- tried_accounts.add(auth_manager._email)
877
- logger.info(f"尝试使用账号 {auth_manager._email}")
 
 
 
 
 
878
 
879
- for attempt in range(max_retries):
880
- try:
881
- url = get_notdiamond_url()
882
- headers = get_notdiamond_headers(auth_manager)
883
-
884
- # 使��� session 发送请求
885
- session = create_custom_session()
886
- response = session.post(
887
- url,
888
- headers=headers,
889
- json=payload,
890
- stream=True,
891
- timeout=request_timeout
892
- )
893
-
894
- if response.status_code == 200:
895
- logger.info(f"请求成功,使用账号 {auth_manager._email}")
896
- return response
897
-
898
- # 处理不同的错误状态码
899
- if response.status_code == 401: # Unauthorized
900
- logger.warning(f"Token expired for account {auth_manager._email}")
901
- if auth_manager.ensure_valid_token():
902
- continue
903
- break
904
-
905
- if response.status_code == 403: # Forbidden
906
- logger.warning(f"Model {model_id} usage limit reached for account {auth_manager._email}")
907
- auth_manager.set_model_unavailable(model_id)
908
- break
909
 
910
- if response.status_code >= 500: # Server error
911
- if attempt < max_retries - 1:
912
- time.sleep(retry_delay * (attempt + 1))
 
 
 
913
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
914
 
915
- logger.error(f"Request failed with status {response.status_code}")
916
-
917
- except requests.Timeout:
918
- logger.warning(f"Request timeout for account {auth_manager._email}, attempt {attempt + 1}")
919
- if attempt < max_retries - 1:
920
- time.sleep(retry_delay * (attempt + 1))
921
- continue
922
- except requests.RequestException as e:
923
- logger.error(f"Request error: {str(e)}")
924
- if attempt < max_retries - 1:
925
- time.sleep(retry_delay * (attempt + 1))
926
- continue
927
- except Exception as e:
928
- logger.error(f"Unexpected error: {str(e)}")
929
- if attempt < max_retries - 1:
930
- time.sleep(retry_delay * (attempt + 1))
931
- continue
932
  except Exception as e:
933
- logger.error(f"Error in main request loop: {str(e)}")
934
- time.sleep(retry_delay)
935
-
936
- raise Exception("无法完成请求,所有可用账号都已尝试")
 
 
 
 
937
 
938
- # 删除 health_check 函数和相关的线程启动代码
939
  if __name__ == "__main__":
 
 
 
940
  port = int(os.environ.get("PORT", 3000))
941
  app.run(debug=False, host='0.0.0.0', port=port, threaded=True)
942
 
 
107
  return session
108
 
109
  # 添加速率限制相关的常量
110
+ AUTH_RETRY_DELAY = 60 # 认证重试延迟(秒)
111
  AUTH_BACKOFF_FACTOR = 2 # 退避因子
112
  AUTH_MAX_RETRIES = 3 # 最大重试次数
113
  AUTH_CHECK_INTERVAL = 300 # 健康检查间隔(秒)
 
307
  def __init__(self, credentials):
308
  self.auth_managers = [AuthManager(email, password) for email, password in credentials]
309
  self.current_index = 0
310
+ self._last_rotation = time.time()
311
+ self._rotation_interval = 300 # 5分钟轮转间隔
312
+
313
+ def _should_rotate(self) -> bool:
314
+ """检查是否应该轮转到下一个账号"""
315
+ return time.time() - self._last_rotation >= self._rotation_interval
316
 
317
  def get_next_auth_manager(self, model):
318
+ """改进的账号选择逻辑"""
319
+ if self._should_rotate():
320
+ self.current_index = (self.current_index + 1) % len(self.auth_managers)
321
+ self._last_rotation = time.time()
322
+
323
+ start_index = self.current_index
324
+ for _ in range(len(self.auth_managers)):
325
+ auth_manager = self.auth_managers[self.current_index]
326
+ if auth_manager.is_model_available(model) and auth_manager._should_attempt_auth():
327
+ return auth_manager
328
+ self.current_index = (self.current_index + 1) % len(self.auth_managers)
329
+ if self.current_index == start_index:
330
+ break
331
+ return None
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  def ensure_valid_token(self, model):
334
+ for _ in range(len(self.auth_managers)):
335
+ auth_manager = self.get_next_auth_manager(model)
336
+ if auth_manager and auth_manager.ensure_valid_token():
337
+ return auth_manager
338
  return None
339
 
340
  def reset_all_model_status(self):
 
445
  "delta": {"content": content} if content else {},
446
  "logprobs": None,
447
  "finish_reason": finish_reason,
448
+ # 添加上下文相关信息
449
  "context_preserved": True
450
  }
451
  ]
 
472
  buffer = ""
473
  full_content = ""
474
 
475
+ for chunk in response.iter_content(chunk_size=1024):
476
  if chunk:
477
  try:
478
+ new_content = chunk.decode('utf-8')
479
+ buffer += new_content
480
+ full_content += new_content
481
+
482
+ # 创建完整的响应块
483
+ chunk_data = create_openai_chunk(new_content, model)
484
+
485
+ # 确保响应块包含完整的上下文
486
+ if 'choices' in chunk_data and chunk_data['choices']:
487
+ chunk_data['choices'][0]['delta']['content'] = new_content
488
+ chunk_data['choices'][0]['context'] = full_content # 添加完整上下文
489
+
490
+ yield chunk_data
 
 
 
 
 
 
 
 
491
 
492
  except Exception as e:
493
  logger.error(f"Error processing chunk: {e}")
 
496
  # 发送完成标记
497
  final_chunk = create_openai_chunk('', model, 'stop')
498
  if 'choices' in final_chunk and final_chunk['choices']:
499
+ final_chunk['choices'][0]['context'] = full_content # 在最终块中包含完整上下文
500
  yield final_chunk
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  def handle_non_stream_response(response, model, prompt_tokens):
503
+ """改进的非流式响应处理,确保保持完整上下文。"""
504
  full_content = ""
505
+ context_buffer = []
506
 
507
  try:
508
+ for chunk in response.iter_content(chunk_size=1024):
509
  if chunk:
510
+ content = chunk.decode('utf-8')
511
+ full_content += content
512
+ context_buffer.append(content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
 
514
  completion_tokens = count_tokens(full_content, model)
515
  total_tokens = prompt_tokens + completion_tokens
 
526
  "index": 0,
527
  "message": {
528
  "role": "assistant",
529
+ "content": full_content,
530
+ "context": ''.join(context_buffer) # 包含完整上下文
531
  },
532
  "finish_reason": "stop"
533
  }
 
545
  logger.error(f"Error processing non-stream response: {e}")
546
  raise
547
 
548
+ def generate_stream_response(response, model, prompt_tokens):
549
+ """生成流式 HTTP 响应。"""
550
+ total_completion_tokens = 0
551
+
552
+ for chunk in stream_notdiamond_response(response, model):
553
+ content = chunk['choices'][0]['delta'].get('content', '')
554
+ total_completion_tokens += count_tokens(content, model)
555
+
556
+ chunk['usage'] = {
557
+ "prompt_tokens": prompt_tokens,
558
+ "completion_tokens": total_completion_tokens,
559
+ "total_tokens": prompt_tokens + total_completion_tokens
560
+ }
561
+
562
+ yield f"data: {json.dumps(chunk)}\n\n"
563
+
564
+ yield "data: [DONE]\n\n"
565
+
566
  def get_auth_credentials():
567
  """从API获取认证凭据"""
568
  try:
 
783
  global multi_auth_manager
784
  max_retries = 3
785
  retry_delay = 1
 
786
 
787
  logger.info(f"尝试发送请求,模型:{model_id}")
788
 
 
802
  # 记录已尝试的账号
803
  tried_accounts = set()
804
 
805
+ while len(tried_accounts) < len(multi_auth_manager.auth_managers):
806
+ auth_manager = multi_auth_manager.get_next_auth_manager(model_id)
807
+ if not auth_manager:
808
+ break
809
+
810
+ # 如果这个账号已经尝试过,继续下一个
811
+ if auth_manager._email in tried_accounts:
812
+ continue
813
+
814
+ tried_accounts.add(auth_manager._email)
815
+ logger.info(f"尝试使用账号 {auth_manager._email}")
816
+
817
+ for attempt in range(max_retries):
818
+ try:
819
+ url = get_notdiamond_url()
820
+ headers = get_notdiamond_headers(auth_manager)
821
+ response = executor.submit(
822
+ requests.post,
823
+ url,
824
+ headers=headers,
825
+ json=payload,
826
+ stream=True
827
+ ).result()
828
 
829
+ if response.status_code == 200 and response.headers.get('Content-Type') == 'text/event-stream':
830
+ logger.info(f"请求成功,使用账号 {auth_manager._email}")
831
+ return response
832
+
833
+ headers_cache.clear()
834
+
835
+ if response.status_code == 401: # Unauthorized
836
+ logger.info(f"Token expired for account {auth_manager._email}, attempting refresh")
837
+ if auth_manager.ensure_valid_token():
838
+ continue
839
+
840
+ if response.status_code == 403: # Forbidden, 模型使用限制
841
+ logger.warning(f"Model {model_id} usage limit reached for account {auth_manager._email}")
842
+ auth_manager.set_model_unavailable(model_id)
843
+ break # 跳出重试循环,尝试下一个账号
844
 
845
+ logger.error(f"Request failed with status {response.status_code} for account {auth_manager._email}")
846
+
847
+ except Exception as e:
848
+ logger.error(f"Request attempt {attempt + 1} failed for account {auth_manager._email}: {e}")
849
+ if attempt < max_retries - 1:
850
+ time.sleep(retry_delay)
851
+ continue
852
 
853
+ # 所有账号都尝试过且失败后,才进行注册
854
+ if len(tried_accounts) == len(multi_auth_manager.auth_managers):
855
+ logger.info("所有现有账号都已尝试,开始注册新账号")
856
+ successful_accounts = register_bot.register_and_verify(5)
857
+ if successful_accounts:
858
+ credentials = [(account['email'], account['password']) for account in successful_accounts]
859
+ multi_auth_manager = MultiAuthManager(credentials)
860
+ # 使用新注册的账号重试请求
861
+ return make_request(payload, None, model_id)
862
+
863
+ raise Exception("所有账号均不可用,且注册新账号失败")
864
+
865
+ def health_check():
866
+ """改进的健康检查函数"""
867
+ last_check_time = {} # 用于跟踪每个账号的最后检查时间
868
+
869
+ while True:
870
+ try:
871
+ if multi_auth_manager:
872
+ current_time = time.time()
873
+
874
+ for auth_manager in multi_auth_manager.auth_managers:
875
+ email = auth_manager._email
 
 
 
 
 
 
 
876
 
877
+ # 检查是否需要进行健康检查
878
+ if email not in last_check_time or \
879
+ current_time - last_check_time[email] >= AUTH_CHECK_INTERVAL:
880
+
881
+ if not auth_manager._should_attempt_auth():
882
+ logger.info(f"Skipping health check for {email} due to rate limiting")
883
  continue
884
+
885
+ if not auth_manager.ensure_valid_token():
886
+ logger.warning(f"Auth token validation failed during health check for {email}")
887
+ auth_manager.clear_auth()
888
+ else:
889
+ logger.info(f"Health check passed for {email}")
890
+
891
+ last_check_time[email] = current_time
892
+
893
+ # 每天重置所有账号的模型使用状态
894
+ current_time_local = time.localtime()
895
+ if current_time_local.tm_hour == 0 and current_time_local.tm_min == 0:
896
+ multi_auth_manager.reset_all_model_status()
897
+ logger.info("Reset model status for all accounts")
898
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
  except Exception as e:
900
+ logger.error(f"Health check error: {e}")
901
+
902
+ sleep(60) # 主循环每分钟运行一次
903
+
904
+ # 为了兼容 Flask CLI 和 Gunicorn,修改启动逻辑
905
+ if __name__ != "__main__":
906
+ health_check_thread = threading.Thread(target=health_check, daemon=True)
907
+ health_check_thread.start()
908
 
 
909
  if __name__ == "__main__":
910
+ health_check_thread = threading.Thread(target=health_check, daemon=True)
911
+ health_check_thread.start()
912
+
913
  port = int(os.environ.get("PORT", 3000))
914
  app.run(debug=False, host='0.0.0.0', port=port, threaded=True)
915