Implementa configuração de variáveis de ambiente e logging, atualiza documentação. Inclui novos arquivos .env.example e config.py, além de melhorias no tratamento de erros e mensagens de log.

This commit is contained in:
Davidson Gomes 2024-12-03 19:15:22 -03:00
parent 76dd77ecb7
commit 97f50f51bd
7 changed files with 397 additions and 263 deletions

View File

@ -13,3 +13,6 @@ GROQ_API_KEY=your_groq_api_key
# Host e porta do Redis (caso esteja utilizando)
REDIS_HOST=localhost
REDIS_PORT=6379
DEBUG_MODE=true
LOG_LEVEL=INFO

117
config.py Normal file
View File

@ -0,0 +1,117 @@
import os
from dotenv import load_dotenv
import logging
from pathlib import Path
# Configuração de logging com cores e formatação melhorada
class ColoredFormatter(logging.Formatter):
"""Formatter personalizado que adiciona cores aos logs"""
grey = "\x1b[38;21m"
blue = "\x1b[38;5;39m"
yellow = "\x1b[38;5;226m"
red = "\x1b[38;5;196m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
def __init__(self, fmt):
super().__init__()
self.fmt = fmt
self.FORMATS = {
logging.DEBUG: self.blue + self.fmt + self.reset,
logging.INFO: self.grey + self.fmt + self.reset,
logging.WARNING: self.yellow + self.fmt + self.reset,
logging.ERROR: self.red + self.fmt + self.reset,
logging.CRITICAL: self.bold_red + self.fmt + self.reset
}
def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
# Configuração inicial do logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setFormatter(ColoredFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
# Carregar variáveis de ambiente
env_path = Path('.env')
if env_path.exists():
logger.debug(f"Arquivo .env encontrado em: {env_path.absolute()}")
load_dotenv(override=True)
else:
logger.warning("Arquivo .env não encontrado! Usando variáveis de ambiente do sistema.")
class Settings:
def __init__(self):
logger.debug("Iniciando carregamento das configurações...")
# Carregamento das variáveis com logs detalhados
self.DEBUG_MODE = os.getenv('DEBUG_MODE', 'false').lower() == 'true'
logger.debug(f"DEBUG_MODE configurado como: {self.DEBUG_MODE}")
self.WHATSAPP_INSTANCE = os.getenv('WHATSAPP_INSTANCE')
logger.debug(f"WHATSAPP_INSTANCE configurada como: {self.WHATSAPP_INSTANCE}")
self.WHATSAPP_API_KEY = os.getenv('WHATSAPP_API_KEY')
logger.debug(f"WHATSAPP_API_KEY configurada: {'Presente' if self.WHATSAPP_API_KEY else 'Ausente'}")
self.WHATSAPP_API_URL = os.getenv('WHATSAPP_API_URL')
logger.debug(f"WHATSAPP_API_URL configurada como: {self.WHATSAPP_API_URL}")
self.GROQ_API_KEY = os.getenv('GROQ_API_KEY')
if self.GROQ_API_KEY:
masked_key = f"{self.GROQ_API_KEY[:10]}...{self.GROQ_API_KEY[-4:]}"
logger.debug(f"GROQ_API_KEY carregada: {masked_key}")
else:
logger.error("GROQ_API_KEY não encontrada!")
self.BUSINESS_MESSAGE = os.getenv('BUSINESS_MESSAGE', '*Impacte AI* Premium Services')
logger.debug(f"BUSINESS_MESSAGE configurada como: {self.BUSINESS_MESSAGE}")
self.PROCESS_GROUP_MESSAGES = os.getenv('PROCESS_GROUP_MESSAGES', 'false').lower() == 'true'
logger.debug(f"PROCESS_GROUP_MESSAGES configurado como: {self.PROCESS_GROUP_MESSAGES}")
self.LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
logger.debug(f"LOG_LEVEL configurado como: {self.LOG_LEVEL}")
def validate(self):
"""Validação detalhada das configurações críticas"""
logger.debug("Iniciando validação das configurações...")
validation_errors = []
if not self.GROQ_API_KEY:
validation_errors.append("GROQ_API_KEY não está definida")
elif not self.GROQ_API_KEY.startswith('gsk_'):
validation_errors.append("GROQ_API_KEY inválida: deve começar com 'gsk_'")
if not self.WHATSAPP_API_KEY:
validation_errors.append("WHATSAPP_API_KEY não está definida")
if not self.WHATSAPP_API_URL:
validation_errors.append("WHATSAPP_API_URL não está definida")
if not self.WHATSAPP_INSTANCE:
validation_errors.append("WHATSAPP_INSTANCE não está definida")
if validation_errors:
for error in validation_errors:
logger.error(f"Erro de validação: {error}")
return False
logger.info("Todas as configurações foram validadas com sucesso!")
return True
# Criar instância das configurações
settings = Settings()
# Validar configurações
if not settings.validate():
logger.critical("Configurações inválidas detectadas. A aplicação pode não funcionar corretamente!")
# Ajustar nível de log
log_level = logging.DEBUG if settings.DEBUG_MODE else getattr(logging, settings.LOG_LEVEL.upper())
logger.setLevel(log_level)
logger.info(f"Nível de log definido como: {logging.getLevelName(log_level)}")

