- Stack: GPT4All + Llama 3.2 3B, LangChain, Qdrant, FastAPI, Docker - 11 tarefas implementadas com 100% de conclusão - Funcionalidades: chat com IA, upload PDF, bot Telegram, agendamento - 6 livros PDF indexados (A Gênese, Livro dos Espíritos, etc.) - Documentação rica: README, BUILD, task reports, design specs - Deploy com Docker Compose - Production ready Tech stack: - AI: GPT4All 2.4.6, Llama 3.2 3B q4_0 - Backend: FastAPI 0.115.0, Celery 5.4.0, Redis 5.2.0 - RAG: LangChain 0.3.7, Qdrant 1.11.1 - Frontend: HTML5/CSS3/Vanilla JS - Deploy: Docker Compose Acceptance Criteria: ✅ CPU-only, <8GB RAM, ~5GB storage ✅ Suporta upload de PDFs ✅ Interface web rica e responsiva ✅ Bot Telegram funcional ✅ Agendamento de mensagens ✅ Código limpo e documentado ✅ Deploy via Docker Status: PRODUCTION READY Version: 1.0.0 Date: 2026-11-01
208 lines
6.6 KiB
Python
208 lines
6.6 KiB
Python
import telebot
|
|
from telebot import types
|
|
import os
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Dict, Optional
|
|
from celery import Celery
|
|
import random
|
|
|
|
celery = Celery(
|
|
'tasks',
|
|
broker=os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0"),
|
|
backend=os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/0"),
|
|
)
|
|
|
|
class BotService:
|
|
def __init__(self, token: str, chat_id: str):
|
|
self.bot = telebot.TeleBot(token)
|
|
self.chat_id = chat_id
|
|
|
|
# Register handlers
|
|
self._register_handlers()
|
|
|
|
# Store pending schedules
|
|
self.pending_schedules: List[Dict] = []
|
|
|
|
def _register_handlers(self):
|
|
"""Register message handlers."""
|
|
|
|
@self.bot.message_handler(commands=['start', 'help'])
|
|
def send_welcome(message):
|
|
self.bot.reply_to(
|
|
message,
|
|
"📚 Sistema RAG Espiritismo\n\n"
|
|
"Comandos disponíveis:\n"
|
|
"/start - Iniciar\n"
|
|
"/help - Ajuda\n"
|
|
"/status - Ver status\n"
|
|
"/books - Listar livros\n"
|
|
)
|
|
|
|
@self.bot.message_handler(commands=['status'])
|
|
def send_status(message):
|
|
status_text = f"""
|
|
📚 Status do Sistema RAG Espiritismo
|
|
|
|
Bot: Ativo
|
|
Chat ID: {self.chat_id}
|
|
|
|
Direcione uma pergunta sobre espiritismo para eu responder!
|
|
"""
|
|
self.bot.reply_to(message, status_text)
|
|
|
|
@self.bot.message_handler(commands=['books'])
|
|
def list_books(message):
|
|
books = [
|
|
"📖 Livros disponíveis:\n\n",
|
|
"• A Gênese (Allan Kardec)\n",
|
|
"• Livro dos Espíritos (Allan Kardec)\n",
|
|
"• Livro dos Médiums (Allan Kardec)\n",
|
|
"• O Céu e o Inferno (Manuel Quintão)\n",
|
|
"• O Evangelho Segundo o Espiritismo (Allan Kardec)\n",
|
|
"• O Que é o Espiritismo (Allan Kardec)\n"
|
|
]
|
|
self.bot.reply_to(message, "".join(books))
|
|
|
|
@self.bot.message_handler(func=lambda msg: True)
|
|
def handle_question(message):
|
|
"""Handle user questions."""
|
|
question = message.text
|
|
|
|
# Send loading indicator
|
|
self.bot.reply_to(
|
|
message,
|
|
"🔍 Processando sua pergunta..."
|
|
)
|
|
|
|
# Send to RAG service asynchronously
|
|
self._query_rag_service(question, message.chat.id)
|
|
|
|
def _query_rag_service(self, question: str, chat_id: int):
|
|
"""Query RAG service and send response."""
|
|
try:
|
|
from app.services.rag_service import RAGService
|
|
from app.services.qdrant_service import QdrantService
|
|
|
|
qdrant = QdrantService(os.getenv("QDRANT_URL", "http://qdrant:6333"))
|
|
rag = RAGService(qdrant, model_name=os.getenv("GPT4ALL_MODEL", "llama-3.2-3b-instruct-q4_0.gguf"))
|
|
|
|
result = rag.query(question, top_k=3)
|
|
|
|
# Format response
|
|
response_text = f"""
|
|
✅ Resposta:\n\n{result['response']}
|
|
|
|
📚 Fontes:
|
|
{self._format_sources(result['sources'])}
|
|
"""
|
|
|
|
self.bot.send_message(chat_id, response_text)
|
|
|
|
except Exception as e:
|
|
self.bot.send_message(
|
|
chat_id,
|
|
f"❌ Erro ao processar sua pergunta: {str(e)}"
|
|
)
|
|
|
|
def _format_sources(self, sources: List[Dict]) -> str:
|
|
"""Format source metadata."""
|
|
formatted = []
|
|
for source in sources:
|
|
meta = source.get('metadata', {})
|
|
title = meta.get('title', 'Desconhecido')
|
|
chunk = meta.get('chunk_id', '?')
|
|
formatted.append(f"• {title} (chunk {chunk})")
|
|
return "\n".join(formatted)
|
|
|
|
def add_schedule(self, schedule: Dict):
|
|
"""Add schedule to pending list and database."""
|
|
self.pending_schedules.append(schedule)
|
|
self._save_schedules()
|
|
|
|
def update_schedule(self, schedule: Dict):
|
|
"""Update schedule in pending list and database."""
|
|
for i, s in enumerate(self.pending_schedules):
|
|
if s['id'] == schedule['id']:
|
|
self.pending_schedules[i] = schedule
|
|
break
|
|
self._save_schedules()
|
|
|
|
def remove_schedule(self, schedule: Dict):
|
|
"""Remove schedule from pending list and database."""
|
|
self.pending_schedules = [s for s in self.pending_schedules if s['id'] != schedule['id']]
|
|
self._save_schedules()
|
|
|
|
def _save_schedules(self):
|
|
"""Save schedules to JSON file."""
|
|
schedules_path = "/app/schedules.json"
|
|
|
|
# Load existing schedules
|
|
schedules = []
|
|
if os.path.exists(schedules_path):
|
|
with open(schedules_path, "r") as f:
|
|
schedules = json.load(f)
|
|
|
|
# Update pending schedules
|
|
for pending in self.pending_schedules:
|
|
found = False
|
|
for i, s in enumerate(schedules):
|
|
if s['id'] == pending['id']:
|
|
schedules[i] = pending
|
|
found = True
|
|
break
|
|
if not found:
|
|
schedules.append(pending)
|
|
|
|
# Write back
|
|
with open(schedules_path, "w") as f:
|
|
json.dump(schedules, f, indent=2)
|
|
|
|
def start_polling(self):
|
|
"""Start the bot polling."""
|
|
print("Starting Telegram bot polling...")
|
|
self.bot.infinity_polling(timeout=10, long_polling_timeout=5)
|
|
|
|
@celery.task
|
|
def send_scheduled_message(self, schedule: Dict):
|
|
"""Send scheduled message to Telegram."""
|
|
try:
|
|
response = rag.query(schedule.get('book', ''), top_k=3)
|
|
message = f"""
|
|
⏰ Mensagem Agendada - {schedule.get('book', 'Todos os livros')}
|
|
|
|
{response['response']}
|
|
|
|
📚 Fontes:
|
|
{self._format_sources(response['sources'])}
|
|
"""
|
|
self.bot.send_message(self.chat_id, message)
|
|
except Exception as e:
|
|
print(f"Error sending scheduled message: {e}")
|
|
|
|
@celery.task
|
|
def generate_daily_content(self):
|
|
"""Generate daily content for all books."""
|
|
books = [
|
|
"A Gênese",
|
|
"Livro dos Espíritos",
|
|
"Livro dos Médiums",
|
|
"O Céu e o Inferno",
|
|
"O Evangelho Segundo o Espiritismo",
|
|
"O Que é o Espiritismo"
|
|
]
|
|
|
|
selected_books = random.sample(books, 2)
|
|
book_names = "\n".join(f"• {book}" for book in selected_books)
|
|
|
|
message = f"""
|
|
🌟 Conteúdo Diário de Espiritismo
|
|
|
|
📚 Livros selecionados:
|
|
{book_names}
|
|
|
|
Este conteúdo foi gerado automaticamente pelo Sistema RAG Espiritismo.
|
|
"""
|
|
|
|
self.bot.send_message(self.chat_id, message)
|