CoolFace
Apppublic

Astrobot314/Gemini-reproxy

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
app.py490 linesDownload Raw Back to root
1from flask import Flask, request, jsonify, Response, stream_with_context, render_template_string
2from google.generativeai.types import BlockedPromptException, StopCandidateException, generation_types
3from google.api_core.exceptions import InvalidArgument, ResourceExhausted, Aborted, InternalServerError, ServiceUnavailable, PermissionDenied
4import google.generativeai as genai
5import json
6import os
7import re
8import logging
9import func
10from datetime import datetime, timedelta
11from apscheduler.schedulers.background import BackgroundScheduler
12import time
13import requests
14from collections import deque
15import random
16
17os.environ['TZ'] = 'Asia/Shanghai'
18
19app = Flask(__name__)
20
21app.secret_key = os.urandom(24)
22
23formatter = logging.Formatter('%(message)s')
24logger = logging.getLogger(__name__)
25logger.setLevel(logging.INFO)
26handler = logging.StreamHandler()
27handler.setFormatter(formatter)
28logger.addHandler(handler)
29
30MAX_RETRIES = int(os.environ.get('MaxRetries', 3))
31MAX_REQUESTS = int(os.environ.get('MaxRequests', 2))
32LIMIT_WINDOW = int(os.environ.get('LimitWindow', 60))
33RETRY_DELAY = 1
34MAX_RETRY_DELAY = 16
35
36request_counts = {}
37
38api_key_blacklist = set()
39api_key_blacklist_duration = 60
40
41# 核心优势
42safety_settings = [
43    {
44        "category": "HARM_CATEGORY_HARASSMENT",
45        "threshold": "BLOCK_NONE"
46    },
47    {
48        "category": "HARM_CATEGORY_HATE_SPEECH",
49        "threshold": "BLOCK_NONE"
50    },
51    {
52        "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
53        "threshold": "BLOCK_NONE"
54    },
55    {
56        "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
57        "threshold": "BLOCK_NONE"
58    }
59]
60
61class APIKeyManager:
62    def __init__(self):
63        self.api_keys = re.findall(r"AIzaSy[a-zA-Z0-9_-]{33}", os.environ.get('KeyArray'))
64        self.current_index = random.randint(0, len(self.api_keys) - 1)
65
66    def get_available_key(self):
67        num_keys = len(self.api_keys)
68        for _ in range(num_keys):
69            if self.current_index >= num_keys:
70                self.current_index = 0
71            current_key = self.api_keys[self.current_index]
72            self.current_index += 1
73
74            if current_key not in api_key_blacklist:
75                return current_key
76
77        logger.error("所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
78        return None
79
80    def show_all_keys(self):
81        logger.info(f"当前可用API key个数: {len(self.api_keys)} ")
82        for i, api_key in enumerate(self.api_keys):
83            logger.info(f"API Key{i}: {api_key[:11]}...{api_key[-3:]}")
84
85    def blacklist_key(self, key):
86        logger.warning(f"{key[:11]} → 暂时禁用 {api_key_blacklist_duration} 秒")
87        api_key_blacklist.add(key)
88
89        scheduler.add_job(lambda: api_key_blacklist.discard(key), 'date', run_date=datetime.now() + timedelta(seconds=api_key_blacklist_duration))
90
91key_manager = APIKeyManager()
92key_manager.show_all_keys()
93current_api_key = key_manager.get_available_key()
94
95def switch_api_key():
96    global current_api_key
97    key = key_manager.get_available_key()
98    if key:
99      current_api_key = key
100      logger.info(f"API key 替换为 → {current_api_key[:11]}...{current_api_key[-3:]}")
101    else:
102      logger.error("API key 替换失败,所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
103
104logger.info(f"当前 API key: {current_api_key[:11]}...{current_api_key[-3:]}")
105
106GEMINI_MODELS = [
107    {"id": "gemini-1.5-flash-8b-latest"},
108    {"id": "gemini-1.5-flash-8b-exp-0924"},
109    {"id": "gemini-1.5-flash-latest"},
110    {"id": "gemini-1.5-flash-exp-0827"},
111    {"id": "gemini-1.5-pro-latest"},
112    {"id": "gemini-1.5-pro-exp-0827"},
113    {"id": "learnlm-1.5-pro-experimental"},
114    {"id": "gemini-exp-1114"},
115    {"id": "gemini-exp-1121"},
116    {"id": "gemini-exp-1206"},
117    {"id": "gemini-2.0-flash-exp"},
118    {"id": "gemini-2.0-flash-thinking-exp-1219"},
119    {"id": "gemini-2.0-pro-exp"}
120]
121
122@app.route('/')
123def index():
124    main_content = "Moonfanz Reminiproxy v2.2.0 2025-01-11"
125    html_template = """
126<!DOCTYPE html>
127<html>
128<head>
129<meta charset="utf-8">
130<script>
131function copyToClipboard(text) {
132  var textarea = document.createElement("textarea");
133  textarea.textContent = text;
134  textarea.style.position = "fixed";
135  document.body.appendChild(textarea);
136  textarea.select();
137  try {
138    return document.execCommand("copy");
139  } catch (ex) {
140    console.warn("Copy to clipboard failed.", ex);
141    return false;
142  } finally {
143    document.body.removeChild(textarea);
144  }
145}
146function copyLink(event) {
147  event.preventDefault();
148  const url = new URL(window.location.href);
149  const link = url.protocol + '//' + url.host + '/hf/v1';
150  copyToClipboard(link);
151  alert('链接已复制: ' + link);
152}
153</script>
154</head>
155<body>
156{{ main_content }}<br/><br/>完全开源、免费且禁止商用<br/><br/>点击复制反向代理: <a href="v1" onclick="copyLink(event)">Copy Link</a><br/>聊天来源选择"自定义(兼容 OpenAI)"<br/>将复制的网址填入到自定义端点<br/>将设置password填入自定义API秘钥<br/><br/><br/>
157</body>
158</html>
159    """
160    return render_template_string(html_template, main_content=main_content)
161
162def is_within_rate_limit(api_key):
163    now = datetime.now()
164    if api_key not in request_counts:
165        request_counts[api_key] = deque()
166
167    while request_counts[api_key] and request_counts[api_key][0] < now - timedelta(seconds=LIMIT_WINDOW):
168        request_counts[api_key].popleft()
169
170    if len(request_counts[api_key]) >= MAX_REQUESTS:
171        earliest_request_time = request_counts[api_key][0]
172        wait_time = (earliest_request_time + timedelta(seconds=LIMIT_WINDOW)) - now
173        return False, wait_time.total_seconds()
174    else:
175        return True, 0
176
177def increment_request_count(api_key):
178    now = datetime.now()
179    if api_key not in request_counts:
180        request_counts[api_key] = deque()
181    request_counts[api_key].append(now)
182
183def handle_api_error(error, attempt):
184    if attempt > MAX_RETRIES:
185        logger.error(f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入")
186        return 0, jsonify({
187                'error': {
188                    'message': f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入",
189                    'type': 'max_retries_exceeded'
190                }
191        })
192
193    if isinstance(error, InvalidArgument):
194        logger.error(f"{current_api_key[:11]} → 无效,可能已过期或被删除")
195        key_manager.blacklist_key(current_api_key)
196        switch_api_key()
197        return 0, None
198
199    elif isinstance(error, ResourceExhausted):
200        delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
201        logger.warning(f"{current_api_key[:11]} → 429 官方资源耗尽 → {delay} 秒后重试...")
202        key_manager.blacklist_key(current_api_key)
203        switch_api_key()
204        time.sleep(delay)
205        return 0, None
206
207    elif isinstance(error, Aborted):
208        delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
209        logger.warning(f"{current_api_key[:11]} → 操作被中止 → {delay} 秒后重试...")
210        switch_api_key()
211        time.sleep(delay)
212        return 0, None
213
214    elif isinstance(error, InternalServerError):
215        delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
216        logger.warning(f"{current_api_key[:11]} → 500 服务器内部错误 → {delay} 秒后重试...")
217        switch_api_key()
218        time.sleep(delay)
219        return 0, None
220
221    elif isinstance(error, ServiceUnavailable):
222        delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
223        logger.warning(f"{current_api_key[:11]} → 503 服务不可用 → {delay} 秒后重试...")
224        switch_api_key()
225        time.sleep(delay)
226        return 0, None
227
228    elif isinstance(error, PermissionDenied):
229        logger.error(f"{current_api_key[:11]} → 403 权限被拒绝,该 API KEY 可能已经被官方封禁")
230        key_manager.blacklist_key(current_api_key)
231        switch_api_key()
232        return 0, None
233
234    elif isinstance(error, StopCandidateException):
235        logger.warning(f"AI输出内容被Gemini官方阻挡,代理没有得到有效回复")
236        switch_api_key()
237        return 0, None
238
239    elif isinstance(error, generation_types.BlockedPromptException):
240        try:
241            full_reason_str = str(error.args[0])
242            logger.error(f"{full_reason_str}")
243            if "block_reason:" in full_reason_str:
244                start_index = full_reason_str.find("block_reason:") + len("block_reason:")
245                block_reason_str = full_reason_str[start_index:].strip()
246
247                if block_reason_str == "SAFETY":
248                    logger.warning(f"用户输入因安全原因被阻止")
249                    return 1, None
250                elif block_reason_str == "BLOCKLIST":
251                    logger.warning(f"用户输入因包含阻止列表中的术语而被阻止")
252                    return 1, None
253                elif block_reason_str == "PROHIBITED_CONTENT":
254                    logger.warning(f"用户输入因包含禁止内容而被阻止")
255                    return 1, None
256                elif block_reason_str == "OTHER":
257                    logger.warning(f"用户输入因未知原因被阻止")
258                    return 1, None
259                else:
260                    logger.warning(f"用户输入被阻止,原因未知: {block_reason_str}")
261                    return 1, None
262            else:
263                logger.warning(f"用户输入被阻止,原因未知: {full_reason_str}")
264                return 1, None
265
266        except (IndexError, AttributeError) as e:
267            logger.error(f"获取提示原因失败↙\n{e}")
268            logger.error(f"提示被阻止↙\n{error}")
269            return 2, None
270
271    else:
272        logger.error(f"该模型还未发布,暂时不可用,请更换模型或未来一段时间再试")
273        logger.error(f"证明↙\n{error}")
274        return 2, None
275
276@app.route('/hf/v1/chat/completions', methods=['POST'])
277def chat_completions():
278    is_authenticated, auth_error, status_code = func.authenticate_request(request)
279    if not is_authenticated:
280        return auth_error if auth_error else jsonify({'error': '未授权'}), status_code if status_code else 401
281
282    request_data = request.get_json()
283    messages = request_data.get('messages', [])
284    model = request_data.get('model', 'gemini-2.0-flash-exp')
285    temperature = request_data.get('temperature', 1)
286    max_tokens = request_data.get('max_tokens', 8192)
287    stream = request_data.get('stream', False)
288    hint = "流式" if stream else "非流"
289    logger.info(f"\n{model} [{hint}] → {current_api_key[:11]}...")
290
291    gemini_history, user_message, system_instruction, error_response = func.process_messages_for_gemini(messages)
292    # r_g = json.dumps(gemini_history, indent=4, ensure_ascii=False).replace('\\n', '\n')
293    # r_u = json.dumps(user_message, indent=4, ensure_ascii=False).replace('\\n', '\n')
294    # r_s = json.dumps(system_instruction, indent=4, ensure_ascii=False).replace('\\n', '\n')
295    # logger.info(f"历史对话: {r_g}")
296    # logger.info(f"用户消息: {r_u}")
297    # logger.info(f"系统指令: {r_s}")
298    if error_response:
299        logger.error(f"处理输入消息时出错↙\n {error_response}")
300        return jsonify(error_response), 400
301
302    def do_request(current_api_key, attempt):
303        isok, time = is_within_rate_limit(current_api_key)
304        if not isok:
305            logger.warning(f"{current_api_key[:11]} → 暂时超过限额,该API key将在 {time} 秒后启用...")
306            switch_api_key()
307            return 0, None
308
309        increment_request_count(current_api_key)
310
311        genai.configure(api_key=current_api_key)
312
313        generation_config = {
314            "temperature": temperature,
315            "max_output_tokens": max_tokens
316        }
317
318        gen_model = genai.GenerativeModel(
319            model_name=model,
320            generation_config=generation_config,
321            safety_settings=safety_settings,
322            system_instruction=system_instruction
323        )
324
325        try:
326            if gemini_history:
327                chat_session = gen_model.start_chat(history=gemini_history)
328                response = chat_session.send_message(user_message, stream=stream)
329            else:
330                response = gen_model.generate_content(user_message, stream=stream)
331            return 1, response
332        except Exception as e:
333            return handle_api_error(e, attempt)
334
335    def generate(response):
336        try:
337            logger.info(f"流式开始...")
338            for chunk in response:
339                if chunk.text:
340                    data = {
341                        'choices': [
342                            {
343                                'delta': {
344                                    'content': chunk.text
345                                },
346                                'finish_reason': None,
347                                'index': 0
348                            }
349                        ],
350                        'object': 'chat.completion.chunk'
351                    }
352                    yield f"data: {json.dumps(data)}\n\n"
353
354            data = {
355                        'choices': [
356                            {
357                                'delta': {},
358                                'finish_reason': 'stop',
359                                'index': 0
360                            }
361                        ],
362                        'object': 'chat.completion.chunk'
363                    }
364            logger.info(f"流式结束")
365            yield f"data: {json.dumps(data)}\n\n"
366            logger.info(f"200!")
367
368        except Exception:
369            logger.error(f"流式输出中途被截断,请关闭流式输出或修改你的输入")
370            logger.info(f"流式结束")
371            error_data = {
372                'error': {
373                    'message': '流式输出时截断,请关闭流式输出或修改你的输入',
374                    'type': 'internal_server_error'
375                }
376            }
377            yield f"data: {json.dumps(error_data)}\n\n"
378            data = {
379                        'choices': [
380                            {
381                                'delta': {},
382                                'finish_reason': 'stop',
383                                'index': 0
384                            }
385                        ],
386                        'object': 'chat.completion.chunk'
387                    }
388
389            yield f"data: {json.dumps(data)}\n\n"
390
391    attempt = 0
392    success = 0
393    response = None
394
395    for attempt in range(1, MAX_RETRIES + 1):
396        logger.info(f"第 {attempt}/{MAX_RETRIES} 次尝试 ...")
397        success, response = do_request(current_api_key, attempt)
398
399        if success == 1:
400            break
401        elif success == 2:
402
403            logger.error(f"{model} 很可能暂时不可用,请更换模型或未来一段时间再试")
404            response = {
405                'error': {
406                    'message': f'{model} 很可能暂时不可用,请更换模型或未来一段时间再试',
407                    'type': 'internal_server_error'
408                }
409            }
410            return jsonify(response), 503
411
412    else:
413        logger.error(f"{MAX_RETRIES} 次尝试均失败,请调整配置,或等待官方恢复,或向Moonfanz反馈")
414        response = {
415            'error': {
416                'message': f'{MAX_RETRIES} 次尝试均失败,请调整配置或向Moonfanz反馈',
417                'type': 'internal_server_error'
418            }
419        }
420        return jsonify(response), 500 if response is not None else 503
421
422    if stream:
423        return Response(stream_with_context(generate(response)), mimetype='text/event-stream')
424    else:
425        try:
426            text_content = response.text
427        except (AttributeError, IndexError, TypeError, ValueError) as e:
428            if "response.candidates" in str(e) or "response.text" in str(e):
429                logger.error(f"用户输入被AI安全过滤器阻止")
430                return jsonify({
431                    'error': {
432                        'message': '用户输入被AI安全过滤器阻止',
433                        'type': 'prompt_blocked_error',
434                        'details': str(e)
435                    }
436                }), 400
437            else:
438                return jsonify({
439                    'error': {
440                        'message': 'AI响应处理失败',
441                        'type': 'response_processing_error'
442                    }
443                }), 500
444
445        response_data = {
446            'id': 'chatcmpl-xxxxxxxxxxxx',  
447            'object': 'chat.completion',
448            'created': int(datetime.now().timestamp()),
449            'model': model,
450            'choices': [{
451                'index': 0,
452                'message': {
453                    'role': 'assistant',
454                    'content': text_content
455                },
456                'finish_reason': 'stop'
457            }],
458            'usage': {
459                'prompt_tokens': 0,
460                'completion_tokens': 0,
461                'total_tokens': 0
462            }
463        }
464        logger.info(f"200!")
465        return jsonify(response_data)
466
467@app.route('/hf/v1/models', methods=['GET'])
468def list_models():
469    response = {"object": "list", "data": GEMINI_MODELS}
470    return jsonify(response)
471
472def keep_alive():
473    try:
474        response = requests.get("http://127.0.0.1:7860/", timeout=10)
475        response.raise_for_status()  
476        print(f"Keep alive ping successful: {response.status_code} at {time.ctime()}")
477    except requests.exceptions.RequestException as e:
478        print(f"Keep alive ping failed: {e} at {time.ctime()}")
479
480if __name__ == '__main__':
481    scheduler = BackgroundScheduler()
482
483    scheduler.add_job(keep_alive, 'interval', hours=12)
484    scheduler.start()
485    logger.info(f"Reminiproxy v2.2.0 启动")
486    logger.info(f"最大尝试次数/MaxRetries: {MAX_RETRIES}")
487    logger.info(f"最大请求次数/MaxRequests: {MAX_REQUESTS}")
488    logger.info(f"请求限额窗口/LimitWindow: {LIMIT_WINDOW} 秒")
489
490    app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))