melhoria no sistema de chaves groq

This commit is contained in:
Fábio Cavalcanti 2025-01-23 15:19:39 -03:00
parent 3cd75903fc
commit be82707ccc
3 changed files with 272 additions and 175 deletions

98
groq_handler.py Normal file
View File

@ -0,0 +1,98 @@
import aiohttp
import json
from typing import Optional, Tuple
from datetime import datetime
async def test_groq_key(key: str) -> bool:
"""Teste se uma chave GROQ é válida e está funcionando."""
url = "https://api.groq.com/openai/v1/models"
headers = {"Authorization": f"Bearer {key}"}
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
if response.status == 200:
data = await response.json()
return bool(data.get("data"))
return False
except Exception:
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:
return False
async def get_working_groq_key(storage) -> 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
key = storage.get_next_groq_key()
if key and await test_groq_key(key):
return key
storage.add_log("ERROR", "No working GROQ keys available")
return None
async def handle_groq_request(url: str, headers: dict, data: dict, storage) -> Tuple[bool, dict, str]:
"""
Handle GROQ API request with retries and key rotation.
Returns: (success, response_data, error_message)
"""
max_retries = len(storage.get_groq_keys())
for attempt in range(max_retries):
try:
async with aiohttp.ClientSession() as session:
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):
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
})
continue
return False, {}, f"API Error: {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
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"

View File

@ -252,7 +252,7 @@ def login_page():
# Modificar a função de logout no dashboard # Modificar a função de logout no dashboard
def dashboard(): def dashboard():
# Versão do sistema # Versão do sistema
APP_VERSION = "2.3.1" APP_VERSION = "2.3.2"
show_logo() show_logo()
st.sidebar.markdown('<div class="sidebar-header">TranscreveZAP - Menu</div>', unsafe_allow_html=True) st.sidebar.markdown('<div class="sidebar-header">TranscreveZAP - Menu</div>', unsafe_allow_html=True)

View File

