# app/routers/admin_rag.py from fastapi import APIRouter, Depends, HTTPException from pymysql.cursors import DictCursor from typing import Dict from deps import get_db, get_qdrant_client, admin_required from config import get_settings from models.rag_models import ( IndexRequest, IndexSingleRequest, IndexResponse, SearchRequest, SearchResponse, SearchResult, QueryRequest, QueryResponse, QuerySource, StatusResponse, ProvidersResponse, ProviderInfo ) from services.provider_factory import ProviderFactory from services.rag_service import RAGService router = APIRouter() @router.post("/index", response_model=IndexResponse, name="rag_index_all") def index_all_posts( request: IndexRequest, user = Depends(admin_required) ): """ Index all published posts to Qdrant. Admin-only endpoint. """ settings = get_settings() db_conn = get_db() qdrant_client = get_qdrant_client() try: # Create provider provider = ProviderFactory.create_provider( provider_name=request.provider, settings=settings ) # Create RAG service rag_service = RAGService( db_conn=db_conn, qdrant_client=qdrant_client, embedding_provider=provider ) # Index posts result = rag_service.index_all_posts(locale=request.locale) return IndexResponse( status="success", **result ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Indexing failed: {str(e)}") finally: db_conn.close() @router.post("/index/{post_id}", response_model=IndexResponse, name="rag_index_single") def index_single_post( post_id: int, request: IndexSingleRequest, user = Depends(admin_required) ): """ Index a single post to Qdrant. Admin-only endpoint. """ settings = get_settings() db_conn = get_db() qdrant_client = get_qdrant_client() try: # Fetch post from database with db_conn.cursor(DictCursor) as cur: cur.execute( "SELECT id, title, slug, locale, body_md FROM posts WHERE id=%s", (post_id,) ) post = cur.fetchone() if not post: raise HTTPException(status_code=404, detail="Post not found") # Create provider provider = ProviderFactory.create_provider( provider_name=request.provider, settings=settings ) # Create RAG service rag_service = RAGService( db_conn=db_conn, qdrant_client=qdrant_client, embedding_provider=provider ) # Index post result = rag_service.index_post( post_id=post['id'], title=post['title'], slug=post['slug'], locale=post['locale'], body_md=post['body_md'] ) return IndexResponse(**result) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Indexing failed: {str(e)}") finally: db_conn.close() @router.delete("/index/{post_id}", name="rag_delete_index") def delete_post_index( post_id: int, locale: str, user = Depends(admin_required) ): """ Remove a post from the vector index. Admin-only endpoint. """ db_conn = get_db() qdrant_client = get_qdrant_client() try: # Get any provider (we only need it for service initialization) settings = get_settings() available_providers = ProviderFactory.get_available_providers(settings) if not available_providers: raise HTTPException(status_code=500, detail="No providers configured") provider = ProviderFactory.create_provider( provider_name=available_providers[0], settings=settings ) # Create RAG service rag_service = RAGService( db_conn=db_conn, qdrant_client=qdrant_client, embedding_provider=provider ) # Delete post index result = rag_service.delete_post_index(post_id=post_id, locale=locale) return result except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Deletion failed: {str(e)}") finally: db_conn.close() @router.post("/search", response_model=SearchResponse, name="rag_search") def search_posts( request: SearchRequest, user = Depends(admin_required) ): """ Semantic search across indexed posts. Admin-only endpoint. """ settings = get_settings() db_conn = get_db() qdrant_client = get_qdrant_client() try: # Create provider provider = ProviderFactory.create_provider( provider_name=request.provider, settings=settings ) # Create RAG service rag_service = RAGService( db_conn=db_conn, qdrant_client=qdrant_client, embedding_provider=provider ) # Search results = rag_service.search_posts( query=request.query, locale=request.locale, limit=request.limit ) # Convert to response model search_results = [SearchResult(**r) for r in results] return SearchResponse( results=search_results, query=request.query, locale=request.locale, provider=request.provider ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Search failed: {str(e)}") finally: db_conn.close() @router.post("/query", response_model=QueryResponse, name="rag_query") def query_with_rag( request: QueryRequest, user = Depends(admin_required) ): """ RAG-powered question answering using post content. Admin-only endpoint. """ settings = get_settings() db_conn = get_db() qdrant_client = get_qdrant_client() try: # Create embedding provider embedding_provider = ProviderFactory.create_provider( provider_name=request.provider, settings=settings ) # Create completion provider (may be same or different) completion_provider = ProviderFactory.create_provider( provider_name=request.provider, settings=settings ) # Create RAG service rag_service = RAGService( db_conn=db_conn, qdrant_client=qdrant_client, embedding_provider=embedding_provider, completion_provider=completion_provider ) # Query with RAG result = rag_service.query_with_rag( question=request.question, locale=request.locale, context_limit=request.context_limit ) # Convert sources to response model sources = [QuerySource(**s) for s in result['sources']] return QueryResponse( answer=result['answer'], sources=sources, provider=result['provider'], model=result['model'], question=request.question ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Query failed: {str(e)}") finally: db_conn.close() @router.get("/status", response_model=StatusResponse, name="rag_status") def get_indexing_status(user = Depends(admin_required)): """ Get indexing status across all collections. Admin-only endpoint. """ settings = get_settings() db_conn = get_db() qdrant_client = get_qdrant_client() try: # Get any available provider (we only need it for service initialization) available_providers = ProviderFactory.get_available_providers(settings) if not available_providers: raise HTTPException(status_code=500, detail="No providers configured") provider = ProviderFactory.create_provider( provider_name=available_providers[0], settings=settings ) # Create RAG service rag_service = RAGService( db_conn=db_conn, qdrant_client=qdrant_client, embedding_provider=provider ) # Get status status = rag_service.get_indexing_status() return StatusResponse(**status) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to get status: {str(e)}") finally: db_conn.close() @router.get("/providers", response_model=ProvidersResponse, name="rag_providers") def get_providers_status(user = Depends(admin_required)): """ List available providers and their status. Admin-only endpoint. """ settings = get_settings() try: # Get provider status provider_status = ProviderFactory.get_provider_status(settings) # Convert to response model providers = [] for name, info in provider_status.items(): provider_info = ProviderInfo( name=name, available=info.get('available', False), provider_name=info.get('provider_name'), model=info.get('model'), dimension=info.get('dimension'), supports_embeddings=info.get('supports_embeddings'), supports_completions=info.get('supports_completions'), error=info.get('error') ) providers.append(provider_info) return ProvidersResponse(providers=providers) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to get providers: {str(e)}")