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:
1 parent
67739dcf7e
commit
7af7ba530d
19 files changed
+342
-250
No files matched your search
+111
-101
@@ -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"}
|
||||
Reference in new issue
Block a user