View File

@ -19,6 +19,8 @@ services:
GROQ_API_KEY: "substitua_sua_chave_GROQ_aqui" #coloque sua chave GROQ aqui
BUSINESS_MESSAGE: "substitua_sua_mensagem_de_servico_aqui" #coloque a mensagem que será enviada ao final da transcrição aqui
PROCESS_GROUP_MESSAGES: "false" # Define se mensagens de grupos devem ser processadas
DEBUG_MODE: "false"
LOG_LEVEL: "INFO"
deploy:
mode: replicated
replicas: 1

62
main.py
View File

@ -7,78 +7,72 @@ from services import (
)
from models import WebhookRequest
import aiohttp
from dotenv import load_dotenv
import os
from services import get_env_var
# Carregar variáveis do .env
load_dotenv()
from config import settings, logger
app = FastAPI()
# Obter a mensagem do negócio da variável de ambiente
BUSINESS_MESSAGE = get_env_var("BUSINESS_MESSAGE", "*Impacte AI* Premium Services")
PROCESS_GROUP_MESSAGES = get_env_var("PROCESS_GROUP_MESSAGES", "false").lower() == "true"
@app.post("/transcreve-audios")
async def transcreve_audios(request: Request):
try:
# Receber o corpo do webhook
logger.info("Iniciando processamento de áudio")
body = await request.json()
# Extraindo informações necessárias do JSON
if settings.DEBUG_MODE:
logger.debug(f"Payload recebido: {body}")
# Extraindo informações
server_url = body["server_url"]
instance = body["instance"] # os.getenv("WHATSAPP_INSTANCE")
apikey = body["apikey"] # os.getenv("WHATSAPP_API_KEY")
instance = body.get("instance", settings.WHATSAPP_INSTANCE)
apikey = body.get("apikey", settings.WHATSAPP_API_KEY)
audio_key = body["data"]["key"]["id"]
from_me = body["data"]["key"]["fromMe"]
remote_jid = body["data"]["key"]["remoteJid"]
# Verificar se a mensagem foi enviada por mim
if from_me:
logger.info("Mensagem enviada pelo próprio usuário, ignorando")
return {"message": "Mensagem enviada por mim, sem operação"}
# Decidir se processa mensagens de grupos
if "@g.us" in remote_jid and not PROCESS_GROUP_MESSAGES:
if "@g.us" in remote_jid and not settings.PROCESS_GROUP_MESSAGES:
logger.info("Mensagem de grupo ignorada conforme configuração")
return {"message": "Mensagem enviada por um grupo, sem operação"}
if "base64" not in body:
# Pega o áudio em Base64
base64_audio = await get_audio_base64(
server_url, instance, apikey, audio_key
)
# Verificar se temos mediaUrl ou precisamos pegar o base64
if "mediaUrl" in body["data"]["message"]:
audio_source = body["data"]["message"]["mediaUrl"]
logger.debug(f"Usando mediaUrl: {audio_source}")
else:
base64_audio = body["data"]["message"]["base64"]
logger.debug("MediaUrl não encontrada, obtendo áudio via base64")
base64_audio = await get_audio_base64(server_url, instance, apikey, audio_key)
audio_source = await convert_base64_to_file(base64_audio)
logger.debug(f"Áudio convertido e salvo em: {audio_source}")
# Converter Base64 para arquivo de áudio
audio_file = await convert_base64_to_file(base64_audio)
# Transcrever o áudio usando o modelo da API externa
transcription_text, is_summary = await transcribe_audio(audio_file)
# Transcrever o áudio
transcription_text, is_summary = await transcribe_audio(audio_source)
header_message = (
"*Resumo do áudio:*\n\n" if is_summary else "*Transcrição desse áudio:*\n\n"
)
# Formatar o conteúdo da mensagem
summary_message = f"{header_message}{transcription_text}\n\n{BUSINESS_MESSAGE}"
summary_message = f"{header_message}{transcription_text}\n\n{settings.BUSINESS_MESSAGE}"
logger.debug(f"Mensagem formatada: {summary_message[:100]}...")
# Enviar o resumo transcrito de volta via WhatsApp
await send_message_to_whatsapp(
server_url,
instance,
apikey,
summary_message,
body["data"]["key"]["remoteJid"],
remote_jid,
audio_key,
)
logger.info("Áudio processado e resposta enviada com sucesso")
return {"message": "Áudio transcrito e resposta enviada com sucesso"}
except Exception as e:
logger.error(f"Erro ao processar áudio: {str(e)}", exc_info=settings.DEBUG_MODE)
raise HTTPException(
status_code=500, detail=f"Erro ao processar a requisição: {str(e)}"
status_code=500,
detail=f"Erro ao processar a requisição: {str(e)}"
)

