Moonfanz commited on
Commit
913b6ab
·
verified ·
1 Parent(s): 556f737

Upload 4 files

Browse files
Files changed (3) hide show
  1. app.py +274 -208
  2. func.py +49 -66
  3. requirements.txt +1 -2
app.py CHANGED
@@ -1,272 +1,334 @@
1
-
2
- from flask import Flask, request, jsonify, Response, stream_with_context, render_template, session, redirect, url_for
3
- from apscheduler.schedulers.background import BackgroundScheduler
4
  import google.generativeai as genai
5
  import json
6
- from datetime import datetime
7
  import os
8
- from termcolor import colored
9
  import logging
10
  import func
11
- import requests
 
12
  import time
 
 
 
13
 
14
  os.environ['TZ'] = 'Asia/Shanghai'
 
15
  app = Flask(__name__)
16
- if not os.environ.get('TERM'):
17
- os.environ['TERM'] = 'xterm'
18
  app.secret_key = os.urandom(24)
19
 
20
- ProxyPasswords = os.environ.get('ProxyPasswords').split(',')
21
 
22
  formatter = logging.Formatter('%(message)s')
23
- ADMIN_PASSWORD = os.environ.get('AdminPassword')
24
-
25
  logger = logging.getLogger(__name__)
26
- logger.setLevel(logging.INFO)
27
-
28
  handler = logging.StreamHandler()
29
  handler.setFormatter(formatter)
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- logger.addHandler(handler)
32
-
33
- safety_settings = [
34
- {
35
- "category": "HARM_CATEGORY_HARASSMENT",
36
- "threshold": "BLOCK_NONE"
37
- },
38
- {
39
- "category": "HARM_CATEGORY_HATE_SPEECH",
40
- "threshold": "BLOCK_NONE"
41
- },
42
- {
43
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
44
- "threshold": "BLOCK_NONE"
45
- },
46
- {
47
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
48
- "threshold": "BLOCK_NONE"
49
- },
50
- ]
51
  class APIKeyManager:
52
  def __init__(self):
53
- self.api_keys = os.environ.get('API_KEYS').split(',')
54
- self.current_index = 0
55
-
56
-
57
  def get_available_key(self):
58
- if self.current_index >= len(self.api_keys):
59
- self.current_index = 0
60
- current_key = self.api_keys[self.current_index]
61
- self.current_index += 1
62
- return current_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  key_manager = APIKeyManager()
 
65
  current_api_key = key_manager.get_available_key()
66
- logger.info(f"Current API key: {current_api_key[:11]}...")
67
- genai.configure(api_key=current_api_key)
68
- models = genai.list_models()
69
- logger.info("Available models:")
70
- for model in models:
71
- logger.info(f"- {model.name}: {model.supported_generation_methods}")
 
 
 
 
 
 
72
  GEMINI_MODELS = [
73
- {"id": "gemini-pro"},
74
- {"id": "gemini-pro-vision"},
75
- {"id": "gemini-1.0-pro"},
76
- {"id": "gemini-1.0-pro-vision"},
77
  {"id": "gemini-1.5-pro-002"},
 
 
 
78
  {"id": "gemini-exp-1114"},
79
  {"id": "gemini-exp-1121"},
80
  {"id": "gemini-exp-1206"},
81
  {"id": "gemini-2.0-flash-exp"},
82
  {"id": "gemini-2.0-flash-thinking-exp-1219"},
83
- {"id": "gemini-2.0-pro-exp"},
84
  ]
85
 
