Add RateLimiter

This commit is contained in:
itqop 2025-06-28 20:06:40 +03:00
parent acaa9cae29
commit 4585d181f8
3 changed files with 80 additions and 1 deletions

View File

@ -20,6 +20,15 @@ class CLIInterface:
print(f"📊 Максимум заявок: {settings.application.max_applications}") print(f"📊 Максимум заявок: {settings.application.max_applications}")
ai_status = "✅ Доступен" if settings.enable_ai_matching() else "❌ Недоступен" ai_status = "✅ Доступен" if settings.enable_ai_matching() else "❌ Недоступен"
print(f"🤖 Gemini AI: {ai_status}") print(f"🤖 Gemini AI: {ai_status}")
if settings.enable_ai_matching():
try:
from ..services.gemini_service import GeminiAIService
gemini_service = GeminiAIService()
print(f" {gemini_service.get_api_status()}")
except Exception:
pass
browser_mode = "Фоновый" if settings.browser.headless else "Видимый" browser_mode = "Фоновый" if settings.browser.headless else "Видимый"
print(f"🌐 Режим браузера: {browser_mode}") print(f"🌐 Режим браузера: {browser_mode}")
@ -102,6 +111,14 @@ class CLIInterface:
else: else:
print("\n😕 Заявки не были отправлены") print("\n😕 Заявки не были отправлены")
if settings.enable_ai_matching():
try:
from ..services.gemini_service import GeminiAIService
gemini_service = GeminiAIService()
print(f"\n{gemini_service.get_api_status()}")
except Exception:
pass
print(UIFormatter.create_separator(long=True)) print(UIFormatter.create_separator(long=True))
@staticmethod @staticmethod

View File

@ -76,6 +76,9 @@ class GeminiConfig:
base_url: str = AppConstants.GEMINI_BASE_URL base_url: str = AppConstants.GEMINI_BASE_URL
match_threshold: float = AppConstants.DEFAULT_AI_THRESHOLD match_threshold: float = AppConstants.DEFAULT_AI_THRESHOLD
max_requests_per_minute: int = 15
rate_limit_window_seconds: int = 61
@dataclass @dataclass
class ResumeConfig: class ResumeConfig:

View File

@ -1,7 +1,9 @@
import json import json
import requests import requests
import logging import logging
import time
from typing import Dict, Optional, Tuple, List from typing import Dict, Optional, Tuple, List
from collections import deque
import traceback import traceback
from pathlib import Path from pathlib import Path
@ -11,6 +13,48 @@ from ..models.vacancy import Vacancy
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RateLimiter:
"""Ограничитель скорости запросов к API"""
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.request_times = deque()
def wait_if_needed(self) -> None:
"""Ожидание если превышен лимит запросов"""
current_time = time.time()
while (self.request_times and
current_time - self.request_times[0] >= self.window_seconds):
self.request_times.popleft()
if len(self.request_times) >= self.max_requests:
wait_time = self.window_seconds - (current_time - self.request_times[0])
if wait_time > 0:
logger.info(f"⏳ Достигнут лимит {self.max_requests} запросов в минуту. "
f"Ожидание {wait_time:.1f} секунд...")
time.sleep(wait_time + 0.1)
self.request_times.append(current_time)
def get_remaining_requests(self) -> int:
"""Количество оставшихся запросов в текущем окне"""
current_time = time.time()
while (self.request_times and
current_time - self.request_times[0] >= self.window_seconds):
self.request_times.popleft()
return max(0, self.max_requests - len(self.request_times))
def get_status(self) -> str:
"""Статус rate limiter для логирования"""
remaining = self.get_remaining_requests()
return (f"📊 API лимит: {remaining}/{self.max_requests} запросов осталось "
f"(окно {self.window_seconds}с)")
class GeminiApiClient: class GeminiApiClient:
"""Клиент для работы с Gemini API""" """Клиент для работы с Gemini API"""
@ -19,8 +63,17 @@ class GeminiApiClient:
self.base_url = AppConstants.GEMINI_BASE_URL self.base_url = AppConstants.GEMINI_BASE_URL
self.model = AppConstants.GEMINI_MODEL self.model = AppConstants.GEMINI_MODEL
# Инициализируем rate limiter
self.rate_limiter = RateLimiter(
max_requests=settings.gemini.max_requests_per_minute,
window_seconds=settings.gemini.rate_limit_window_seconds
)
def generate_content(self, prompt: str) -> Optional[Dict]: def generate_content(self, prompt: str) -> Optional[Dict]:
"""Генерация контента через Gemini API""" """Генерация контента через Gemini API"""
# Применяем rate limiting
self.rate_limiter.wait_if_needed()
url = f"{self.base_url}/models/{self.model}:generateContent" url = f"{self.base_url}/models/{self.model}:generateContent"
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
@ -35,7 +88,7 @@ class GeminiApiClient:
} }
try: try:
logger.info("Отправка запроса к Gemini API") logger.info(f"Отправка запроса к Gemini API. {self.rate_limiter.get_status()}")
response = requests.post( response = requests.post(
url, url,
headers=headers, headers=headers,
@ -422,3 +475,9 @@ class GeminiAIService:
С уважением, С уважением,
Telegram @itqen""" Telegram @itqen"""
def get_api_status(self) -> str:
"""Получение статуса API лимитов"""
if not self.api_client:
return "❌ Gemini API недоступен"
return self.api_client.rate_limiter.get_status()