199
readme.md
View File

@ -12,7 +12,7 @@ Antes de começar, certifique-se de ter os seguintes requisitos:
- Python 3.10+ instalado ([Download](https://www.python.org/downloads/))
- Docker e Docker Compose instalados ([Instruções](https://docs.docker.com/get-docker/))
- Uma conta Evolution API com chave válida
- Uma conta GROQ API com chave válida
- Uma conta GROQ API com chave válida (começa com 'gsk_')
---
@ -33,55 +33,82 @@ python -m venv .venv
source .venv/Scripts/activate
pip install -r requirements.txt
```
Para sair do ambiente virtual, use:
### Configuração do Arquivo .env
Copie o arquivo `.env.example` para `.env` e configure suas variáveis:
```bash
deactivate
cp .env.example .env
```
### 🚀 Como Executar Localmente
Certifique-se de que todas as dependências foram instaladas.
Rode o comando abaixo para iniciar o servidor:
## 📖 **Configuração Detalhada das Variáveis**
### Variáveis Essenciais
| Variável | Descrição | Obrigatória | Exemplo |
|-----------------------|----------------------------------------------------------|-------------|----------------------------------------------------------|
| `WHATSAPP_API_KEY` | Chave da API Evolution para autenticação | Sim | `429683C4C977415CAAFCCE10F7D57E11` |
| `WHATSAPP_API_URL` | URL base da sua instância Evolution API | Sim | `https://api.evolution.com` |
| `WHATSAPP_INSTANCE` | Nome da instância configurada na Evolution API | Sim | `instance1` |
| `GROQ_API_KEY` | Chave da API GROQ (deve começar com 'gsk_') | Sim | `gsk_abc123...` |
### Variáveis de Personalização
| Variável | Descrição | Padrão | Exemplo |
|-----------------------|----------------------------------------------------------|-------------|----------------------------------------------------------|
| `BUSINESS_MESSAGE` | Mensagem de rodapé após transcrição | Vazio | `substitua_sua_mensagem_de_servico_aqui` |
| `PROCESS_GROUP_MESSAGES` | Habilita processamento de mensagens em grupos | `false` | `true` ou `false` |
### Variáveis de Debug e Log
| Variável | Descrição | Padrão | Valores Possíveis |
|-----------------------|----------------------------------------------------------|-------------|----------------------------------------------------------|
| `DEBUG_MODE` | Ativa logs detalhados para debugging | `false` | `true` ou `false` |
| `LOG_LEVEL` | Define o nível de detalhamento dos logs | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
---
## 🚀 **Métodos de Execução**
### Execução Local
```bash
uvicorn main:app --host 0.0.0.0 --port 8005
```
Acesse o serviço localmente em: `http://127.0.0.1:8005/transcreve-audios`. Insira este endereço na Evolution API para consumir as transcrições.
### 🌐 Configuração de Webhook na Evolution API
Endpoint para Webhook
Use o seguinte endpoint para configurar seu webhook na Evolution API:
```bash
https://transcricaoaudio.seudominio.com.br/transcreve-audios
```
### Testando Localmente
Se estiver rodando localmente, use o comando curl para testar:
```bash
curl --location 'http://127.0.0.1:8005/transcreve-audios'
```
### 🐳 Instalação com Docker Swarm e Traefik
Se preferir rodar o projeto em um ambiente de produção com Docker Swarm e Traefik, use o arquivo de configuração abaixo como referência.
docker-compose.yaml
```bash
### 🐳 Docker Compose Simples
```yaml
version: "3.7"
services:
transcricaoaudio:
image: impacteai/transcrevezap:latest
build: .
networks:
- suarededocker #troque pela sua rede do docker
ports:
- 8005:8005
environment:
Uvicorn_port: 8005
Uvicorn_host: 0.0.0.0
Uvicorn_reload: "true"
Uvicorn_workers: 1
WHATSAPP_API_KEY: "substitua_sua_chave_aqui" #coloque sua api key evolution aqui
WHATSAPP_API_URL: "https://suaevolutionapi.sedominio.com.br/" #coloque sua url evolution aqui
WHATSAPP_INSTANCE: "substitua_sua_instancia_aqui" #coloque nome da sua instancia evolution aqui
GROQ_API_KEY: "substitua_sua_chave_GROQ_aqui" #coloque sua chave GROQ aqui
BUSINESS_MESSAGE: "substitua_sua_mensagem_de_servico_aqui" #coloque a mensagem que será enviada ao final da transcrição aqui
PROCESS_GROUP_MESSAGES: "false" # Define se mensagens de grupos devem ser processadas
WHATSAPP_API_KEY: "sua_chave_aqui"
WHATSAPP_API_URL: "https://sua_url_aqui"
WHATSAPP_INSTANCE: "sua_instancia"
GROQ_API_KEY: "sua_chave_groq"
BUSINESS_MESSAGE: "substitua_sua_mensagem_de_servico_aqui"
PROCESS_GROUP_MESSAGES: "false"
DEBUG_MODE: "false"
LOG_LEVEL: "INFO"
```
### 🌟 Docker Swarm com Traefik
```yaml
version: "3.7"
services:
transcricaoaudio:
image: impacteai/transcrevezap:latest
networks:
- suarededocker
environment:
WHATSAPP_API_KEY: "sua_chave_aqui"
WHATSAPP_API_URL: "https://sua_url_aqui"
WHATSAPP_INSTANCE: "sua_instancia"
GROQ_API_KEY: "sua_chave_groq"
BUSINESS_MESSAGE: "substitua_sua_mensagem_de_servico_aqui"
PROCESS_GROUP_MESSAGES: "false"
DEBUG_MODE: "false"
deploy:
mode: replicated
replicas: 1
@ -90,99 +117,43 @@ services:
- node.role == manager
labels:
- traefik.enable=true
- traefik.http.routers.transcricaoaudio.rule=Host(`transcricaoaudio.seudominio.com.br`) #coloque seu subdominio apontado aqui
- traefik.http.routers.transcricaoaudio.rule=Host(`transcricaoaudio.seudominio.com.br`)
- traefik.http.routers.transcricaoaudio.entrypoints=websecure
- traefik.http.routers.transcricaoaudio.tls.certresolver=letsencryptresolver
- traefik.http.services.transcricaoaudio.loadbalancer.server.port=8005
- traefik.http.services.transcricaoaudio.loadbalancer.passHostHeader=true
- traefik.http.routers.transcricaoaudio.service=transcricaoaudio
- traefik.http.middlewares.traefik-compress.compress=true
- traefik.http.routers.transcricaoaudio.middlewares=traefik-compress
resources:
limits:
cpus: "1"
memory: 1024M
networks:
suarededocker: #troque pela sua rede do docker
suarededocker:
external: true
name: suarededocker #troque pela sua rede do docker
```
---
## 🐳 **Rodando com Docker Compose (Sem Traefik)**
## 🔧 **Configuração do Traefik**
Se você prefere rodar a aplicação em um ambiente simples, sem usar o Traefik para gerenciamento de subdomínios, siga as orientações abaixo.
Para usar com Traefik, certifique-se de:
1. Ter o Traefik configurado em seu ambiente Docker Swarm
2. Configurar o DNS do seu domínio para apontar para o servidor
3. Ajustar as labels do Traefik conforme seu ambiente
4. Verificar se a rede externa existe no Docker Swarm
### **1. Usando `docker run` diretamente**
## 📝 **Notas Importantes**
- A GROQ_API_KEY deve começar com 'gsk_'
- O BUSINESS_MESSAGE suporta formatação do WhatsApp (*negrito*, _itálico_)
- Para quebras de linha no BUSINESS_MESSAGE, use \n
- Em produção, recomenda-se DEBUG_MODE=false
- Configure LOG_LEVEL=DEBUG apenas para troubleshooting
Execute o seguinte comando para rodar o contêiner:
```bash
docker run -d \
--name transcricaoaudio \
-p 8005:8005 \
-e Uvicorn_port=8005 \
-e Uvicorn_host=0.0.0.0 \
-e Uvicorn_reload="true" \
-e Uvicorn_workers=1 \
-e WHATSAPP_API_KEY="substitua_sua_chave_aqui" \
-e WHATSAPP_API_URL="https://suaevolutionapi.sedominio.com.br/" \
-e WHATSAPP_INSTANCE="substitua_sua_instancia_aqui" \
-e GROQ_API_KEY="substitua_sua_chave_GROQ_aqui" \
-e BUSINESS_MESSAGE="substitua_sua_mensagem_de_servico_aqui" \
-e PROCESS_GROUP_MESSAGES="false" \
impacteai/transcrevezap:latest
```
Usando `docker-compose.yaml`
Crie um arquivo chamado `docker-compose.yaml` com o seguinte conteúdo:
```bash
version: "3.7"
services:
transcricaoaudio:
image: impacteai/transcrevezap:latest
ports:
- 8005:8005
environment:
Uvicorn_port: 8005
Uvicorn_host: 0.0.0.0
Uvicorn_reload: "true"
Uvicorn_workers: 1
WHATSAPP_API_KEY: "substitua_sua_chave_aqui" # Coloque sua chave API Evolution aqui
WHATSAPP_API_URL: "https://suaevolutionapi.sedominio.com.br/" # URL da sua instância Evolution
WHATSAPP_INSTANCE: "substitua_sua_instancia_aqui" # Nome da sua instância Evolution
GROQ_API_KEY: "substitua_sua_chave_GROQ_aqui" # Chave da API GROQ
BUSINESS_MESSAGE: "substitua_sua_mensagem_de_servico_aqui" # Mensagem adicionada ao final da transcrição
PROCESS_GROUP_MESSAGES: "false" # Define se mensagens de grupos devem ser processadas
```
Para rodar com Docker Compose, execute:
```bash
docker-compose up -d
```
- Acessando o serviço
- Após rodar a aplicação, acesse:
http://127.0.0.1:8005/transcreve-audios para ambiente local, inserindo este endereço na Evolution API.
Você pode substituir 127.0.0.1 pelo IP ou domínio público, se configurado.
---
## 📖 **Configuração das Variáveis de Ambiente**
Ao usar o Docker Compose, configure as seguintes variáveis de ambiente no arquivo `docker-compose.yaml`:
| Variável | Descrição |
|------------------------|--------------------------------------------------------------------------------------------|
| `WHATSAPP_API_KEY` | Chave da API Evolution para integração com o WhatsApp. |
| `WHATSAPP_API_URL` | URL da sua instância da Evolution API. |
| `WHATSAPP_INSTANCE` | Nome da instância configurada na Evolution API. |
| `GROQ_API_KEY` | Chave da API GROQ para realizar transcrições e resumos de áudios. |
| `BUSINESS_MESSAGE` | Mensagem de divulgação que será adicionada ao final das transcrições. |
| `PROCESS_GROUP_MESSAGES` | Define se mensagens enviadas em grupos devem ser processadas (`true`) ou ignoradas (`false`). |
---
## 🔍 **Troubleshooting**
Se encontrar problemas:
1. Verifique se todas as variáveis obrigatórias estão configuradas
2. Ative DEBUG_MODE=true temporariamente
3. Verifique os logs do container
4. Certifique-se que as APIs estão acessíveis
## 📄 **Licença**
Este projeto está licenciado sob a Licença MIT. Isso significa que você pode usar, modificar e distribuir este software livremente, desde que mantenha o aviso de copyright e a licença original em todas as cópias ou partes substanciais do software.
Você pode consultar o texto completo da licença no arquivo [LICENSE](LICENSE).
Este projeto está licenciado sob a Licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.
---

View File

@ -8,18 +8,19 @@ attrs==24.2.0
certifi==2024.8.30
charset-normalizer==2.1.1
click==8.1.7
fastapi==0.78.0
fastapi==0.109.2
frozenlist==1.4.1
h11==0.14.0
idna==3.10
multidict==6.1.0
pip-system-certs==4.0
propcache==0.2.0
pydantic==1.10.18
python-dotenv==0.20.0
pydantic==2.10.3
pydantic-settings==2.6.1
python-dotenv==1.0.1
requests==2.28.1
sniffio==1.3.1
starlette==0.19.1
starlette>=0.36.3,<0.37.0
typing_extensions==4.12.2
urllib3==1.26.20
uvicorn==0.17.6

View File

@ -1,40 +1,37 @@
import aiohttp
import base64
import aiofiles
from dotenv import load_dotenv
import os
from fastapi import HTTPException
from config import settings, logger
# Carregar variáveis do .env
load_dotenv()
# Função para obter as variáveis
def get_env_var(var_name, default=None):
return os.getenv(var_name, default)
# Função assíncrona para converter base64 em arquivo temporário
async def convert_base64_to_file(base64_data):
"""Converte dados base64 em arquivo temporário"""
try:
logger.debug("Iniciando conversão de base64 para arquivo")
audio_data = base64.b64decode(base64_data)
audio_file_path = "/tmp/audio_file.mp3"
async with aiofiles.open(audio_file_path, "wb") as f:
await f.write(audio_data)
logger.debug(f"Arquivo temporário criado em: {audio_file_path}")
return audio_file_path
except Exception as e:
logger.error(f"Erro na conversão base64: {str(e)}", exc_info=settings.DEBUG_MODE)
raise
# Função assíncrona para resumir o texto se necessário
async def summarize_text_if_needed(text):
"""Resumir texto usando a API GROQ"""
logger.debug("Iniciando processo de resumo do texto")
url_completions = "https://api.groq.com/openai/v1/chat/completions"
headers = {
"Authorization": f"Bearer {get_env_var('GROQ_API_KEY')}",
"Authorization": f"Bearer {settings.GROQ_API_KEY}",
"Content-Type": "application/json",
}
json_data = {
"messages": [
{
"messages": [{
"role": "user",
"content": f"""
Entenda o contexto desse áudio e faça um resumo super enxuto sobre o que se trata.
@ -47,108 +44,157 @@ async def summarize_text_if_needed(text):
Escreva o resumo com base nessa mensagem do áudio,
como se você estivesse escrevendo esse resumo e enviando em
texto pelo whatsapp: {text}""",
}
],
}],
"model": "llama-3.2-90b-vision-preview",
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url_completions,
headers=headers,
json=json_data,
) as summary_response:
logger.debug("Enviando requisição para API GROQ")
async with session.post(url_completions, headers=headers, json=json_data) as summary_response:
if summary_response.status == 200:
summary_result = await summary_response.json()
summary_text = summary_result["choices"][0]["message"]["content"]
logger.info("Resumo gerado com sucesso")
logger.debug(f"Resumo: {summary_text[:100]}...")
return summary_text
else:
raise Exception("Erro ao resumir o texto")
error_text = await summary_response.text()
logger.error(f"Erro na API GROQ: {error_text}")
raise Exception(f"Erro ao resumir o texto: {error_text}")
except Exception as e:
logger.error(f"Erro no processo de resumo: {str(e)}", exc_info=settings.DEBUG_MODE)
raise
# Função assíncrona para transcrever o áudio
async def transcribe_audio(audio_file):
async def transcribe_audio(audio_source, apikey=None):
"""Transcreve áudio usando a API GROQ"""
logger.info("Iniciando processo de transcrição")
url = "https://api.groq.com/openai/v1/audio/transcriptions"
headers = {"Authorization": f"Bearer {get_env_var('GROQ_API_KEY')}"}
groq_headers = {"Authorization": f"Bearer {settings.GROQ_API_KEY}"}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url,
headers=headers,
data={
"file": open(audio_file, "rb"),
"model": "whisper-large-v3",
"language": "pt",
},
) as response:
# Se o audio_source for uma URL
if isinstance(audio_source, str) and audio_source.startswith('http'):
logger.debug(f"Baixando áudio da URL: {audio_source}")
download_headers = {"apikey": apikey} if apikey else {}
is_summary = False
async with session.get(audio_source, headers=download_headers) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"Erro no download do áudio: Status {response.status}, Resposta: {error_text}")
raise Exception(f"Erro ao baixar áudio: {error_text}")
audio_data = await response.read()
temp_file = "/tmp/audio_from_url.mp3"
async with aiofiles.open(temp_file, "wb") as f:
await f.write(audio_data)
audio_source = temp_file
logger.debug(f"Áudio salvo temporariamente em: {temp_file}")
# Preparar dados para transcrição
data = aiohttp.FormData()
data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3')
data.add_field('model', 'whisper-large-v3')
data.add_field('language', 'pt')
logger.debug("Enviando áudio para transcrição")
async with session.post(url, headers=groq_headers, data=data) as response:
if response.status == 200:
result = await response.json()
message = result.get("text", "")
logger.info("Transcrição concluída com sucesso")
logger.debug(f"Texto transcrito: {message[:100]}...")
is_summary = False
if len(message) > 1000:
logger.debug("Texto longo detectado, iniciando resumo")
is_summary = True
message = await summarize_text_if_needed(message)
return message, is_summary
else:
raise Exception("Erro ao transcrever o áudio")
error_text = await response.text()
logger.error(f"Erro na transcrição: {error_text}")
raise Exception(f"Erro na transcrição: {error_text}")
except Exception as e:
logger.error(f"Erro no processo de transcrição: {str(e)}", exc_info=settings.DEBUG_MODE)
raise
def get_body_message_to_whatsapp_v2(message, remote_jid, message_id):
return {
"number": remote_jid,
"text": message,
"quoted": {"key": {"remoteJid": remote_jid, "fromMe": False, "id": message_id}},
}
async def send_message_to_whatsapp(server_url, instance, apikey, message, remote_jid, message_id):
"""Envia mensagem via WhatsApp"""
logger.debug(f"Preparando envio de mensagem para: {remote_jid}")
url = f"{server_url}/message/sendText/{instance}"
headers = {"apikey": apikey}
try:
# Tentar enviar na V1
body = get_body_message_to_whatsapp_v1(message, remote_jid)
logger.debug("Tentando envio no formato V1")
result = await call_whatsapp(url, body, headers)
# Se falhar, tenta V2
if not result:
logger.debug("Formato V1 falhou, tentando formato V2")
body = get_body_message_to_whatsapp_v2(message, remote_jid, message_id)
await call_whatsapp(url, body, headers)
logger.info("Mensagem enviada com sucesso")
except Exception as e:
logger.error(f"Erro no envio da mensagem: {str(e)}", exc_info=settings.DEBUG_MODE)
raise
def get_body_message_to_whatsapp_v1(message, remote_jid):
"""Formata mensagem no formato V1"""
return {
"number": remote_jid,
"options": {"delay": 1200, "presence": "composing", "linkPreview": False},
"textMessage": {"text": message},
}
def get_body_message_to_whatsapp_v2(message, remote_jid, message_id):
"""Formata mensagem no formato V2"""
return {
"number": remote_jid,
"text": message,
"quoted": {"key": {"remoteJid": remote_jid, "fromMe": False, "id": message_id}},
}
async def call_whatsapp(url, body, headers):
"""Realiza chamada à API do WhatsApp"""
try:
async with aiohttp.ClientSession() as session:
logger.debug(f"Enviando requisição para: {url}")
async with session.post(url, json=body, headers=headers) as response:
if response.status not in [200, 201]:
print(f"Erro ao enviar mensagem via WhatsApp: {await response.text()}")
error_text = await response.text()
logger.error(f"Erro na API do WhatsApp: Status {response.status}, Resposta: {error_text}")
return False
logger.debug("Requisição bem-sucedida")
return True
except Exception as e:
logger.error(f"Erro na chamada WhatsApp: {str(e)}", exc_info=settings.DEBUG_MODE)
return False
# Função assíncrona para enviar a mensagem de resumo via WhatsApp
async def send_message_to_whatsapp(
server_url, instance, apikey, message, remote_jid, message_id
):
url = f"{server_url}/message/sendText/{instance}"
headers = {"apikey": apikey}
# Tentar enviar na V1
body = get_body_message_to_whatsapp_v1(message, remote_jid)
result = await call_whatsapp(url, body, headers)
# Se falhar, monta novo body na V2 e reenvia
if not result:
body = get_body_message_to_whatsapp_v2(message, remote_jid, message_id)
await call_whatsapp(url, body, headers)
# Função para obter o áudio em Base64 via API do WhatsApp
async def get_audio_base64(server_url, instance, apikey, message_id):
"""Obtém áudio em Base64 via API do WhatsApp"""
logger.debug(f"Obtendo áudio base64 para mensagem: {message_id}")
url = f"{server_url}/chat/getBase64FromMediaMessage/{instance}"
headers = {"apikey": apikey}
body = {"message": {"key": {"id": message_id}}, "convertToMp4": False}
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, json=body, headers=headers) as response:
if response.status in [200, 201]:
result = await response.json()
logger.info("Áudio base64 obtido com sucesso")
return result.get("base64", "")
else:
raise HTTPException(
status_code=500, detail="Falha ao obter áudio em base64"
)
error_text = await response.text()
logger.error(f"Erro ao obter áudio base64: {error_text}")
raise HTTPException(status_code=500, detail="Falha ao obter áudio em base64")
except Exception as e:
logger.error(f"Erro na obtenção do áudio base64: {str(e)}", exc_info=settings.DEBUG_MODE)
raise