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:
parent
76dd77ecb7
commit
97f50f51bd
@ -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
117
config.py
Normal 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)}")
|
@ -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
62
main.py
@ -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
199
readme.md
@ -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.
|
||||
|
||||
---
|
||||
|
@ -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
|
||||
|
180
services.py
180
services.py
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user