corrigido sistem de validação de chaves groq

This commit is contained in:
Fábio Cavalcanti 2025-01-24 21:06:13 -03:00
parent a25dc9c4e7
commit af20510b2b
3 changed files with 174 additions and 140 deletions

View File

@ -1,7 +1,17 @@
import aiohttp
import json
from typing import Optional, Tuple
from typing import Optional, Tuple, Any
from datetime import datetime
import logging
from storage import StorageHandler
import asyncio
logger = logging.getLogger("GROQHandler")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
async def test_groq_key(key: str) -> bool:
"""Teste se uma chave GROQ é válida e está funcionando."""
@ -15,89 +25,87 @@ async def test_groq_key(key: str) -> bool:
data = await response.json()
return bool(data.get("data"))
return False
except Exception:
except Exception as e:
logger.error(f"Erro ao testar chave GROQ: {e}")
return False
async def validate_transcription_response(response_text: str) -> bool:
"""Valide se a resposta da transcrição é significativa."""
try:
# Remove common whitespace and punctuation
cleaned_text = response_text.strip()
# Check minimum content length (adjustable threshold)
return len(cleaned_text) >= 10
except Exception:
except Exception as e:
logger.error(f"Erro ao validar resposta da transcrição: {e}")
return False
async def get_working_groq_key(storage) -> Optional[str]:
async def get_working_groq_key(storage: StorageHandler) -> Optional[str]:
"""Obtenha uma chave GROQ funcional do pool disponível."""
keys = storage.get_groq_keys()
for _ in range(len(keys)): # Try each key once
for _ in range(len(keys)):
key = storage.get_next_groq_key()
if key and await test_groq_key(key):
return key
if not key:
continue
storage.add_log("ERROR", "No working GROQ keys available")
penalized_until = storage.get_penalized_until(key)
if penalized_until and penalized_until > datetime.utcnow():
continue
if await test_groq_key(key):
return key
else:
storage.penalize_key(key, penalty_duration=300)
storage.add_log("ERROR", "Nenhuma chave GROQ funcional disponível.")
return None
async def handle_groq_request(url: str, headers: dict, data, storage, is_form_data: bool = False) -> Tuple[bool, dict, str]:
"""
Handle GROQ API request with retries and key rotation.
Suporta tanto JSON quanto FormData.
Returns: (success, response_data, error_message)
"""
async def handle_groq_request(
url: str,
headers: dict,
data: Any,
storage: StorageHandler,
is_form_data: bool = False
) -> Tuple[bool, dict, str]:
"""Lida com requisições para a API GROQ com suporte a retries e rotação de chaves."""
max_retries = len(storage.get_groq_keys())
for attempt in range(max_retries):
try:
storage.add_log("DEBUG", "Iniciando tentativa de requisição para GROQ", {
"url": url,
"is_form_data": is_form_data,
"attempt": attempt + 1
})
async with aiohttp.ClientSession() as session:
if is_form_data:
async with session.post(url, headers=headers, data=data) as response:
response_data = await response.json()
if response.status == 200 and response_data.get("text"):
return True, response_data, ""
else:
async with session.post(url, headers=headers, json=data) as response:
response_data = await response.json()
if response.status == 200:
# Validate response content
if "choices" in response_data and response_data["choices"]:
content = response_data["choices"][0].get("message", {}).get("content")
if content and await validate_transcription_response(content):
if response.status == 200 and response_data.get("choices"):
return True, response_data, ""
# Handle specific error cases
error_msg = response_data.get("error", {}).get("message", "")
if "organization_restricted" in error_msg or "invalid_api_key" in error_msg:
# Try next key
new_key = await get_working_groq_key(storage)
if new_key:
headers["Authorization"] = f"Bearer {new_key}"
storage.add_log("INFO", "Tentando nova chave GROQ após erro", {
"error": error_msg,
"attempt": attempt + 1
})
await asyncio.sleep(1)
continue
return False, {}, f"API Error: {error_msg}"
return False, response_data, error_msg
except Exception as e:
# Tratamento específico para erros de conexão
if "Connection" in str(e) and attempt < max_retries - 1:
storage.add_log("WARNING", "Erro de conexão, tentando novamente", {
"error": str(e),
"attempt": attempt + 1
})
await asyncio.sleep(1) # Espera 1 segundo antes de retry
storage.add_log("ERROR", "Erro na requisição", {"error": str(e)})
if attempt < max_retries - 1:
await asyncio.sleep(1)
continue
# Se for última tentativa ou outro tipo de erro
if attempt == max_retries - 1:
storage.add_log("ERROR", "Todas tentativas falharam", {
"error": str(e),
"total_attempts": max_retries
})
return False, {}, f"Request failed: {str(e)}"
continue
storage.add_log("ERROR", "Todas as chaves GROQ falharam")
return False, {}, "All GROQ keys exhausted"
storage.add_log("ERROR", "Todas as chaves GROQ falharam.")
return False, {}, "All GROQ keys exhausted."

