from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import sys import os import logging # Add the parent directory to sys.path so we can import the chat functionality sys.path.append(os.path.dirname(os.path.abspath(__file__))) # Import and validate configuration on startup from secure_config import validate_config validate_config() from chat_openai import query_qdrant, format_response from ingest_local_docs import ingest_local_docs # <- only import the main function, no circular import # Create FastAPI app app = FastAPI( title="AI Assistant Backend", description="Backend API for Docusaurus AI Assistant" ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:3001", "http://127.0.0.1:8000", "http://localhost:8000", "https://*.vercel.app" ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Define request/response models class ChatRequest(BaseModel): query: str class ChatResponse(BaseModel): response: str # ====================== # Startup Event # ====================== @app.on_event("startup") async def startup_event(): """Ensure Qdrant collection exists and ingest docs if empty""" try: print("Starting ingestion process on startup...") # This will safely create collection if needed and ingest docs documents_indexed = ingest_local_docs() print(f"Ingestion completed. Total chunks stored: {documents_indexed}") except Exception as e: logging.error(f"Startup ingestion failed: {e}") # Continue running backend even if ingestion fails # ====================== # Basic endpoints # ====================== @app.get("/") def read_root(): return {"message": "AI Assistant Backend is running!"} @app.get("/health") def health_check(): return {"status": "ok", "service": "backend", "errors": False} # ====================== # Chat endpoint # ====================== @app.post("/chat", response_model=ChatResponse) async def chat_endpoint(request: ChatRequest): try: query = request.query if not query: raise HTTPException(status_code=400, detail="Query is required") # Query Qdrant database try: search_results = query_qdrant(query) except Exception as e: logging.error(f"Error querying Qdrant: {str(e)}") raise HTTPException( status_code=500, detail=f"Unable to connect to the knowledge base: {str(e)}" ) # Format the response try: response = format_response(query, search_results) except Exception as e: logging.error(f"Error formatting response: {str(e)}") raise HTTPException( status_code=500, detail=f"Error generating response: {str(e)}" ) return ChatResponse(response=response) except HTTPException: raise except Exception as e: logging.error(f"Unexpected error in chat endpoint: {str(e)}") raise HTTPException( status_code=500, detail=f"An unexpected error occurred: {str(e)}" ) # ====================== # Admin endpoints # ====================== @app.post("/admin/ingest") async def admin_ingest(): """Manual ingestion endpoint""" try: documents_indexed = ingest_local_docs() return { "status": "success", "message": "Documents ingested successfully", "documents_indexed": documents_indexed } except Exception as e: logging.error(f"Error during manual ingestion: {str(e)}") raise HTTPException(status_code=500, detail=f"Error during ingestion: {str(e)}") @app.get("/admin/status") async def admin_status(): """Check collection status""" try: from qdrant_client import QdrantClient from config import QDRANT_URL, QDRANT_API_KEY, COLLECTION_NAME qdrant_client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY, prefer_grpc=False) collection_info = qdrant_client.get_collection(collection_name=COLLECTION_NAME) point_count = getattr(collection_info, "points_count", 0) return {"collection_exists": True, "point_count": point_count, "status": "healthy"} except Exception as e: return {"collection_exists": False, "point_count": 0, "status": "missing", "error": str(e)} @app.get("/debug/docs") async def debug_docs(): """Check if docs directory exists and its contents""" docs_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'docs') if os.path.exists(docs_path): files = os.listdir(docs_path) return {"docs_exists": True, "file_count": len(files), "files": files} else: return {"docs_exists": False, "file_count": 0, "files": []} # ====================== # Run Uvicorn # ====================== if __name__ == "__main__": import uvicorn port = int(os.environ.get("PORT", 8000)) uvicorn.run(app, host="0.0.0.0", port=port)