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()
if settings.DEBUG_MODE:
logger.debug(f"Payload recebido: {body}")
# Extraindo informações necessárias do JSON
# 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,154 +1,200 @@
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):
audio_data = base64.b64decode(base64_data)
audio_file_path = "/tmp/audio_file.mp3"
"""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)
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
return audio_file_path
# 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": [
{
"role": "user",
"content": f"""
Entenda o contexto desse áudio e faça um resumo super enxuto sobre o que se trata.
Esse áudio foi enviado pelo whatsapp, de alguém, para Gabriel.
Escreva APENAS o resumo do áudio como se fosse você que estivesse enviando
essa mensagem! Não comprimente, não de oi, não escreva nada antes nem depois
do resumo, responda apenas um resumo enxuto do que foi falado no áudio.
IMPORTANTE: Não faça esse resumo como se fosse um áudio que uma terceira
pessoa enviou, não diga coisas como 'a pessoa está falando...' etc.
Escreva o resumo com base nessa mensagem do áudio,
como se você estivesse escrevendo esse resumo e enviando em
texto pelo whatsapp: {text}""",
}
],
"messages": [{
"role": "user",
"content": f"""
Entenda o contexto desse áudio e faça um resumo super enxuto sobre o que se trata.
Esse áudio foi enviado pelo whatsapp, de alguém, para Gabriel.
Escreva APENAS o resumo do áudio como se fosse você que estivesse enviando
essa mensagem! Não comprimente, não de oi, não escreva nada antes nem depois
do resumo, responda apenas um resumo enxuto do que foi falado no áudio.
IMPORTANTE: Não faça esse resumo como se fosse um áudio que uma terceira
pessoa enviou, não diga coisas como 'a pessoa está falando...' etc.
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",
}
async with aiohttp.ClientSession() as session:
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"]
return summary_text
else:
raise Exception("Erro ao resumir o texto")
try:
async with aiohttp.ClientSession() as session:
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:
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}"}
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:
try:
async with aiohttp.ClientSession() as session:
# 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 {}
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}")
is_summary = False
if response.status == 200:
result = await response.json()
message = result.get("text", "")
# 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')
if len(message) > 1000:
is_summary = True
message = await summarize_text_if_needed(message)
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]}...")
return message, is_summary
else:
raise Exception("Erro ao transcrever o áudio")
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:
error_text = await response.text()
logger.error(f"Erro na transcrição: {error_text}")
raise Exception(f"Erro na transcrição: {error_text}")
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}},
}
except Exception as e:
logger.error(f"Erro no processo de transcrição: {str(e)}", exc_info=settings.DEBUG_MODE)
raise
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):
async with aiohttp.ClientSession() as session:
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()}")
return False
"""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]:
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}
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()
return result.get("base64", "")
else:
raise HTTPException(
status_code=500, detail="Falha ao obter áudio em base64"
)
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:
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