@ -7,7 +7,7 @@ from storage import StorageHandler
import os import os
import json import json
import tempfile import tempfile
from groq_handler import get_working_groq_key, validate_transcription_response, handle_groq_request
# Inicializa o storage handler # Inicializa o storage handler
storage = StorageHandler() storage = StorageHandler()
@ -54,7 +54,9 @@ async def summarize_text_if_needed(text):
"redis_value": redis_client.get("TRANSCRIPTION_LANGUAGE") "redis_value": redis_client.get("TRANSCRIPTION_LANGUAGE")
}) })
url_completions = "https://api.groq.com/openai/v1/chat/completions" url_completions = "https://api.groq.com/openai/v1/chat/completions"
groq_key = await get_groq_key() groq_key = await get_working_groq_key(storage)
if not groq_key:
raise Exception("Nenhuma chave GROQ disponível")
headers = { headers = {
"Authorization": f"Bearer {groq_key}", "Authorization": f"Bearer {groq_key}",
"Content-Type": "application/json", "Content-Type": "application/json",
@ -144,25 +146,29 @@ async def summarize_text_if_needed(text):
} }
try: try:
async with aiohttp.ClientSession() as session: success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage)
storage.add_log("DEBUG", "Enviando requisição para API GROQ") if not success:
async with session.post(url_completions, headers=headers, json=json_data) as summary_response: raise Exception(error)
if summary_response.status == 200:
summary_result = await summary_response.json() summary_text = response_data["choices"][0]["message"]["content"]
summary_text = summary_result["choices"][0]["message"]["content"] # Validar se o resumo não está vazio
storage.add_log("INFO", "Resumo gerado com sucesso", { if not await validate_transcription_response(summary_text):
"original_length": len(text), storage.add_log("ERROR", "Resumo vazio ou inválido recebido")
"summary_length": len(summary_text), raise Exception("Resumo vazio ou inválido recebido")
"language": language # Validar se o resumo é menor que o texto original
}) if len(summary_text) >= len(text):
return summary_text storage.add_log("WARNING", "Resumo maior que texto original", {
else: "original_length": len(text),
error_text = await summary_response.text() "summary_length": len(summary_text)
storage.add_log("ERROR", "Erro na API GROQ", { })
"error": error_text, storage.add_log("INFO", "Resumo gerado com sucesso", {
"status": summary_response.status "original_length": len(text),
}) "summary_length": len(summary_text),
raise Exception(f"Erro ao resumir o texto: {error_text}") "language": language
})
return summary_text
except Exception as e: except Exception as e:
storage.add_log("ERROR", "Erro no processo de resumo", { storage.add_log("ERROR", "Erro no processo de resumo", {
"error": str(e), "error": str(e),
@ -190,7 +196,9 @@ async def transcribe_audio(audio_source, apikey=None, remote_jid=None, from_me=F
}) })
url = "https://api.groq.com/openai/v1/audio/transcriptions" url = "https://api.groq.com/openai/v1/audio/transcriptions"
groq_key = await get_groq_key() groq_key = await get_working_groq_key(storage)
if not groq_key:
raise Exception("Nenhuma chave GROQ disponível")
groq_headers = {"Authorization": f"Bearer {groq_key}"} groq_headers = {"Authorization": f"Bearer {groq_key}"}
# Inicializar variáveis # Inicializar variáveis
@ -230,25 +238,23 @@ async def transcribe_audio(audio_source, apikey=None, remote_jid=None, from_me=F
data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3') data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3')
data.add_field('model', 'whisper-large-v3') data.add_field('model', 'whisper-large-v3')
async with aiohttp.ClientSession() as session: success, response_data, error = await handle_groq_request(url, groq_headers, data, storage)
async with session.post(url, headers=groq_headers, data=data) as response: if success:
if response.status == 200: initial_text = response_data.get("text", "")
initial_result = await response.json()
initial_text = initial_result.get("text", "")
# Detectar idioma do texto transcrito # Detectar idioma do texto transcrito
detected_lang = await detect_language(initial_text) detected_lang = await detect_language(initial_text)
# Salvar no cache E na configuração do contato # Salvar no cache E na configuração do contato
storage.cache_language_detection(contact_id, detected_lang) storage.cache_language_detection(contact_id, detected_lang)
storage.set_contact_language(contact_id, detected_lang) storage.set_contact_language(contact_id, detected_lang)
contact_language = detected_lang contact_language = detected_lang
storage.add_log("INFO", "Idioma detectado e configurado", { storage.add_log("INFO", "Idioma detectado e configurado", {
"language": detected_lang, "language": detected_lang,
"remote_jid": remote_jid, "remote_jid": remote_jid,
"auto_detected": True "auto_detected": True
}) })
except Exception as e: except Exception as e:
storage.add_log("WARNING", "Erro na detecção automática de idioma", { storage.add_log("WARNING", "Erro na detecção automática de idioma", {
"error": str(e), "error": str(e),
@ -307,23 +313,19 @@ async def transcribe_audio(audio_source, apikey=None, remote_jid=None, from_me=F
if use_timestamps: if use_timestamps:
data.add_field('response_format', 'verbose_json') 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)
if not success:
raise Exception(f"Erro na transcrição: {error}")
transcription = format_timestamped_result(response_data) if use_timestamps else response_data.get("text", "")
# Realizar transcrição # Validar o conteúdo da transcrição
async with aiohttp.ClientSession() as session: if not await validate_transcription_response(transcription):
async with session.post(url, headers=groq_headers, data=data) as response: storage.add_log("ERROR", "Transcrição vazia ou inválida recebida")
if response.status != 200: raise Exception("Transcrição vazia ou inválida recebida")
error_text = await response.text()
storage.add_log("ERROR", "Erro na transcrição", {
"error": error_text,
"status": response.status
})
raise Exception(f"Erro na transcrição: {error_text}")
result = await response.json()
# Processar resposta baseado no formato
transcription = format_timestamped_result(result) if use_timestamps else result.get("text", "")
# Detecção automática para novos contatos # Detecção automática para novos contatos
if (is_private and storage.get_auto_language_detection() and if (is_private and storage.get_auto_language_detection() and
not from_me and not contact_language): not from_me and not contact_language):
@ -434,7 +436,10 @@ async def detect_language(text: str) -> str:
} }
url_completions = "https://api.groq.com/openai/v1/chat/completions" url_completions = "https://api.groq.com/openai/v1/chat/completions"
groq_key = await get_groq_key() groq_key = await get_working_groq_key(storage)
if not groq_key:
raise Exception("Nenhuma chave GROQ disponível")
headers = { headers = {
"Authorization": f"Bearer {groq_key}", "Authorization": f"Bearer {groq_key}",
"Content-Type": "application/json", "Content-Type": "application/json",
@ -470,32 +475,25 @@ async def detect_language(text: str) -> str:
} }
try: try:
async with aiohttp.ClientSession() as session: success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage)
storage.add_log("DEBUG", "Enviando requisição para API GROQ - Detecção de idioma") if not success:
async with session.post(url_completions, headers=headers, json=json_data) as response: raise Exception(error)
if response.status == 200:
result = await response.json() detected_language = response_data["choices"][0]["message"]["content"].strip().lower()
detected_language = result["choices"][0]["message"]["content"].strip().lower()
# Validar o resultado
# Validar o resultado if detected_language not in SUPPORTED_LANGUAGES:
if detected_language not in SUPPORTED_LANGUAGES: storage.add_log("WARNING", "Idioma detectado não suportado", {
storage.add_log("WARNING", "Idioma detectado não suportado", { "detected": detected_language,
"detected": detected_language, "fallback": "en"
"fallback": "en" })
}) detected_language = "en"
detected_language = "en"
storage.add_log("INFO", "Idioma detectado com sucesso", {
storage.add_log("INFO", "Idioma detectado com sucesso", { "detected_language": detected_language
"detected_language": detected_language })
}) return detected_language
return detected_language
else:
error_text = await response.text()
storage.add_log("ERROR", "Erro na detecção de idioma", {
"error": error_text,
"status": response.status
})
raise Exception(f"Erro na detecção de idioma: {error_text}")
except Exception as e: except Exception as e:
storage.add_log("ERROR", "Erro no processo de detecção de idioma", { storage.add_log("ERROR", "Erro no processo de detecção de idioma", {
"error": str(e), "error": str(e),
@ -639,96 +637,97 @@ async def format_message(transcription_text, summary_text=None):
return "\n\n".join(message_parts) return "\n\n".join(message_parts)
async def translate_text(text: str, source_language: str, target_language: str) -> str: async def translate_text(text: str, source_language: str, target_language: str) -> str:
""" """
Traduz o texto usando a API GROQ Traduz o texto usando a API GROQ
Args: Args:
text: Texto para traduzir text: Texto para traduzir
source_language: Código ISO 639-1 do idioma de origem source_language: Código ISO 639-1 do idioma de origem
target_language: Código ISO 639-1 do idioma de destino target_language: Código ISO 639-1 do idioma de destino
Returns: Returns:
str: Texto traduzido str: Texto traduzido
""" """
storage.add_log("DEBUG", "Iniciando tradução", { storage.add_log("DEBUG", "Iniciando tradução", {
"source_language": source_language, "source_language": source_language,
"target_language": target_language, "target_language": target_language,
"text_length": len(text) "text_length": len(text)
}) })
# Se os idiomas forem iguais, retorna o texto original # Se os idiomas forem iguais, retorna o texto original
if source_language == target_language: if source_language == target_language:
return text return text
url_completions = "https://api.groq.com/openai/v1/chat/completions" url_completions = "https://api.groq.com/openai/v1/chat/completions"
groq_key = await get_groq_key() groq_key = await get_working_groq_key(storage)
headers = { if not groq_key:
"Authorization": f"Bearer {groq_key}", raise Exception("Nenhuma chave GROQ disponível")
"Content-Type": "application/json",
} headers = {
"Authorization": f"Bearer {groq_key}",
# Prompt melhorado com contexto e instruções específicas "Content-Type": "application/json",
prompt = f""" }
Você é um tradutor profissional especializado em manter o tom e estilo do texto original.
prompt = f"""
Instruções: Você é um tradutor profissional especializado em manter o tom e estilo do texto original.
1. Traduza o texto de {source_language} para {target_language}
2. Preserve todas as formatações (negrito, itálico, emojis) Instruções:
3. Mantenha os mesmos parágrafos e quebras de linha 1. Traduza o texto de {source_language} para {target_language}
4. Preserve números, datas e nomes próprios 2. Preserve todas as formatações (negrito, itálico, emojis)
5. Não adicione ou remova informações 3. Mantenha os mesmos parágrafos e quebras de linha
6. Não inclua notas ou explicações 4. Preserve números, datas e nomes próprios
7. Mantenha o mesmo nível de formalidade 5. Não adicione ou remova informações
6. Não inclua notas ou explicações
Texto para tradução: 7. Mantenha o mesmo nível de formalidade
{text}
""" Texto para tradução:
{text}
json_data = { """
"messages": [{
"role": "system", json_data = {
"content": "Você é um tradutor profissional que mantém o estilo e formatação do texto original." "messages": [{
}, { "role": "system",
"role": "user", "content": "Você é um tradutor profissional que mantém o estilo e formatação do texto original."
"content": prompt }, {
}], "role": "user",
"model": "llama-3.3-70b-versatile", "content": prompt
"temperature": 0.3 }],
} "model": "llama-3.3-70b-versatile",
"temperature": 0.3
}
try: try:
async with aiohttp.ClientSession() as session: success, response_data, error = await handle_groq_request(url_completions, headers, json_data, storage)
storage.add_log("DEBUG", "Enviando requisição de tradução") if not success:
async with session.post(url_completions, headers=headers, json=json_data) as response: raise Exception(error)
if response.status == 200:
result = await response.json() translated_text = response_data["choices"][0]["message"]["content"].strip()
translated_text = result["choices"][0]["message"]["content"].strip()
# Verificar se a tradução manteve aproximadamente o mesmo tamanho
# Verificar se a tradução manteve aproximadamente o mesmo tamanho length_ratio = len(translated_text) / len(text)
length_ratio = len(translated_text) / len(text) if not (0.5 <= length_ratio <= 1.5):
if not (0.5 <= length_ratio <= 1.5): storage.add_log("WARNING", "Possível erro na tradução - diferença significativa no tamanho", {
storage.add_log("WARNING", "Possível erro na tradução - diferença significativa no tamanho", { "original_length": len(text),
"original_length": len(text), "translated_length": len(translated_text),
"translated_length": len(translated_text), "ratio": length_ratio
"ratio": length_ratio })
})
# Validar se a tradução não está vazia
storage.add_log("INFO", "Tradução concluída com sucesso", { if not await validate_transcription_response(translated_text):
"original_length": len(text), storage.add_log("ERROR", "Tradução vazia ou inválida recebida")
"translated_length": len(translated_text), raise Exception("Tradução vazia ou inválida recebida")
"ratio": length_ratio
}) storage.add_log("INFO", "Tradução concluída com sucesso", {
return translated_text "original_length": len(text),
else: "translated_length": len(translated_text),
error_text = await response.text() "ratio": length_ratio
storage.add_log("ERROR", "Erro na tradução", { })
"status": response.status,
"error": error_text return translated_text
})
raise Exception(f"Erro na tradução: {error_text}") except Exception as e:
except Exception as e: storage.add_log("ERROR", "Erro no processo de tradução", {
storage.add_log("ERROR", "Erro no processo de tradução", { "error": str(e),
"error": str(e), "type": type(e).__name__
"type": type(e).__name__ })
}) raise
raise