Usa o modelo multilingual, ignora trechos curtos e o template do Llama 3.2 para a resposta sair em uma frase completa. O chat deixa a API livre durante a geração, o upload indexa o PDF e os agendamentos não são gravados duas vezes.
178 lines
5.7 KiB
Python
178 lines
5.7 KiB
Python
import asyncio
|
|
import json
|
|
import os
|
|
import uuid
|
|
from typing import List
|
|
|
|
from fastapi import FastAPI, File, HTTPException, UploadFile
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from app.models import (
|
|
BookInfo,
|
|
ChatRequest,
|
|
ChatResponse,
|
|
ScheduleInfo,
|
|
ScheduleRequest,
|
|
UploadResponse,
|
|
)
|
|
from app.services.bot_service import BotService
|
|
from app.services.rag_service import QdrantService, RAGService
|
|
from app.utils.schedule_manager import ScheduleManager
|
|
|
|
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
SCHEDULES_PATH = os.getenv("SCHEDULES_PATH", "/app/schedules.json")
|
|
UPLOAD_DIR = os.getenv("UPLOAD_DIR", "/app/uploads")
|
|
QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333")
|
|
MODEL_PATH = os.getenv("GPT4ALL_MODEL", "/app/models/llama-3.2-3b-instruct-q4_0.gguf")
|
|
|
|
qdrant_service: QdrantService | None = None
|
|
rag_service: RAGService | None = None
|
|
bot_service = BotService(os.getenv("TELEGRAM_BOT_TOKEN", ""), os.getenv("TELEGRAM_CHAT_ID", ""))
|
|
schedule_manager = ScheduleManager(SCHEDULES_PATH)
|
|
|
|
app = FastAPI(title="Espiritismo RAG API")
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.mount(
|
|
"/static",
|
|
StaticFiles(directory=os.path.join(BASE_DIR, "frontend", "static")),
|
|
name="static",
|
|
)
|
|
|
|
|
|
def get_qdrant() -> QdrantService:
|
|
global qdrant_service
|
|
if qdrant_service is None:
|
|
qdrant_service = QdrantService(QDRANT_URL)
|
|
return qdrant_service
|
|
|
|
|
|
def get_rag() -> RAGService:
|
|
global rag_service
|
|
if rag_service is None:
|
|
rag_service = RAGService(get_qdrant(), model_name=MODEL_PATH)
|
|
return rag_service
|
|
|
|
|
|
def load_schedules():
|
|
if os.path.isfile(SCHEDULES_PATH):
|
|
with open(SCHEDULES_PATH, "r", encoding="utf-8") as handle:
|
|
data = json.load(handle)
|
|
return data if isinstance(data, list) else []
|
|
return []
|
|
|
|
|
|
def index_pdf(file_path: str, filename: str):
|
|
from app.services.pdf_processor import PDFProcessor
|
|
from app.utils.embeddings import EmbeddingService
|
|
|
|
title = os.path.splitext(os.path.basename(filename))[0].replace("-", " ").replace("_", " ")
|
|
processor = PDFProcessor(EmbeddingService())
|
|
chunks = processor.embed_chunks(
|
|
processor.load_and_split(
|
|
file_path,
|
|
{"title": title, "author": "Desconhecido", "type": "book"},
|
|
)
|
|
)
|
|
for chunk in chunks:
|
|
chunk["id"] = str(uuid.uuid4())
|
|
get_qdrant().add_chunks(chunks)
|
|
|
|
|
|
def save_schedules(schedules: list):
|
|
parent = os.path.dirname(SCHEDULES_PATH)
|
|
if parent:
|
|
os.makedirs(parent, exist_ok=True)
|
|
with open(SCHEDULES_PATH, "w", encoding="utf-8") as handle:
|
|
json.dump(schedules, handle, indent=2)
|
|
handle.write("\n")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "ok"}
|
|
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def index():
|
|
template = os.path.join(BASE_DIR, "templates", "index.html")
|
|
with open(template, "r", encoding="utf-8") as handle:
|
|
return handle.read()
|
|
|
|
|
|
@app.get("/api/books", response_model=List[BookInfo])
|
|
async def get_books():
|
|
books = get_qdrant().get_books()
|
|
return [{"title": book, "source": book} for book in books]
|
|
|
|
|
|
@app.post("/api/chat", response_model=ChatResponse)
|
|
def chat(request: ChatRequest):
|
|
try:
|
|
return get_rag().query(request.message, request.top_k)
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
|
|
@app.post("/api/upload", response_model=UploadResponse)
|
|
async def upload_pdf(file: UploadFile = File(...)):
|
|
if not file.filename or not file.filename.endswith(".pdf"):
|
|
raise HTTPException(status_code=400, detail="Only PDF files are allowed")
|
|
|
|
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|
file_path = os.path.join(UPLOAD_DIR, os.path.basename(file.filename))
|
|
try:
|
|
content = await file.read()
|
|
with open(file_path, "wb") as handle:
|
|
handle.write(content)
|
|
await asyncio.to_thread(index_pdf, file_path, file.filename)
|
|
return UploadResponse(message="File uploaded and indexed", filename=file.filename)
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
|
|
@app.get("/api/schedules", response_model=List[ScheduleInfo])
|
|
async def get_schedules():
|
|
return [ScheduleInfo(**item) for item in load_schedules()]
|
|
|
|
|
|
@app.post("/api/schedules", response_model=ScheduleInfo)
|
|
async def create_schedule(request: ScheduleRequest):
|
|
new_schedule = schedule_manager.add_schedule(request.model_dump())
|
|
return ScheduleInfo(**new_schedule)
|
|
|
|
|
|
@app.put("/api/schedules/{schedule_id}", response_model=ScheduleInfo)
|
|
async def update_schedule(schedule_id: int, request: ScheduleRequest):
|
|
updated = schedule_manager.update_schedule(schedule_id, request.model_dump())
|
|
if updated is None:
|
|
raise HTTPException(status_code=404, detail="Schedule not found")
|
|
save_schedules(schedule_manager.list_schedules())
|
|
try:
|
|
bot_service.update_schedule(updated)
|
|
except Exception as exc:
|
|
print(f"Warning: Could not update schedule in bot: {exc}")
|
|
return ScheduleInfo(**updated)
|
|
|
|
|
|
@app.delete("/api/schedules/{schedule_id}")
|
|
async def delete_schedule(schedule_id: int):
|
|
current = schedule_manager.get_schedule(schedule_id)
|
|
if current is None or not schedule_manager.delete_schedule(schedule_id):
|
|
raise HTTPException(status_code=404, detail="Schedule not found")
|
|
save_schedules(schedule_manager.list_schedules())
|
|
try:
|
|
bot_service.remove_schedule(current)
|
|
except Exception as exc:
|
|
print(f"Warning: Could not remove schedule from bot: {exc}")
|
|
return {"message": "Schedule deleted"}
|