CoolFace
Apppublic

fdgsdfg3/3

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
utils.py240 linesDownload Raw Back to app
1import random
2from fastapi import HTTPException, Request
3import time
4import re
5from datetime import datetime, timedelta
6from apscheduler.schedulers.background import BackgroundScheduler
7import os
8import requests
9import httpx
10from threading import Lock
11import logging
12import sys
13
14DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
15LOG_FORMAT_DEBUG = '%(asctime)s - %(levelname)s - [%(key)s]-%(request_type)s-[%(model)s]-%(status_code)s: %(message)s - %(error_message)s'
16LOG_FORMAT_NORMAL = '[%(key)s]-%(request_type)s-[%(model)s]-%(status_code)s: %(message)s'
17
18# 配置 logger
19logger = logging.getLogger("my_logger")
20logger.setLevel(logging.DEBUG)
21
22handler = logging.StreamHandler()
23# formatter = logging.Formatter('%(message)s')
24# handler.setFormatter(formatter)
25logger.addHandler(handler)
26
27def format_log_message(level, message, extra=None):
28    extra = extra or {}
29    log_values = {
30        'asctime': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
31        'levelname': level,
32        'key': extra.get('key', 'N/A'),
33        'request_type': extra.get('request_type', 'N/A'),
34        'model': extra.get('model', 'N/A'),
35        'status_code': extra.get('status_code', 'N/A'),
36        'error_message': extra.get('error_message', ''),
37        'message': message
38    }
39    log_format = LOG_FORMAT_DEBUG if DEBUG else LOG_FORMAT_NORMAL
40    return log_format % log_values
41
42
43class APIKeyManager:
44    def __init__(self):
45        self.api_keys = re.findall(
46            r"AIzaSy[a-zA-Z0-9_-]{33}", os.environ.get('GEMINI_API_KEYS', ""))
47        self.key_stack = [] # 初始化密钥栈
48        self._reset_key_stack() # 初始化时创建随机密钥栈
49        # self.api_key_blacklist = set()
50        # self.api_key_blacklist_duration = 60
51        self.scheduler = BackgroundScheduler()
52        self.scheduler.start()
53        self.tried_keys_for_request = set()  # 用于跟踪当前请求尝试中已试过的 key
54
55    def _reset_key_stack(self):
56        """创建并随机化密钥栈"""
57        shuffled_keys = self.api_keys[:]  # 创建 api_keys 的副本以避免直接修改原列表
58        random.shuffle(shuffled_keys)
59        self.key_stack = shuffled_keys
60
61
62    def get_available_key(self):
63        """从栈顶获取密钥,栈空时重新生成 (修改后)"""
64        while self.key_stack:
65            key = self.key_stack.pop()
66            # if key not in self.api_key_blacklist and key not in self.tried_keys_for_request:
67            if key not in self.tried_keys_for_request:
68                self.tried_keys_for_request.add(key)
69                return key
70
71        if not self.api_keys:
72            log_msg = format_log_message('ERROR', "没有配置任何 API 密钥!")
73            logger.error(log_msg)
74            return None
75
76        self._reset_key_stack() # 重新生成密钥栈
77
78        # 再次尝试从新栈中获取密钥 (迭代一次)
79        while self.key_stack:
80            key = self.key_stack.pop()
81            # if key not in self.api_key_blacklist and key not in self.tried_keys_for_request:
82            if key not in self.tried_keys_for_request:
83                self.tried_keys_for_request.add(key)
84                return key
85
86        return None
87
88
89    def show_all_keys(self):
90        log_msg = format_log_message('INFO', f"当前可用API key个数: {len(self.api_keys)} ")
91        logger.info(log_msg)
92        for i, api_key in enumerate(self.api_keys):
93            log_msg = format_log_message('INFO', f"API Key{i}: {api_key[:8]}...{api_key[-3:]}")
94            logger.info(log_msg)
95
96    # def blacklist_key(self, key):
97    #     log_msg = format_log_message('WARNING', f"{key[:8]} → 暂时禁用 {self.api_key_blacklist_duration} 秒")
98    #     logger.warning(log_msg)
99    #     self.api_key_blacklist.add(key)
100    #     self.scheduler.add_job(lambda: self.api_key_blacklist.discard(key), 'date',
101    #                            run_date=datetime.now() + timedelta(seconds=self.api_key_blacklist_duration))
102
103    def reset_tried_keys_for_request(self):
104        """在新的请求尝试时重置已尝试的 key 集合"""
105        self.tried_keys_for_request = set()
106
107
108def handle_gemini_error(error, current_api_key, key_manager) -> str:
109    if isinstance(error, requests.exceptions.HTTPError):
110        status_code = error.response.status_code
111        if status_code == 400:
112            try:
113                error_data = error.response.json()
114                if 'error' in error_data:
115                    if error_data['error'].get('code') == "invalid_argument":
116                        error_message = "无效的 API 密钥"
117                        extra_log_invalid_key = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
118                        log_msg = format_log_message('ERROR', f"{current_api_key[:8]} ... {current_api_key[-3:]} → 无效,可能已过期或被删除", extra=extra_log_invalid_key)
119                        logger.error(log_msg)
120                        # key_manager.blacklist_key(current_api_key)
121                        
122                        return error_message
123                    error_message = error_data['error'].get(
124                        'message', 'Bad Request')
125                    extra_log_400 = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
126                    log_msg = format_log_message('WARNING', f"400 错误请求: {error_message}", extra=extra_log_400)
127                    logger.warning(log_msg)
128                    return f"400 错误请求: {error_message}"
129            except ValueError:
130                error_message = "400 错误请求:响应不是有效的JSON格式"
131                extra_log_400_json = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
132                log_msg = format_log_message('WARNING', error_message, extra=extra_log_400_json)
133                logger.warning(log_msg)
134                return error_message
135
136        elif status_code == 429:
137            error_message = "API 密钥配额已用尽或其他原因"
138            extra_log_429 = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
139            log_msg = format_log_message('WARNING', f"{current_api_key[:8]} ... {current_api_key[-3:]} → 429 官方资源耗尽或其他原因", extra=extra_log_429)
140            logger.warning(log_msg)
141            # key_manager.blacklist_key(current_api_key)
142             
143            return error_message
144
145        elif status_code == 403:
146            error_message = "权限被拒绝"
147            extra_log_403 = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
148            log_msg = format_log_message('ERROR', f"{current_api_key[:8]} ... {current_api_key[-3:]} → 403 权限被拒绝", extra=extra_log_403)
149            logger.error(log_msg)
150            # key_manager.blacklist_key(current_api_key)
151            
152            return error_message
153        elif status_code == 500:
154            error_message = "服务器内部错误"
155            extra_log_500 = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
156            log_msg = format_log_message('WARNING', f"{current_api_key[:8]} ... {current_api_key[-3:]} → 500 服务器内部错误", extra=extra_log_500)
157            logger.warning(log_msg)
158            
159            return "Gemini API 内部错误"
160
161        elif status_code == 503:
162            error_message = "服务不可用"
163            extra_log_503 = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
164            log_msg = format_log_message('WARNING', f"{current_api_key[:8]} ... {current_api_key[-3:]} → 503 服务不可用", extra=extra_log_503)
165            logger.warning(log_msg)
166            
167            return "Gemini API 服务不可用"
168        else:
169            error_message = f"未知错误: {status_code}"
170            extra_log_other = {'key': current_api_key[:8], 'status_code': status_code, 'error_message': error_message}
171            log_msg = format_log_message('WARNING', f"{current_api_key[:8]} ... {current_api_key[-3:]} → {status_code} 未知错误", extra=extra_log_other)
172            logger.warning(log_msg)
173            
174            return f"未知错误/模型不可用: {status_code}"
175
176    elif isinstance(error, requests.exceptions.ConnectionError):
177        error_message = "连接错误"
178        log_msg = format_log_message('WARNING', error_message, extra={'error_message': error_message})
179        logger.warning(log_msg)
180        return error_message
181
182    elif isinstance(error, requests.exceptions.Timeout):
183        error_message = "请求超时"
184        log_msg = format_log_message('WARNING', error_message, extra={'error_message': error_message})
185        logger.warning(log_msg)
186        return error_message
187    else:
188        error_message = f"发生未知错误: {error}"
189        log_msg = format_log_message('ERROR', error_message, extra={'error_message': error_message})
190        logger.error(log_msg)
191        return error_message
192
193
194async def test_api_key(api_key: str) -> bool:
195    """
196    测试 API 密钥是否有效。
197    """
198    try:
199        url = "https://generativelanguage.googleapis.com/v1beta/models?key={}".format(api_key)
200        async with httpx.AsyncClient() as client:
201            response = await client.get(url)
202            response.raise_for_status()
203            return True
204    except Exception:
205        return False
206
207
208rate_limit_data = {}
209rate_limit_lock = Lock()
210
211
212def protect_from_abuse(request: Request, max_requests_per_minute: int = 30, max_requests_per_day_per_ip: int = 600):
213    now = int(time.time())
214    minute = now // 60
215    day = now // (60 * 60 * 24)
216
217    minute_key = f"{request.url.path}:{minute}"
218    day_key = f"{request.client.host}:{day}"
219
220    with rate_limit_lock:
221        minute_count, minute_timestamp = rate_limit_data.get(
222            minute_key, (0, now))
223        if now - minute_timestamp >= 60:
224            minute_count = 0
225            minute_timestamp = now
226        minute_count += 1
227        rate_limit_data[minute_key] = (minute_count, minute_timestamp)
228
229        day_count, day_timestamp = rate_limit_data.get(day_key, (0, now))
230        if now - day_timestamp >= 86400:
231            day_count = 0
232            day_timestamp = now
233        day_count += 1
234        rate_limit_data[day_key] = (day_count, day_timestamp)
235
236    if minute_count > max_requests_per_minute:
237        raise HTTPException(status_code=429, detail={
238            "message": "Too many requests per minute", "limit": max_requests_per_minute})
239    if day_count > max_requests_per_day_per_ip:
240        raise HTTPException(status_code=429, detail={"message": "Too many requests per day from this IP", "limit": max_requests_per_day_per_ip})