Spaces:
Sleeping
Sleeping
| import logging | |
| from typing import Optional | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from agents import Runner | |
| from simple_agents.aagents import Triage_Agent | |
| from models.user_context import UserContext | |
| from pydantic import BaseModel | |
| from services.rag import RAGService | |
| from data.vector_store import VectorStore | |
| # Initialize services globally but handle initialization errors gracefully | |
| try: | |
| vector_store = VectorStore() | |
| rag_service = RAGService() | |
| rag_service.set_vector_store(vector_store) | |
| except Exception as e: | |
| logging.error(f"Failed to initialize services: {e}") | |
| vector_store = None | |
| rag_service = None | |
| app = FastAPI() | |
| # CORS middleware for Vercel deployment | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=[ | |
| "https://muhammedsuhaib.github.io", | |
| "http://localhost:3000", | |
| "http://localhost:8080", | |
| ], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # --------------------------- | |
| # Pydantic Models for Frontend Requests | |
| # --------------------------- | |
| # Matches the payload for the general chat endpoint (/api/query) | |
| class QueryRequest(BaseModel): | |
| query: str | |
| user_context: Optional[dict] = None | |
| # Matches the payload for the selection endpoint (/api/selection) | |
| class SelectionRequest(BaseModel): | |
| selected_text: str | |
| question: str | |
| user_context: Optional[dict] = None | |
| # Matches the payload for the translation endpoint (/api/translate-text) | |
| class TranslationRequest(BaseModel): | |
| text: str | |
| target_language: str | |
| # --------------------------- | |
| # FastAPI Endpoints (Matching React expectations) | |
| # --------------------------- | |
| def read_root(): | |
| return {"message": "Python Assistant Backend is running."} | |
| async def handle_query(req: QueryRequest): | |
| """Handles general chat queries from the React component.""" | |
| logging.info(f"Received general query: {req.query}") | |
| # Check if services are properly initialized | |
| if not rag_service or not vector_store: | |
| logging.error("RAG service not initialized") | |
| return { | |
| "answer": "Service temporarily unavailable", | |
| "sources": [] | |
| } | |
| # Create user context from request data | |
| user_context_data = req.user_context or {} | |
| user_context = UserContext( | |
| name=user_context_data.get('name', 'User'), | |
| uid=user_context_data.get('uid'), | |
| email=user_context_data.get('email'), | |
| personalization_data=user_context_data.get('personalization_data'), | |
| session_id=user_context_data.get('session_id') | |
| ) | |
| # Use global RAG service to get context from Qdrant | |
| # Get relevant context from Qdrant | |
| try: | |
| rag_result = await rag_service.query(req.query) | |
| print(rag_result) | |
| print(rag_result.sources) | |
| context = rag_result.answer if rag_result.answer != "I don't know" else "" | |
| except Exception as e: | |
| logging.error(f"RAG query failed: {e}") | |
| # Fallback to no context if RAG fails | |
| rag_result = None | |
| context = "" | |
| # Include context in the agent's query if available | |
| if context and context != "I don't know": | |
| enhanced_query = f"Based on the following context: {context}\n\nQuestion: {req.query}" | |
| else: | |
| enhanced_query = req.query | |
| # Run the main agent with the enhanced query and user context | |
| result = await Runner.run( | |
| Triage_Agent, | |
| enhanced_query, | |
| context=user_context | |
| ) | |
| # CRITICAL: Response structure must match React component: {"answer": "...", "sources": []} | |
| return { | |
| "answer": result.final_output, | |
| "sources": rag_result.sources if rag_result and hasattr(rag_result, 'sources') else [] # Must be included, even if empty | |
| } | |
| async def handle_selection(req: SelectionRequest): | |
| """Handles queries based on selected text (RAG context).""" | |
| logging.info(f"Received selection query. Question: {req.question}") | |
| # Create user context from request data | |
| user_context_data = req.user_context or {} | |
| user_context = UserContext( | |
| name=user_context_data.get('name', 'User'), | |
| uid=user_context_data.get('uid'), | |
| email=user_context_data.get('email'), | |
| personalization_data=user_context_data.get('personalization_data'), | |
| session_id=user_context_data.get('session_id') | |
| ) | |
| # Check if services are properly initialized | |
| if not rag_service or not vector_store: | |
| logging.error("RAG service not initialized") | |
| return { | |
| "answer": "Service temporarily unavailable", | |
| "sources": [] | |
| } | |
| # Use global RAG service to get additional context from Qdrant | |
| # Get relevant context from Qdrant based on the question | |
| try: | |
| rag_result = await rag_service.query(req.question) | |
| additional_context = rag_result.answer if rag_result.answer != "I don't know" else "" | |
| except Exception as e: | |
| logging.error(f"RAG query failed: {e}") | |
| # Fallback to no context if RAG fails | |
| rag_result = None | |
| additional_context = "" | |
| # Construct a RAG-style prompt for the agent | |
| if additional_context and additional_context != "I don't know": | |
| prompt = ( | |
| f"Based *only* on the following context, answer the user's question. " | |
| f"If the context does not contain the answer, state that. " | |
| f"Context: \"{req.selected_text}\"\n\nAdditional context from knowledge base: {additional_context} " | |
| f"Question: {req.question}" | |
| ) | |
| else: | |
| prompt = ( | |
| f"Based *only* on the following context, answer the user's question. " | |
| f"If the context does not contain the answer, state that. " | |
| f"Context: \"{req.selected_text}\" " | |
| f"Question: {req.question}" | |
| ) | |
| # Run the agent with the context-aware prompt and user context | |
| result = await Runner.run( | |
| Triage_Agent, | |
| prompt, | |
| context=user_context | |
| ) | |
| # CRITICAL: Response structure must match React component: {"answer": "...", "sources": []} | |
| return { | |
| "answer": result.final_output, | |
| "sources": rag_result.sources if rag_result and hasattr(rag_result, 'sources') else [] # Must be included, even if empty | |
| } | |
| def health_check(): | |
| """Health check endpoint for Vercel deployment.""" | |
| return {"status": "healthy", "message": "Backend is running"} | |
| async def translate_text(req: TranslationRequest): | |
| """Translates text to the specified target language.""" | |
| from deep_translator import GoogleTranslator | |
| try: | |
| # Validate target language | |
| if req.target_language != 'ur': | |
| return {"error": "Currently only Urdu (ur) translation is supported"} | |
| # Perform translation | |
| translated = GoogleTranslator(source='en', target=req.target_language).translate(req.text) | |
| return {"translated_text": translated} | |
| except Exception as e: | |
| logging.error(f"Translation error: {e}") | |
| return {"error": str(e)} | |