86
- @app.route("/", methods=["GET", "POST"])
87
- def login():
88
- if request.method == "POST":
89
- password = request.form.get("password")
90
- if password == ADMIN_PASSWORD:
91
- session["logged_in"] = True
92
- return redirect(url_for("manage_keys"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  else:
94
- return render_template("login.html", error="Incorrect password")
95
- return render_template("login.html", error=None)
96
-
97
- @app.route("/manage", methods=["GET", "POST"])
98
- def manage_keys():
99
- if not session.get("logged_in"):
100
- return redirect(url_for("login"))
101
-
102
- if request.method == "POST":
103
- action = request.form.get("action")
104
- if action == "add":
105
- new_key = request.form.get("new_key")
106
- if new_key:
107
- ProxyPasswords.append(new_key)
108
- elif action == "delete":
109
- key_to_delete = request.form.get("key_to_delete")
110
- if key_to_delete in ProxyPasswords:
111
- ProxyPasswords.remove(key_to_delete)
112
-
113
- return render_template("manage.html", keys=ProxyPasswords)
114
-
115
- @app.route("/logout")
116
- def logout():
117
- session.pop("logged_in", None)
118
- return redirect(url_for("login"))
119
 
120
  @app.route('/hf/v1/chat/completions', methods=['POST'])
121
  def chat_completions():
122
- global current_api_key
123
  is_authenticated, auth_error, status_code = func.authenticate_request(request)
124
  if not is_authenticated:
125
- return auth_error if auth_error else jsonify({'error': 'Unauthorized'}), status_code if status_code else 401
126
- try:
127
- request_data = request.get_json()
128
- r_data = func.sanitize_request_data(request_data)
129
- r_data = json.dumps(r_data, indent=4, ensure_ascii=False).replace('\\n', '\n')
130
- os.system('cls' if os.name == 'nt' else 'clear')
131
- logger.info(r_data)
132
- messages = request_data.get('messages', [])
133
- model = request_data.get('model', 'gemini-exp-1206')
134
- temperature = request_data.get('temperature', 1)
135
- max_tokens = request_data.get('max_tokens', 8192)
136
- stream = request_data.get('stream', False)
137
-
138
- logger.info(colored(f"\n{model} [r] -> {current_api_key[:11]}...", 'yellow'))
139
-
140
- # OpenAI 格式的消息转换为 Gemini 格式
141
- gemini_history, user_message, error_response = func.process_messages_for_gemini(messages)
142
-
143
- if error_response:
144
- # 处理错误
145
- print(error_response)
146
-
147
- genai.configure(api_key=current_api_key)
148
-
149
- generation_config = {
150
- "temperature": temperature,
151
- "max_output_tokens": max_tokens
152
- }
153
 
154
- gen_model = genai.GenerativeModel(
155
- model_name=model,
156
- generation_config=generation_config,
157
- safety_settings=safety_settings
158
- )
 
159
 
160
-
161
- if stream:
162
- # 流式响应
 
 
163
  if gemini_history:
164
  chat_session = gen_model.start_chat(history=gemini_history)
165
- response = chat_session.send_message(user_message, stream=True)
166
  else:
167
- response = gen_model.generate_content(user_message, stream=True)
168
-
169
- def generate():
170
- try:
171
- for chunk in response:
172
- if chunk.text:
173
- data = {
174
- 'choices': [
175
- {
176
- 'delta': {
177
- 'content': chunk.text
178
- },
179
- 'finish_reason': None,
180
- 'index': 0
181
- }
182
- ],
183
- 'object': 'chat.completion.chunk'
184
- }
185
-
186
- yield f"data: {json.dumps(data)}\n\n"
187
  data = {
188
  'choices': [
189
  {
190
- 'delta': {},
191
- 'finish_reason': 'stop',
 
 
192
  'index': 0
193
  }
194
  ],
195
  'object': 'chat.completion.chunk'
196
  }
197
-
198
- yield f"data: {json.dumps(data)}\n\n"
199
- except Exception as e:
200
- logger.error(f"Error during streaming: {str(e)}")
201
-
202
- data = {
203
- 'error': {
204
- 'message': str(e),
205
- 'type': 'internal_server_error'
206
- }
207
- }
208
  yield f"data: {json.dumps(data)}\n\n"
209
 
210
- return Response(stream_with_context(generate()), mimetype='text/event-stream')
211
- else:
212
- # 调用 API
213
- if gemini_history:
214
- chat_session = gen_model.start_chat(history=gemini_history)
215
- response = chat_session.send_message(user_message)
216
- else:
217
- response = gen_model.generate_content(user_message)
218
- try:
219
- text_content = response.candidates[0].content.parts[0].text
220
-
221
- except (AttributeError, IndexError, TypeError) as e:
222
- logger.error(colored(f"Error getting text content: {str(e)}",'red'))
223
-
224
- text_content = "Error: Unable to get text content."
225
-
226
- response_data = {
227
- 'id': 'chatcmpl-xxxxxxxxxxxx',
228
- 'object': 'chat.completion',
229
- 'created': int(datetime.now().timestamp()),
230
- 'model': model,
231
- 'choices': [{
232
- 'index': 0,
233
- 'message': {
234
- 'role': 'assistant',
235
- 'content': text_content
236
- },
237
- 'finish_reason': 'stop'
238
- }],
239
- 'usage':{
240
- 'prompt_tokens': 0,
241
- 'completion_tokens': 0,
242
- 'total_tokens': 0
243
  }
244
  }
245
- logger.info(colored(f"Generation Success", 'green'))
246
- return jsonify(response_data)
247
 
248
- except Exception as e:
249
- logger.error(f"Error in chat completions: {str(e)}")
250
- return jsonify({
 
 
 
 
 
 
 
 
 
251
  'error': {
252
- 'message': str(e),
253
- 'type': 'invalid_request_error'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
- }), 500
256
- finally:
257
- current_api_key = key_manager.get_available_key()
258
- logger.info(colored(f"API KEY Switched -> {current_api_key[:11]}...", 'aqua'))
259
 
260
  @app.route('/hf/v1/models', methods=['GET'])
261
  def list_models():
262
- is_authenticated, auth_error, status_code = func.authenticate_request(request)
263
- if not is_authenticated:
264
- return auth_error if auth_error else jsonify({'error': 'Unauthorized'}), status_code if status_code else 401
265
  response = {"object": "list", "data": GEMINI_MODELS}
266
  return jsonify(response)
267
 
268
  def keep_alive():
269
- """ 定期向应用自身发送请求,保持活跃 """
270
  try:
271
  response = requests.get("http://127.0.0.1:7860/", timeout=10)
272
  response.raise_for_status()
@@ -276,8 +338,12 @@ def keep_alive():
276
 
277
  if __name__ == '__main__':
278
  scheduler = BackgroundScheduler()
279
- # 设置定时任务,每 12 小时执行一次 keep_alive 函数
280
- scheduler.add_job(keep_alive, 'interval', hours = 12)
281
-
282
  scheduler.start()
283
- app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, Response, stream_with_context, render_template_string
2
+ from google.generativeai.types import generation_types
3
+ from google.api_core.exceptions import InvalidArgument, ResourceExhausted, ServiceUnavailable, InternalServerError, Aborted
4
  import google.generativeai as genai
5
  import json
 
6
  import os
 
7
  import logging
8
  import func
9
+ from datetime import datetime, timedelta
10
+ from apscheduler.schedulers.background import BackgroundScheduler
11
  import time
12
+ import requests
13
+ from collections import deque
14
+
15
 
16
  os.environ['TZ'] = 'Asia/Shanghai'
17
+
18
  app = Flask(__name__)
19
+
 
20
  app.secret_key = os.urandom(24)
21
 
 
22
 
23
  formatter = logging.Formatter('%(message)s')
 
 
24
  logger = logging.getLogger(__name__)
25
+ logger.setLevel(logging.INFO)
 
26
  handler = logging.StreamHandler()
27
  handler.setFormatter(formatter)
28
+ logger.addHandler(handler)
29
+
30
+ MAX_RETRIES = int(os.environ.get('MaxRetries', 3))
31
+ MAX_REQUESTS = int(os.environ.get('MaxRequests', 2))
32
+ LIMIT_WINDOW = int(os.environ.get('LimitWindow', 60))
33
+ RETRY_DELAY = 1
34
+ MAX_RETRY_DELAY = 16
35
+
36
+ request_counts = {}
37
+
38
+ api_key_blacklist = set()
39
+ api_key_blacklist_duration = 60
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  class APIKeyManager:
42
  def __init__(self):
43
+ self.api_keys = os.environ.get('KeyArray').split(',')
44
+ self.current_index = 0
45
+
 
46
  def get_available_key(self):
47
+ num_keys = len(self.api_keys)
48
+ for _ in range(num_keys):
49
+ if self.current_index >= num_keys:
50
+ self.current_index = 0
51
+ current_key = self.api_keys[self.current_index]
52
+ self.current_index += 1
53
+
54
+ if current_key not in api_key_blacklist:
55
+ return current_key
56
+
57
+ logger.error("所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
58
+ return None
59
+
60
+ def show_all_keys(self):
61
+ logger.info(f"当前可用API key个数: {len(self.api_keys)} ")
62
+ for i, api_key in enumerate(self.api_keys):
63
+ logger.info(f"API Key{i}: {api_key[:11]}...")
64
+
65
+ def blacklist_key(self, key):
66
+ logger.warning(f"{key[:11]} → 暂时禁用 {api_key_blacklist_duration} 秒")
67
+ api_key_blacklist.add(key)
68
+
69
+ scheduler.add_job(lambda: api_key_blacklist.discard(key), 'date', run_date=datetime.now() + timedelta(seconds=api_key_blacklist_duration))
70
 
71
  key_manager = APIKeyManager()
72
+ key_manager.show_all_keys()
73
  current_api_key = key_manager.get_available_key()
74
+
75
+ def switch_api_key():
76
+ global current_api_key
77
+ key = key_manager.get_available_key()
78
+ if key:
79
+ current_api_key = key
80
+ logger.info(f"API key 替换为 → {current_api_key[:11]}...")
81
+ else:
82
+ logger.error("API key 替换失败,所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
83
+
84
+ logger.info(f"当前 API key: {current_api_key[:11]}...")
85
+
86
  GEMINI_MODELS = [
 
 
 
 
87
  {"id": "gemini-1.5-pro-002"},
88
+ {"id": "gemini-1.5-pro-latest"},
89
+ {"id": "gemini-1.5-pro-exp-0827"},
90
+ {"id": "learnlm-1.5-pro-experimental"},
91
  {"id": "gemini-exp-1114"},
92
  {"id": "gemini-exp-1121"},
93
  {"id": "gemini-exp-1206"},
94
  {"id": "gemini-2.0-flash-exp"},
95
  {"id": "gemini-2.0-flash-thinking-exp-1219"},
96
+ {"id": "gemini-2.0-pro-exp"}
97
  ]
98
 
99
+ @app.route('/')
100
+ def index():
101
+ main_content = "Moonfanz Reminiproxy"
102
+ html_template = """
103
+ <!DOCTYPE html>
104
+ <html>
105
+ <head>
106
+ <meta charset="utf-8">
107
+ <script>
108
+ function copyToClipboard(text) {
109
+ var textarea = document.createElement("textarea");
110
+ textarea.textContent = text;
111
+ textarea.style.position = "fixed";
112
+ document.body.appendChild(textarea);
113
+ textarea.select();
114
+ try {
115
+ return document.execCommand("copy");
116
+ } catch (ex) {
117
+ console.warn("Copy to clipboard failed.", ex);
118
+ return false;
119
+ } finally {
120
+ document.body.removeChild(textarea);
121
+ }
122
+ }
123
+ function copyLink(event) {
124
+ event.preventDefault();
125
+ const url = new URL(window.location.href);
126
+ const link = url.protocol + '//' + url.host + '/hf/v1';
127
+ copyToClipboard(link);
128
+ alert('链接已复制: ' + link);
129
+ }
130
+ </script>
131
+ </head>
132
+ <body>
133
+ {{ main_content }}<br/><br/>完全开源、免费且禁止商用<br/><br/>点击复制反向代理: <a href="v1" onclick="copyLink(event)">Copy Link</a><br/>聊天来源选择"自定义(兼容 OpenAI)"<br/>将复制的网址填入到自定义端点<br/>将设置password填入自定义API秘钥<br/><br/><br/>
134
+ </body>
135
+ </html>
136
+ """
137
+ return render_template_string(html_template, main_content=main_content)
138
+
139
+ def is_within_rate_limit(api_key):
140
+ now = datetime.now()
141
+ if api_key not in request_counts:
142
+ request_counts[api_key] = deque()
143
+
144
+ while request_counts[api_key] and request_counts[api_key][0] < now - timedelta(seconds=LIMIT_WINDOW):
145
+ request_counts[api_key].popleft()
146
+
147
+ if len(request_counts[api_key]) >= MAX_REQUESTS:
148
+ earliest_request_time = request_counts[api_key][0]
149
+ wait_time = (earliest_request_time + timedelta(seconds=LIMIT_WINDOW)) - now
150
+ return False, wait_time.total_seconds()
151
+ else:
152
+ return True, 0
153
+
154
+ def increment_request_count(api_key):
155
+ now = datetime.now()
156
+ if api_key not in request_counts:
157
+ request_counts[api_key] = deque()
158
+ request_counts[api_key].append(now)
159
+
160
+ def handle_api_error(error, attempt, stream=False):
161
+ if attempt > MAX_RETRIES:
162
+ logger.error(f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入")
163
+ return False, jsonify({
164
+ 'error': {
165
+ 'message': f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入",
166
+ 'type': 'max_retries_exceeded'
167
+ }
168
+ })
169
+
170
+ if isinstance(error, InvalidArgument):
171
+ logger.error(f"{current_api_key[:11]} → 无效,可能已过期或被删除")
172
+ key_manager.blacklist_key(current_api_key)
173
+ switch_api_key()
174
+ return False, None
175
+
176
+ elif isinstance(error, (ResourceExhausted, Aborted, InternalServerError, ServiceUnavailable)):
177
+ delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
178
+ if isinstance(error, ResourceExhausted):
179
+ logger.warning(f"{current_api_key[:11]} → 429 官方资源耗尽 → {delay} 秒后重试...")
180
  else:
181
+ logger.warning(f"{current_api_key[:11]} → 未知错误↙ {delay} 秒后重试...\n{type(error).__name__}\n")
182
+ time.sleep(delay)
183
+ if isinstance(error, (ResourceExhausted)):
184
+ key_manager.blacklist_key(current_api_key)
185
+ switch_api_key()
186
+ return False, None
187
+
188
+ elif isinstance(error, generation_types.StopCandidateException):
189
+ logger.warning(f"AI输出内容被Gemini官方阻挡,代理没有得到有效回复")
190
+ switch_api_key()
191
+ return False, None
192
+
193
+ else:
194
+ logger.error(f"未知错误↙\n {error}")
195
+ return False, None
 
 
 
 
 
 
 
 
 
 
196
 
197
  @app.route('/hf/v1/chat/completions', methods=['POST'])
198
  def chat_completions():
 
199
  is_authenticated, auth_error, status_code = func.authenticate_request(request)
200
  if not is_authenticated:
201
+ return auth_error if auth_error else jsonify({'error': '未授权'}), status_code if status_code else 401
202
+
203
+ request_data = request.get_json()
204
+ messages = request_data.get('messages', [])
205
+ model = request_data.get('model', 'gemini-2.0-flash-exp')
206
+ temperature = request_data.get('temperature', 1)
207
+ max_tokens = request_data.get('max_tokens', 8192)
208
+ stream = request_data.get('stream', False)
209
+
210
+ logger.info(f"\n{model} [r] → {current_api_key[:11]}...")
211
+
212
+ gemini_history, user_message, error_response = func.process_messages_for_gemini(messages)
213
+
214
+ if error_response:
215
+ logger.error(f"处理输入消息时出错↙\n {error_response}")
216
+ return jsonify(error_response), 400
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
+ def do_request(current_api_key, attempt):
219
+ isok, time = is_within_rate_limit(current_api_key)
220
+ if not isok:
221
+ logger.warning(f"{current_api_key[:11]} → 暂时超过限额,该API key将在 {time} 秒后启用...")
222
+ switch_api_key()
223
+ return False, None
224
 
225
+ increment_request_count(current_api_key)
226
+
227
+ gen_model = func.get_gen_model(current_api_key, model, temperature, max_tokens)
228
+
229
+ try:
230
  if gemini_history:
231
  chat_session = gen_model.start_chat(history=gemini_history)
232
+ response = chat_session.send_message(user_message, stream=stream)
233
  else:
234
+ response = gen_model.generate_content(user_message, stream=stream)
235
+ return True, response
236
+ except Exception as e:
237
+ return handle_api_error(e, attempt, stream)
238
+
239
+ def generate(response):
240
+ try:
241
+ for chunk in response:
242
+ if chunk.text:
 
 
 
 
 
 
 
 
 
 
 
243
  data = {
244
  'choices': [
245
  {
246
+ 'delta': {
247
+ 'content': chunk.text
248
+ },
249
+ 'finish_reason': None,
250
  'index': 0
251
  }
252
  ],
253
  'object': 'chat.completion.chunk'
254
  }
 
 
 
 
 
 
 
 
 
 
 
255
  yield f"data: {json.dumps(data)}\n\n"
256
 
257
+ yield "data: [DONE]\n\n"
258
+ logger.info(f"200!")
259
+
260
+ except Exception as e:
261
+ logger.error(f"流式输出时截断,请关闭流式输出或修改你的输入")
262
+ error_data = {
263
+ 'error': {
264
+ 'message': '流式输出时截断,请关闭流式输出或修改你的输入',
265
+ 'type': 'internal_server_error'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  }
267
  }
268
+ yield f"data: {json.dumps(error_data)}\n\n"
269
+ yield "data: [DONE]\n\n"
270
 
271
+ attempt = 0
272
+ success = False
273
+ response = None
274
+
275
+ while attempt < MAX_RETRIES and not success:
276
+ attempt += 1
277
+ logger.info(f"第 {attempt}/{MAX_RETRIES} 次尝试 ...")
278
+ success, response = do_request(current_api_key, attempt)
279
+
280
+ if not success:
281
+ logger.error(f" {MAX_RETRIES} 次尝试均失败,请调整配置或向Moonfanz反馈")
282
+ response = {
283
  'error': {
284
+ 'message': f' {MAX_RETRIES} 次尝试均失败,请调整配置或向Moonfanz反馈',
285
+ 'type': 'internal_server_error'
286
+ }
287
+ }
288
+ return jsonify(response), 500 if response is not None else 503
289
+
290
+ if stream:
291
+ return Response(stream_with_context(generate(response)), mimetype='text/event-stream')
292
+ else:
293
+ try:
294
+ text_content = response.text
295
+ except (AttributeError, IndexError, TypeError) as e:
296
+ logger.error(f"处理AI返回消息时出错↙\n{e}")
297
+ return jsonify({
298
+ 'error': {
299
+ 'message': '处理AI返回消息时出错',
300
+ 'type': 'response_processing_error'
301
+ }
302
+ }), 500
303
+
304
+ response_data = {
305
+ 'id': 'chatcmpl-xxxxxxxxxxxx',
306
+ 'object': 'chat.completion',
307
+ 'created': int(datetime.now().timestamp()),
308
+ 'model': model,
309
+ 'choices': [{
310
+ 'index': 0,
311
+ 'message': {
312
+ 'role': 'assistant',
313
+ 'content': text_content
314
+ },
315
+ 'finish_reason': 'stop'
316
+ }],
317
+ 'usage': {
318
+ 'prompt_tokens': 0,
319
+ 'completion_tokens': 0,
320
+ 'total_tokens': 0
321
  }
322
+ }
323
+ logger.info(f"200!")
324
+ return jsonify(response_data)
 
325
 
326
  @app.route('/hf/v1/models', methods=['GET'])
327
  def list_models():
 
 
 
328
  response = {"object": "list", "data": GEMINI_MODELS}
329
  return jsonify(response)
330
 
331
  def keep_alive():
 
332
  try:
333
  response = requests.get("http://127.0.0.1:7860/", timeout=10)
334
  response.raise_for_status()
 
338
 
339
  if __name__ == '__main__':
340
  scheduler = BackgroundScheduler()
341
+
342
+ scheduler.add_job(keep_alive, 'interval', hours=12)
 
343
  scheduler.start()
344
+
345
+ logger.info(f"最大尝试次数/MaxRetries: {MAX_RETRIES}")
346
+ logger.info(f"最大请求次数/MaxRequests: {MAX_REQUESTS}")
347
+ logger.info(f"请求限额窗口/LimitWindow: {LIMIT_WINDOW} 秒")
348
+
349
+ app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
func.py CHANGED
@@ -1,92 +1,75 @@
1
  from io import BytesIO
2
- import os
 
3
  from flask import jsonify
4
  import logging
5
  import json
6
  import re
 
 
 
7
  logger = logging.getLogger(__name__)
8
 
9
- ProxyPasswords = os.environ.get('ProxyPasswords').split(',')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  def authenticate_request(request):
12
  auth_header = request.headers.get('Authorization')
13
 
14
  if not auth_header:
15
- return False, jsonify({'error': 'Authorization header is missing'}), 401
16
-
17
  try:
18
- auth_type, api_key = auth_header.split(' ', 1)
19
  except ValueError:
20
- return False, jsonify({'error': 'Invalid Authorization header format'}), 401
21
 
22
  if auth_type.lower() != 'bearer':
23
- return False, jsonify({'error': 'Authorization type must be Bearer'}), 401
24
 
25
- if api_key not in ProxyPasswords:
26
- return False, jsonify({'error': 'Unauthorized'}), 401
27
 
28
  return True, None, None
29
 
30
- def sanitize_request_data(request_data):
31
- """
32
- 从请求数据中删除base64编码的数据。
33
-
34
- Args:
35
- request_data: 包含可能存在base64数据的字典。
36
-
37
- Returns:
38
- 清理后的字典,其中base64数据被替换为"[Base64 Data Omitted]"。
39
- """
40
-
41
-
42
- def replace_base64(match):
43
- # 替换base64数据为提示信息
44
- return '"[Base64 Data Omitted]"'
45
-
46
-
47
- request_data_str = json.dumps(request_data)
48
-
49
- # 使用正则表达式匹配base64数据,并替换为提示信息
50
- sanitized_request_data_str = re.sub(
51
- r'"(data:[^;]+;base64,)[^"]+"',
52
- replace_base64,
53
- request_data_str
54
- )
55
 
56
- return json.loads(sanitized_request_data_str)
 
 
 
 
 
 
 
 
 
 
57
 
58
  def process_messages_for_gemini(messages):
59
- """
60
- 将通用的对话消息格式转换为Gemini API所需的格式
61
- 这个函数处理消息列表并将其转换为Gemini API兼容的格式。它支持文本、图片和文件内容的处理。
62
- 参数:
63
- messages (list): 包含对话消息的列表。每条消息应该是一个字典,包含'role'和'content'字段。
64
- - role: 可以是 'system', 'user' 或 'assistant'
65
- - content: 可以是字符串或包含多个内容项的列表
66
- 返回:
67
- tuple: 包含三个元素:
68
- - gemini_history (list): 转换后的历史消息列表
69
- - user_message (dict): 最新的用户消息
70
- - error (tuple or None): 如果有错误,返回错误响应;否则返回None
71
- 错误处理:
72
- - 检查角色是否有效
73
- - 验证图片URL格式
74
- - 验证文件URL格式
75
- 示例消息格式:
76
- 文本消息:
77
- {
78
- 'role': 'user',
79
- 'content': '你好'
80
- }
81
- 多模态消息:
82
- {
83
- 'role': 'user',
84
- 'content': [
85
- {'type': 'text', 'text': '这是什么图片?'},
86
- {'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,...'}}
87
- ]
88
- }
89
- """
90
  gemini_history = []
91
  errors = []
92
  for message in messages:
 
1
  from io import BytesIO
2
+ import base64
3
+ from PIL import Image
4
  from flask import jsonify
5
  import logging
6
  import json
7
  import re
8
+ import os
9
+ import requests
10
+ import google.generativeai as genai
11
  logger = logging.getLogger(__name__)
12
 
13
+
14
+ request_counts = {}
15
+
16
+ # 核心优势
17
+ safety_settings = [
18
+ {
19
+ "category": "HARM_CATEGORY_HARASSMENT",
20
+ "threshold": "BLOCK_NONE"
21
+ },
22
+ {
23
+ "category": "HARM_CATEGORY_HATE_SPEECH",
24
+ "threshold": "BLOCK_NONE"
25
+ },
26
+ {
27
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
28
+ "threshold": "BLOCK_NONE"
29
+ },
30
+ {
31
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
32
+ "threshold": "BLOCK_NONE"
33
+ },
34
+ ]
35
+
36
+ password = os.environ['password']
37
 
38
  def authenticate_request(request):
39
  auth_header = request.headers.get('Authorization')
40
 
41
  if not auth_header:
42
+ return False, jsonify({'error': '缺少Authorization请求头'}), 401
43
+
44
  try:
45
+ auth_type, pass_word = auth_header.split(' ', 1)
46
  except ValueError:
47
+ return False, jsonify({'error': 'Authorization请求头格式错误'}), 401
48
 
49
  if auth_type.lower() != 'bearer':
50
+ return False, jsonify({'error': 'Authorization类型必须为Bearer'}), 401
51
 
52
+ if pass_word != password:
53
+ return False, jsonify({'error': '未授权'}), 401
54
 
55
  return True, None, None
56
 
57
+ def get_gen_model(api_key, model, temperature, max_tokens):
58
+ genai.configure(api_key=api_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ generation_config = {
61
+ "temperature": temperature,
62
+ "max_output_tokens": max_tokens
63
+ }
64
+
65
+ gen_model = genai.GenerativeModel(
66
+ model_name=model,
67
+ generation_config=generation_config,
68
+ safety_settings=safety_settings
69
+ )
70
+ return gen_model
71
 
72
  def process_messages_for_gemini(messages):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  gemini_history = []
74
  errors = []
75
  for message in messages:
requirements.txt CHANGED
@@ -4,6 +4,5 @@ requests==2.26.0
4
  Werkzeug==2.0.3
5
  google==3.0.0
6
  google-generativeai==0.8.3
7
- termcolor==2.5.0
8
  pillow==10.4.0
9
- apscheduler
 
4
  Werkzeug==2.0.3
5
  google==3.0.0
6
  google-generativeai==0.8.3
 
7
  pillow==10.4.0
8
+ apscheduler