shankarsain's picture
Clean up repository and update codebase for vidyabot-build-small
a2398a7
Raw
History Blame Contribute Delete
6.24 kB
"""
VidyaBot FastAPI Application
Main entry point for the backend API.
Initializes database, loads cache, sets up routes, and runs Uvicorn.
"""
import sys
import warnings
# Enforce UTF-8 encoding on standard streams to prevent Windows console encoding crashes
if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8')
if hasattr(sys.stderr, 'reconfigure'):
sys.stderr.reconfigure(encoding='utf-8')
# Suppress Gradio's warning about Blocks parameters theme/css moving to launch()
warnings.filterwarnings("ignore", category=UserWarning, module="gradio")
import logging
import os
from pathlib import Path
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from backend.config import settings
from backend.database import init_db, close_db
from backend.cache.semantic_cache import get_cache
from backend.api.routes_ingest import router as ingest_router
from backend.api.routes_query import router as query_router
from backend.api.routes_stats import router as stats_router
from backend.routes_benchmark import router as benchmark_router
from backend.routes_interfaces import router as interfaces_router
from backend.routes_teacher import router as teacher_router
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# ========== Lifespan Events ==========
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Manage application startup and shutdown.
Startup:
- Initialize database
- Load semantic cache
- Validate API key
Shutdown:
- Close database connections
"""
logger.info("πŸš€ VidyaBot starting up...")
try:
# Initialize database
logger.info("Initializing database...")
init_db()
logger.info("βœ… Database initialized")
# Validate LLM backend
if settings.LLM_BACKEND == "ollama":
logger.info("Checking Ollama connection...")
from backend.llm.ollama_client import OllamaClient
ollama_client = OllamaClient()
if ollama_client.validate_connection():
logger.info("βœ… Ollama connected")
else:
logger.warning("⚠️ Ollama not reachable β€” start with: ollama serve")
else:
logger.info("Validating Anthropic API key...")
from backend.llm.claude_client import ClaudeClient
if not ClaudeClient.validate_api_key():
logger.warning("⚠️ API key validation failed β€” some features may not work")
else:
logger.info("βœ… API key validated")
# Load semantic cache
logger.info("Loading semantic cache...")
cache = get_cache()
cache.load_cache()
logger.info("βœ… Cache loaded")
# Warmup cross-encoder reranker (v2 upgrade)
logger.info("Warming up cross-encoder reranker...")
try:
from backend.retrieval.reranker import get_reranker
reranker = get_reranker()
reranker.warmup()
logger.info("βœ… Cross-encoder reranker warmed up")
except Exception as e:
logger.warning(f"⚠️ Could not warmup reranker: {e}")
except Exception as e:
logger.error(f"❌ Startup failed: {e}")
raise
logger.info("βœ… VidyaBot ready to serve")
yield
# Shutdown
logger.info("πŸ›‘ VidyaBot shutting down...")
try:
close_db()
logger.info("βœ… Database closed")
except Exception as e:
logger.error(f"Error during shutdown: {e}")
# ========== FastAPI App Creation ==========
app = FastAPI(
title="VidyaBot API",
description="Offline-first AI tutor for Indian students - Context-pruned, cost-optimized",
version="1.0.0",
lifespan=lifespan
)
# ========== CORS Middleware ==========
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins (development)
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ========== Include Routers ==========
app.include_router(ingest_router)
app.include_router(query_router)
app.include_router(stats_router)
app.include_router(benchmark_router)
app.include_router(interfaces_router)
app.include_router(teacher_router)
logger.info("βœ… All routers registered")
# ========== Root Endpoint ==========
@app.get("/api")
async def api_root():
"""API information and documentation."""
return {
"name": "VidyaBot API",
"version": "1.0.0",
"endpoints": {
"ingest": "POST /api/ingest - Upload PDF",
"textbooks": "GET /api/textbooks - List textbooks",
"query": "POST /api/query - Ask a question",
"stats": "GET /api/stats - Get usage statistics",
"health": "GET /api/health - Health check"
},
"documentation": "/docs"
}
# ========== Mount Frontend ==========
frontend_path = os.path.join(os.path.dirname(__file__), "..", "frontend")
if os.path.exists(frontend_path):
app.mount("/", StaticFiles(directory=frontend_path, html=True), name="frontend")
logger.info(f"Mounted frontend from {frontend_path}")
else:
logger.warning(f"Frontend directory not found: {frontend_path}")
# ========== Error Handlers ==========
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
"""Global exception handler for unhandled errors."""
logger.error(f"Unhandled exception: {exc}", exc_info=True)
return {
"status": "error",
"message": "An error occurred processing your request",
"detail": str(exc)
}
# ========== Main Entry Point ==========
if __name__ == "__main__":
import uvicorn
logger.info(f"Starting Uvicorn server on {settings.API_HOST}:{settings.API_PORT}")
uvicorn.run(
"backend.main:app",
host=settings.API_HOST,
port=settings.API_PORT,
reload=False, # Set to True for development
log_level="info"
)