CoolFace
Apppublic

KSADASF/gemini-rproxy

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
app.py709 linesDownload Raw Back to root
1from flask import Flask, request, jsonify, Response, stream_with_context, render_template_string
2import json
3import os
4import re
5import logging
6import func
7from datetime import datetime, timedelta
8from apscheduler.schedulers.background import BackgroundScheduler
9import time
10import requests
11from collections import deque
12import random
13from dataclasses import dataclass
14from typing import Optional, Dict, Any
15
16app = Flask(__name__)
17
18os.environ['TZ'] = 'Asia/Shanghai'
19
20app = Flask(__name__)
21
22app.secret_key = os.urandom(24)
23
24formatter = logging.Formatter('%(message)s')
25logger = logging.getLogger(__name__)
26logger.setLevel(logging.INFO)
27handler = logging.StreamHandler()
28handler.setFormatter(formatter)
29logger.addHandler(handler)
30
31MAX_RETRIES = int(os.environ.get('MaxRetries', '3').strip() or '3')
32MAX_REQUESTS = int(os.environ.get('MaxRequests', '2').strip() or '2')
33LIMIT_WINDOW = int(os.environ.get('LimitWindow', '60').strip() or '60')
34
35RETRY_DELAY = 1
36MAX_RETRY_DELAY = 16
37
38request_counts = {}
39
40api_key_blacklist = set()
41api_key_blacklist_duration = 60
42
43
44safety_settings = [
45    {
46        "category": "HARM_CATEGORY_HARASSMENT",
47        "threshold": "BLOCK_NONE"
48    },
49    {
50        "category": "HARM_CATEGORY_HATE_SPEECH",
51        "threshold": "BLOCK_NONE"
52    },
53    {
54        "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
55        "threshold": "BLOCK_NONE"
56    },
57    {
58        "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
59        "threshold": "BLOCK_NONE"
60    },
61    {
62        "category": 'HARM_CATEGORY_CIVIC_INTEGRITY',
63        "threshold": 'BLOCK_NONE'
64    }
65]
66safety_settings_g2 = [
67    {
68        "category": "HARM_CATEGORY_HARASSMENT",
69        "threshold": "OFF"
70    },
71    {
72        "category": "HARM_CATEGORY_HATE_SPEECH",
73        "threshold": "OFF"
74    },
75    {
76        "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
77        "threshold": "OFF"
78    },
79    {
80        "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
81        "threshold": "OFF"
82    },
83    {
84        "category": 'HARM_CATEGORY_CIVIC_INTEGRITY',
85        "threshold": 'OFF'
86    }
87]
88@dataclass
89class GeneratedText:
90    text: str
91    finish_reason: Optional[str] = None
92
93
94class ResponseWrapper:
95    def __init__(self, data: Dict[Any, Any]):
96        self._data = data
97        self._text = self._extract_text()
98        self._finish_reason = self._extract_finish_reason()
99        self._prompt_token_count = self._extract_prompt_token_count()
100        self._candidates_token_count = self._extract_candidates_token_count()
101        self._total_token_count = self._extract_total_token_count()
102        self._thoughts = self._extract_thoughts()
103        self._json_dumps = json.dumps(self._data, indent=4, ensure_ascii=False)
104
105    def _extract_thoughts(self) -> Optional[str]:
106        try:
107            for part in self._data['candidates'][0]['content']['parts']:
108                if 'thought' in part:
109                    return part['text']
110            return ""
111        except (KeyError, IndexError):
112            return ""
113
114    def _extract_text(self) -> str:
115        try:
116            for part in self._data['candidates'][0]['content']['parts']:
117                if 'thought' not in part:
118                    return part['text']
119            return ""
120        except (KeyError, IndexError):
121            return ""
122
123    def _extract_finish_reason(self) -> Optional[str]:
124        try:
125            return self._data['candidates'][0].get('finishReason')
126        except (KeyError, IndexError):
127            return None
128
129    def _extract_prompt_token_count(self) -> Optional[int]:
130        try:
131            return self._data['usageMetadata'].get('promptTokenCount')
132        except (KeyError):
133            return None
134
135    def _extract_candidates_token_count(self) -> Optional[int]:
136        try:
137            return self._data['usageMetadata'].get('candidatesTokenCount')
138        except (KeyError):
139            return None
140
141    def _extract_total_token_count(self) -> Optional[int]:
142        try:
143            return self._data['usageMetadata'].get('totalTokenCount')
144        except (KeyError):
145            return None
146
147    @property
148    def text(self) -> str:
149        return self._text
150
151    @property
152    def finish_reason(self) -> Optional[str]:
153        return self._finish_reason
154
155    @property
156    def prompt_token_count(self) -> Optional[int]:
157        return self._prompt_token_count
158
159    @property
160    def candidates_token_count(self) -> Optional[int]:
161        return self._candidates_token_count
162
163    @property
164    def total_token_count(self) -> Optional[int]:
165        return self._total_token_count
166
167    @property
168    def thoughts(self) -> Optional[str]:
169        return self._thoughts
170
171    @property
172    def json_dumps(self) -> str:
173        return self._json_dumps
174
175class APIKeyManager:
176    def __init__(self):
177        self.api_keys = re.findall(r"AIzaSy[a-zA-Z0-9_-]{33}", os.environ.get('KeyArray'))
178        self.current_index = random.randint(0, len(self.api_keys) - 1)
179
180    def get_available_key(self):
181        num_keys = len(self.api_keys)
182        for _ in range(num_keys):
183            if self.current_index >= num_keys:
184                self.current_index = 0
185            current_key = self.api_keys[self.current_index]
186            self.current_index += 1
187
188            if current_key not in api_key_blacklist:
189                return current_key
190
191        logger.error("所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
192        return None
193
194    def show_all_keys(self):
195        logger.info(f"当前可用API key个数: {len(self.api_keys)} ")
196        for i, api_key in enumerate(self.api_keys):
197            logger.info(f"API Key{i}: {api_key[:8]}...{api_key[-3:]}")
198
199    def blacklist_key(self, key):
200        logger.warning(f"{key[:8]} → 暂时禁用 {api_key_blacklist_duration} 秒")
201        api_key_blacklist.add(key)
202
203        scheduler.add_job(lambda: api_key_blacklist.discard(key), 'date', run_date=datetime.now() + timedelta(seconds=api_key_blacklist_duration))
204
205key_manager = APIKeyManager()
206key_manager.show_all_keys()
207current_api_key = key_manager.get_available_key()
208
209def switch_api_key():
210    global current_api_key
211    key = key_manager.get_available_key()
212    if key:
213      current_api_key = key
214      logger.info(f"API key 替换为 → {current_api_key[:8]}...{current_api_key[-3:]}")
215    else:
216      logger.error("API key 替换失败,所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
217
218logger.info(f"当前 API key: {current_api_key[:8]}...{current_api_key[-3:]}")
219
220GEMINI_MODELS = [
221    {"id": "text-embedding-004"},
222    {"id": "gemini-1.5-flash-8b-latest"},
223    {"id": "gemini-1.5-flash-8b-exp-0924"},
224    {"id": "gemini-1.5-flash-latest"},
225    {"id": "gemini-1.5-flash-exp-0827"},
226    {"id": "gemini-1.5-pro-latest"},
227    {"id": "gemini-1.5-pro-exp-0827"},
228    {"id": "learnlm-1.5-pro-experimental"},
229    {"id": "gemini-exp-1114"},
230    {"id": "gemini-exp-1121"},
231    {"id": "gemini-exp-1206"},
232    {"id": "gemini-2.0-flash-exp"},
233    {"id": "gemini-2.0-flash-thinking-exp-1219"},
234    {"id": "gemini-2.0-flash-thinking-exp-01-21"},
235    {"id": "gemini-2.0-exp"},
236    {"id": "gemini-2.0-pro-exp"},
237    {"id": "gemini-2.0-pro-exp-01-28"}
238]
239
240@app.route('/')
241def index():
242    main_content = "Moonfanz Reminiproxy v2.3.5 2025-01-14"
243    html_template = """
244<!DOCTYPE html>
245<html>
246<head>
247<meta charset="utf-8">
248<script>
249function copyToClipboard(text) {
250  var textarea = document.createElement("textarea");
251  textarea.textContent = text;
252  textarea.style.position = "fixed";
253  document.body.appendChild(textarea);
254  textarea.select();
255  try {
256    return document.execCommand("copy");
257  } catch (ex) {
258    console.warn("Copy to clipboard failed.", ex);
259    return false;
260  } finally {
261    document.body.removeChild(textarea);
262  }
263}
264function copyLink(event) {
265  event.preventDefault();
266  const url = new URL(window.location.href);
267  const link = url.protocol + '//' + url.host + '/hf/v1';
268  copyToClipboard(link);
269  alert('链接已复制: ' + link);
270}
271</script>
272</head>
273<body>
274{{ main_content }}<br/><br/>完全开源、免费且禁止商用<br/><br/>点击复制反向代理: <a href="v1" onclick="copyLink(event)">点击我复制地址(不要自己写地址)</a><br/>聊天来源选择"自定义(兼容 OpenAI)"<br/>将复制的网址填入到自定义端点<br/>将设置password填入自定义API秘钥<br/><br/><br/>
275</body>
276</html>
277    """
278    return render_template_string(html_template, main_content=main_content)
279
280def is_within_rate_limit(api_key):
281    now = datetime.now()
282    if api_key not in request_counts:
283        request_counts[api_key] = deque()
284
285    while request_counts[api_key] and request_counts[api_key][0] < now - timedelta(seconds=LIMIT_WINDOW):
286        request_counts[api_key].popleft()
287
288    if len(request_counts[api_key]) >= MAX_REQUESTS:
289        earliest_request_time = request_counts[api_key][0]
290        wait_time = (earliest_request_time + timedelta(seconds=LIMIT_WINDOW)) - now
291        return False, wait_time.total_seconds()
292    else:
293        return True, 0
294
295def increment_request_count(api_key):
296    now = datetime.now()
297    if api_key not in request_counts:
298        request_counts[api_key] = deque()
299    request_counts[api_key].append(now)
300
301def handle_api_error(error, attempt, current_api_key):
302    if attempt > MAX_RETRIES:
303        logger.error(f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入")
304        return 0, jsonify({
305            'error': {
306                'message': f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入",
307                'type': 'max_retries_exceeded'
308            }
309        })
310
311    if isinstance(error, requests.exceptions.HTTPError):
312        status_code = error.response.status_code
313
314        if status_code == 400:
315
316            try:
317                error_data = error.response.json()
318                if 'error' in error_data:
319                    if error_data['error'].get('code') == "invalid_argument":
320                        logger.error(f"{current_api_key[:8]} ... {current_api_key[-3:]} → 无效,可能已过期或被删除")
321                        key_manager.blacklist_key(current_api_key)
322                        switch_api_key()
323                        return 0, None
324                    error_message = error_data['error'].get('message', 'Bad Request')
325                    error_type = error_data['error'].get('type', 'invalid_request_error')
326                    logger.warning(f"400 错误请求: {error_message}")
327                    return 2, jsonify({'error': {'message': error_message, 'type': error_type}})
328            except ValueError:
329                logger.warning("400 错误请求:响应不是有效的JSON格式")
330                return 2, jsonify({'error': {'message': '', 'type': 'invalid_request_error'}})
331
332        elif status_code == 429:
333            logger.warning(
334                f"{current_api_key[:8]} ... {current_api_key[-3:]} → 429 官方资源耗尽 → 立即重试..."
335            )
336            key_manager.blacklist_key(current_api_key)
337            switch_api_key()
338            return 0, None
339
340        elif status_code == 403:
341            logger.error(
342                f"{current_api_key[:8]} ... {current_api_key[-3:]} → 403 权限被拒绝,该 API KEY 可能已经被官方封禁"
343            )
344            key_manager.blacklist_key(current_api_key)
345            switch_api_key()
346            return 0, None
347
348        elif status_code == 500:
349            logger.warning(
350                f"{current_api_key[:8]} ... {current_api_key[-3:]} → 500 服务器内部错误 → 立即重试..."
351            )
352            switch_api_key()
353            return 0, None
354
355        elif status_code == 503:
356            logger.warning(
357                f"{current_api_key[:8]} ... {current_api_key[-3:]} → 503 服务不可用 → 立即重试..."
358            )
359            switch_api_key()
360            return 0, None
361
362        else:
363            logger.warning(
364                f"{current_api_key[:8]} ... {current_api_key[-3:]} → {status_code} 未知错误/模型不可用 → 不重试..."
365            )
366            switch_api_key()
367            return 2, None
368
369    elif isinstance(error, requests.exceptions.ConnectionError):
370        delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
371        logger.warning(f"连接错误 → 立即重试...")
372        time.sleep(delay)
373        return 0, None
374
375    elif isinstance(error, requests.exceptions.Timeout):
376        delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
377        logger.warning(f"请求超时 → 立即重试...")
378        time.sleep(delay)
379        return 0, None
380
381    else:
382        logger.error(f"发生未知错误: {error}")
383        return 0, jsonify({
384            'error': {
385                'message': f"发生未知错误: {error}",
386                'type': 'unknown_error'
387            }
388        })
389
390@app.route('/hf/v1/chat/completions', methods=['POST'])
391def chat_completions():
392    is_authenticated, auth_error, status_code = func.authenticate_request(request)
393    if not is_authenticated:
394        return auth_error if auth_error else jsonify({'error': '未授权'}), status_code if status_code else 401
395
396    request_data = request.get_json()
397    messages = request_data.get('messages', [])
398    model = request_data.get('model', 'gemini-2.0-flash-exp')
399    temperature = request_data.get('temperature', 1)
400    max_tokens = request_data.get('max_tokens', 8192)
401    show_thoughts = request_data.get('show_thoughts', False)
402    stream = request_data.get('stream', False)
403    use_system_prompt = request_data.get('use_system_prompt', False)
404    hint = "流式" if stream else "非流"
405    logger.info(f"\n{model} [{hint}] → {current_api_key[:8]}...{current_api_key[-3:]}")
406    is_thinking = 'thinking' in model
407    api_version = 'v1alpha' if is_thinking else 'v1beta'
408    response_type = 'streamGenerateContent' if stream else 'generateContent'
409    is_SSE = '&alt=sse' if stream else ''
410
411    contents, system_instruction, error_response = func.process_messages_for_gemini(messages, use_system_prompt)
412
413    if error_response:
414        logger.error(f"处理输入消息时出错↙\n {error_response}")
415        return jsonify(error_response), 400
416
417    def do_request(current_api_key, attempt):
418        isok, time_remaining = is_within_rate_limit(current_api_key)
419        if not isok:
420            logger.warning(f"暂时超过限额,该API key将在 {time_remaining} 秒后启用...")
421            switch_api_key()
422            return 0, None
423
424        increment_request_count(current_api_key)
425
426
427        url = f"https://generativelanguage.googleapis.com/{api_version}/models/{model}:{response_type}?key={current_api_key}{is_SSE}"
428        headers = {
429            "Content-Type": "application/json",
430        }
431
432        data = {
433            "contents": contents,
434            "generationConfig": {
435                "temperature": temperature,
436                "maxOutputTokens": max_tokens,
437            },
438            "safetySettings": safety_settings_g2 if 'gemini-2.0-flash-exp' in model else safety_settings,
439        }
440        if system_instruction:
441            data["system_instruction"] = system_instruction
442
443        try:
444            response = requests.post(url, headers=headers, json=data, stream=True)
445            response.raise_for_status()
446
447            if stream:
448                return 1, response
449            else:
450                return 1, ResponseWrapper(response.json())
451        except requests.exceptions.RequestException as e:
452            return handle_api_error(e, attempt, current_api_key)
453
454    def generate_stream(response):
455        logger.info(f"流式开始 →")
456        buffer = b""
457        try:
458            for line in response.iter_lines():
459                if not line:
460                    continue
461                try:
462                    if line.startswith(b'data: '):
463                        line = line[6:]
464
465                    buffer += line
466
467                    try:
468                        data = json.loads(buffer.decode('utf-8'))
469                        buffer = b""
470                        if 'candidates' in data and data['candidates']:
471                            candidate = data['candidates'][0]
472                            if 'content' in candidate:
473                                content = candidate['content']
474                                if 'parts' in content and content['parts']:
475                                    parts = content['parts']
476                                    if is_thinking and not show_thoughts:
477                                        parts = [part for part in parts if not part.get('thought')]
478                                    if parts:
479                                        text = parts[0].get('text', '')
480                                        finish_reason = candidate.get('finishReason')
481
482                                        if text:
483                                            data = {
484                                                'choices': [{
485                                                    'delta': {
486                                                        'content': text
487                                                    },
488                                                    'finish_reason': finish_reason,
489                                                    'index': 0
490                                                }],
491                                                'object': 'chat.completion.chunk'
492                                            }
493                                            yield f"data: {json.dumps(data)}\n\n"
494
495                            if candidate.get("finishReason") and candidate.get("finishReason") != "STOP":
496                                error_message = {
497                                    "error": {
498                                        "code": "content_filter",
499                                        "message": f"模型的响应因违反内容政策而被标记:{candidate.get('finishReason')}",
500                                        "status": candidate.get("finishReason"),
501                                        "details": []
502                                    }
503                                }
504                                logger.warning(f"模型的响应因违反内容政策而被标记: {candidate.get('finishReason')}")
505                                yield f"data: {json.dumps(error_message)}\n\n"
506                                break
507
508                            if 'safetyRatings' in candidate:
509                                for rating in candidate['safetyRatings']:
510                                    if rating['probability'] == 'HIGH':
511                                        error_message = {
512                                            "error": {
513                                                "code": "content_filter",
514                                                "message": f"模型的响应因高概率被标记为 {rating['category']}",
515                                                "status": "SAFETY_RATING_HIGH",
516                                                "details": [rating]
517                                            }
518                                        }
519                                        logger.warning(f"模型的响应因高概率被标记为 {rating['category']}")
520                                        yield f"data: {json.dumps(error_message)}\n\n"
521                                        break
522                                else:
523                                    continue
524                                break
525
526                    except json.JSONDecodeError:
527                        logger.debug(f"JSON解析错误, 当前缓冲区内容: {buffer}")
528                        continue
529
530                except Exception as e:
531                    logger.error(f"流式处理期间发生错误: {e}, 原始数据行↙\n{line}")
532                    yield f"data: {json.dumps({'error': str(e)})}\n\n"
533
534            else:
535                yield f"data: {json.dumps({'choices': [{'delta': {}, 'finish_reason': 'stop', 'index': 0}]})}\n\n"
536                logger.info(f"流式结束 ←")
537                logger.info(f"200!")
538        except Exception as e:
539            logger.error(f"流式处理错误↙\n{e}")
540            yield f"data: {json.dumps({'error': str(e)})}\n\n"
541
542    attempt = 0
543    success = 0
544    response = None
545    for attempt in range(1, MAX_RETRIES + 1):
546        logger.info(f"第 {attempt}/{MAX_RETRIES} 次尝试 ...")
547        success, response = do_request(current_api_key, attempt)
548
549        if success == 0:
550            continue
551        elif success == 1 and response is None:
552            continue
553        elif success == 1 and stream:
554            return Response(
555                stream_with_context(generate_stream(response)),
556                mimetype='text/event-stream'
557            )
558        elif success == 1 and isinstance(response, ResponseWrapper):
559            try:
560                text_content = response.text
561                prompt_tokens = response.prompt_token_count
562                completion_tokens = response.candidates_token_count
563                total_tokens = response.total_token_count
564                finish_reason = response.finish_reason
565
566                if text_content == '':
567                    error_message = None
568                    if response._data and 'error' in response._data:
569                        error_message = response._data['error'].get('message')
570                    if error_message:
571                        logger.error(f"生成内容失败,API 返回错误: {error_message}")
572                    else:
573                        logger.error(f"生成内容失败: text_content 为空")
574                    continue
575
576                if is_thinking and show_thoughts:
577                    text_content = response.thoughts + '\n' + text_content
578
579            except AttributeError as e:
580                logger.error(f"处理响应失败,缺少必要的属性: {e}")
581                logger.error(f"原始响应: {response._data}")
582                continue
583
584            except Exception as e:
585                logger.error(f"处理响应失败: {e}")
586                continue
587
588            response_data = {
589                'id': 'chatcmpl-xxxxxxxxxxxx',
590                'object': 'chat.completion',
591                'created': int(datetime.now().timestamp()),
592                'model': model,
593                'choices': [{
594                    'index': 0,
595                    'message': {
596                        'role': 'assistant',
597                        'content': text_content
598                    },
599                    'finish_reason': finish_reason
600                }],
601                'usage': {
602                    'prompt_tokens': prompt_tokens,
603                    'completion_tokens': completion_tokens,
604                    'total_tokens': total_tokens
605                }
606            }
607            logger.info(f"200!")
608            return jsonify(response_data)
609        elif success == 1 and isinstance(response, tuple):
610            return response[1], response[0]
611        elif success == 2:
612            logger.error(f"{model} 可能暂时不可用,请更换模型或未来一段时间再试")
613            response = {
614                'error': {
615                    'message': f'{model} 可能暂时不可用,请更换模型或未来一段时间再试',
616                    'type': 'internal_server_error'
617                }
618            }
619            return jsonify(response), 503
620    else:
621        logger.error(f"{MAX_RETRIES} 次尝试均失败,请重试或等待官方恢复")
622        response = {
623            'error': {
624                'message': f'{MAX_RETRIES} 次尝试均失败,请重试或等待官方恢复',
625                'type': 'internal_server_error'
626            }
627        }
628        return jsonify(response), 500 if response is not None else 503
629
630@app.route('/hf/v1/models', methods=['GET'])
631def list_models():
632    response = {"object": "list", "data": GEMINI_MODELS}
633    return jsonify(response)
634
635@app.route('/hf/v1/embeddings', methods=['POST'])
636def embeddings():
637    data = request.get_json()
638    model_input = data.get("input")
639    model = data.get("model", "text-embedding-004")
640    if not model_input:
641        return jsonify({"error": "没有提供输入"}), 400
642
643    if isinstance(model_input, str):
644        model_input = [model_input]
645
646    gemini_request = {
647        "model": f"models/{model}",
648        "content": {
649            "parts": [{"text": text} for text in model_input]
650        }
651    }
652
653    gemini_url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:embedContent?key={current_api_key}"
654    headers = {"Content-Type": "application/json"}
655    try:
656        gemini_response = requests.post(gemini_url, json=gemini_request, headers=headers)
657        gemini_response.raise_for_status()
658
659        response_json = gemini_response.json()
660        embeddings_data = []
661        if 'embedding' in response_json:
662          embeddings_data.append({
663              "object": "embedding",
664              "embedding": response_json['embedding']['values'],
665              "index": 0,
666          })
667        elif 'embeddings' in response_json:
668          for i, embedding in enumerate(response_json['embeddings']):
669              embeddings_data.append({
670                  "object": "embedding",
671                  "embedding": embedding['values'],
672                  "index": i,
673              })
674
675        client_response = {
676            "object": "list",
677            "data": embeddings_data,
678            "model": model,
679            "usage": {
680                "prompt_tokens": 0,
681                "total_tokens": 0,
682            },
683        }
684        switch_api_key()
685        return jsonify(client_response)
686
687    except requests.exceptions.RequestException as e:
688        print(f"请求Embeddings失败↙\: {e}")
689        return jsonify({"error": str(e)}), 500
690
691def keep_alive():
692    try:
693        response = requests.get("http://127.0.0.1:7860/", timeout=10)
694        response.raise_for_status()
695        print(f"Keep alive ping successful: {response.status_code} at {time.ctime()}")
696    except requests.exceptions.RequestException as e:
697        print(f"Keep alive ping failed: {e} at {time.ctime()}")
698
699if __name__ == '__main__':
700    scheduler = BackgroundScheduler()
701
702    scheduler.add_job(keep_alive, 'interval', hours=12)
703    scheduler.start()
704    logger.info(f"Reminiproxy v2.3.5 启动")
705    logger.info(f"最大尝试次数/MaxRetries: {MAX_RETRIES}")
706    logger.info(f"最大请求次数/MaxRequests: {MAX_REQUESTS}")
707    logger.info(f"请求限额窗口/LimitWindow: {LIMIT_WINDOW} 秒")
708
709    app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))