fix: make docker compose build and start the API

The image context was backend/, so COPY frontend/ failed. Build from the
repo root, place Python modules on the import path, and pin packages that
exist on PyPI. The web process no longer loads the LLM at startup.
This commit is contained in:
lgcosta committed 2026-11-01 12:00:08 -03:00
1 parent 67739dcf7e
commit 7af7ba530d
19 files changed
+342 -250

No files matched your search

+111 -101
View File
@@ -1,24 +1,37 @@
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from app.services.rag_service import RAGService
from app.services.bot_service import BotService
from app.utils.schedule_manager import ScheduleManager
from app.models import ChatRequest, ChatResponse, BookInfo, UploadResponse, ScheduleInfo, ScheduleRequest
import os
import json
import os
from typing import List
# Initialize services
qdrant_service = QdrantService(os.getenv("QDRANT_URL", "http://qdrant:6333"))
rag_service = RAGService(qdrant_service, model_name=os.getenv("GPT4ALL_MODEL", "llama-3.2-3b-instruct-q4_0.gguf"))
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(os.getenv("SCHEDULES_PATH", "/app/schedules.json"))
schedule_manager = ScheduleManager(SCHEDULES_PATH)
app = FastAPI(title="Espiritismo RAG API")
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -27,128 +40,125 @@ app.add_middleware(
allow_headers=["*"],
)
# Mount static files
app.mount("/static", StaticFiles(directory="../frontend/static"), name="static")
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
# Load existing schedules from file
def load_schedules():
if os.path.exists("/app/schedules.json"):
with open("/app/schedules.json", "r") as f:
return json.load(f)
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 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)
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/", response_class=HTMLResponse)
async def index():
"""Serve the index page."""
with open("../backend/templates/index.html", "r") as f:
return f.read()
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():
"""Get list of books in the collection."""
books = qdrant_service.get_books()
books = get_qdrant().get_books()
return [{"title": book, "source": book} for book in books]
@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""Query the RAG system."""
try:
result = rag_service.query(request.message, request.top_k)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
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(...)):
"""Upload a new PDF file."""
upload_dir = "/app/uploads"
if not file.filename.endswith('.pdf'):
if not file.filename or not file.filename.endswith(".pdf"):
raise HTTPException(status_code=400, detail="Only PDF files are allowed")
file_path = os.path.join(upload_dir, file.filename)
os.makedirs(UPLOAD_DIR, exist_ok=True)
file_path = os.path.join(UPLOAD_DIR, os.path.basename(file.filename))
try:
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
content = await file.read()
with open(file_path, "wb") as handle:
handle.write(content)
return UploadResponse(message="File uploaded successfully", filename=file.filename)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
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():
"""Get all schedules."""
schedules = load_schedules()
return [ScheduleInfo(**s) for s in schedules]
return [ScheduleInfo(**item) for item in load_schedules()]
@app.post("/api/schedules", response_model=ScheduleInfo)
async def create_schedule(request: ScheduleRequest):
"""Create a new schedule."""
schedules = load_schedules()
new_schedule = {
"id": len(schedules) + 1,
"time": request.time,
"book": request.book,
"enabled": request.enabled,
}
schedules.append(new_schedule)
with open("/app/schedules.json", "w") as f:
json.dump(schedules, f, indent=2)
# Start celery beat if needed
new_schedule = schedule_manager.add_schedule(request.model_dump())
if not any(item.get("id") == new_schedule["id"] for item in schedules):
schedules.append(new_schedule)
save_schedules(schedules)
try:
bot_service.add_schedule(new_schedule)
except Exception as e:
print(f"Warning: Could not add schedule to bot: {e}")
except Exception as exc:
print(f"Warning: Could not add schedule to bot: {exc}")
return ScheduleInfo(**new_schedule)
@app.put("/api/schedules/{schedule_id}", response_model=ScheduleInfo)
async def update_schedule(schedule_id: int, request: ScheduleRequest):
"""Update an existing schedule."""
schedules = load_schedules()
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)
for i, s in enumerate(schedules):
if s["id"] == schedule_id:
schedules[i] = {
"id": schedule_id,
"time": request.time,
"book": request.book,
"enabled": request.enabled,
}
with open("/app/schedules.json", "w") as f:
json.dump(schedules, f, indent=2)
try:
bot_service.update_schedule(schedules[i])
except Exception as e:
print(f"Warning: Could not update schedule in bot: {e}")
return ScheduleInfo(**schedules[i])
raise HTTPException(status_code=404, detail="Schedule not found")
@app.delete("/api/schedules/{schedule_id}")
async def delete_schedule(schedule_id: int):
"""Delete a schedule."""
schedules = load_schedules()
for i, s in enumerate(schedules):
if s["id"] == schedule_id:
deleted = schedules.pop(i)
with open("/app/schedules.json", "w") as f:
json.dump(schedules, f, indent=2)
try:
bot_service.remove_schedule(deleted)
except Exception as e:
print(f"Warning: Could not remove schedule from bot: {e}")
return {"message": "Schedule deleted"}
raise HTTPException(status_code=404, detail="Schedule not found")
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"}