View File

@ -7,6 +7,7 @@ from storage import StorageHandler
import os
import json
import tempfile
import traceback
from groq_handler import get_working_groq_key, validate_transcription_response, handle_groq_request
# Inicializa o storage handler
storage = StorageHandler()
@ -146,7 +147,7 @@ async def summarize_text_if_needed(text):
}
try:
success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage)
success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage, is_form_data=False)
if not success:
raise Exception(error)
@ -234,11 +235,12 @@ async def transcribe_audio(audio_source, apikey=None, remote_jid=None, from_me=F
elif not from_me: # Só detecta em mensagens recebidas
try:
# Realizar transcrição inicial sem idioma específico
with open(audio_source, 'rb') as audio_file:
data = aiohttp.FormData()
data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3')
data.add_field('file', audio_file, filename='audio.mp3')
data.add_field('model', 'whisper-large-v3')
success, response_data, error = await handle_groq_request(url, groq_headers, data, storage)
success, response_data, error = await handle_groq_request(url, groq_headers, data, storage, is_form_data=True)
if success:
initial_text = response_data.get("text", "")
@ -306,8 +308,9 @@ async def transcribe_audio(audio_source, apikey=None, remote_jid=None, from_me=F
try:
# Realizar transcrição
with open(audio_source, 'rb') as audio_file:
data = aiohttp.FormData()
data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3')
data.add_field('file', audio_file, filename='audio.mp3')
data.add_field('model', 'whisper-large-v3')
data.add_field('language', transcription_language)
@ -315,7 +318,7 @@ async def transcribe_audio(audio_source, apikey=None, remote_jid=None, from_me=F
data.add_field('response_format', 'verbose_json')
# Usar handle_groq_request para ter retry e validação
success, response_data, error = await handle_groq_request(url, groq_headers, data, storage)
success, response_data, error = await handle_groq_request(url, groq_headers, data, storage, is_form_data=True)
if not success:
raise Exception(f"Erro na transcrição: {error}")
@ -475,9 +478,9 @@ async def detect_language(text: str) -> str:
}
try:
success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage)
success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage, is_form_data=False)
if not success:
raise Exception(error)
raise Exception(f"Falha na detecção de idioma: {error}")
detected_language = response_data["choices"][0]["message"]["content"].strip().lower()
@ -697,9 +700,9 @@ async def translate_text(text: str, source_language: str, target_language: str)
}
try:
success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage)
success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage, is_form_data=False)
if not success:
raise Exception(error)
raise Exception(f"Falha na tradução: {error}")
translated_text = response_data["choices"][0]["message"]["content"].strip()

View File

@ -1,6 +1,6 @@
import json
import os
from typing import List, Dict
from typing import List, Dict, Optional
from datetime import datetime, timedelta
import traceback
import logging
@ -209,6 +209,29 @@ class StorageHandler:
return keys[counter % len(keys)]
def get_penalized_until(self, key: str) -> Optional[datetime]:
"""
Retorna o timestamp até quando a chave está penalizada, ou None se não estiver penalizada.
"""
penalized_key = self._get_redis_key(f"groq_key_penalized_{key}")
penalized_until = self.redis.get(penalized_key)
if penalized_until:
return datetime.fromisoformat(penalized_until)
return None
def penalize_key(self, key: str, penalty_duration: int):
"""
Penaliza uma chave por um tempo determinado (em segundos).
"""
penalized_key = self._get_redis_key(f"groq_key_penalized_{key}")
penalized_until = datetime.utcnow() + timedelta(seconds=penalty_duration)
self.redis.set(penalized_key, penalized_until.isoformat())
self.redis.expire(penalized_key, penalty_duration) # Expira a chave após o tempo de penalidade
self.add_log("INFO", "Chave GROQ penalizada", {
"key": key,
"penalized_until": penalized_until.isoformat()
})
def get_message_settings(self):
"""Obtém as configurações de mensagens."""
return {