# # """ # # Farmer.Chat Backend - FastAPI Application # # Deploy to Hugging Face Space: https://huggingface.co/spaces/aakashdg/farmer-chat-backend # # """ # # from fastapi import FastAPI, HTTPException # # from fastapi.middleware.cors import CORSMiddleware # # from fastapi.responses import FileResponse, JSONResponse # # from pydantic import BaseModel, Field # # from typing import Optional, Dict, Any # # import os # # import asyncio # # import time # # from datetime import datetime # # # Import pipeline components # # from src.pipeline import FarmerChatPipeline # # from src.pdf_generator import generate_pdf_report # # from openai import OpenAI # # import httpx # # # Initialize FastAPI # # app = FastAPI( # # title="Farmer.Chat Backend", # # description="Multi-stage MCP pipeline for agricultural intelligence", # # version="2.0.0" # # ) # # # CORS - Allow all origins for demo (restrict in production) # # app.add_middleware( # # CORSMiddleware, # # allow_origins=["*"], # # allow_credentials=True, # # allow_methods=["*"], # # allow_headers=["*"], # # ) # # # Initialize OpenAI client with FIXED httpx configuration # # OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") # # if not OPENAI_API_KEY: # # raise ValueError("OPENAI_API_KEY environment variable not set!") # # # FIX: Create httpx client without proxy support # # http_client = httpx.Client( # # timeout=httpx.Timeout(60.0), # # limits=httpx.Limits(max_keepalive_connections=5, max_connections=10) # # ) # # # Initialize OpenAI with custom http client (bypasses proxy issues) # # openai_client = OpenAI( # # api_key=OPENAI_API_KEY, # # http_client=http_client # # ) # # print("✅ OpenAI client initialized with custom httpx client") # # print(f" Model: gpt-4o") # # # Default location (Bangalore Agricultural Region) # # DEFAULT_LOCATION = { # # "name": "Bangalore Agricultural Region", # # "lat": 12.8716, # # "lon": 77.4946 # # } # # # Initialize pipeline # # pipeline = FarmerChatPipeline(openai_client, DEFAULT_LOCATION) # # # Request/Response Models # # class QueryRequest(BaseModel): # # query: str = Field(..., min_length=3, max_length=500, description="Farmer's question") # # location: Optional[Dict[str, Any]] = Field(None, description="Custom location (lat, lon, name)") # # class Config: # # json_schema_extra = { # # "example": { # # "query": "Should I plant rice today?", # # "location": { # # "name": "Bangalore", # # "lat": 12.8716, # # "lon": 77.4946 # # } # # } # # } # # class QueryResponse(BaseModel): # # success: bool # # query: str # # advice: str # # routing: Dict[str, Any] # # data: Dict[str, Any] # # execution_time_seconds: float # # timestamp: str # # # Health check # # @app.get("/") # # async def root(): # # return { # # "service": "Farmer.Chat Backend", # # "status": "operational", # # "version": "2.0.0", # # "endpoints": { # # "query": "/api/query", # # "health": "/api/health", # # "servers": "/api/servers" # # } # # } # # @app.get("/api/health") # # async def health_check(): # # """Health check endpoint""" # # return { # # "status": "healthy", # # "timestamp": datetime.now().isoformat(), # # "openai_configured": bool(OPENAI_API_KEY), # # "location": DEFAULT_LOCATION # # } # # @app.get("/api/servers") # # async def list_servers(): # # """List available MCP servers""" # # from src.executor import MCP_SERVER_REGISTRY # # return { # # "total_servers": len(MCP_SERVER_REGISTRY), # # "servers": MCP_SERVER_REGISTRY # # } # # @app.post("/api/query", response_model=QueryResponse) # # async def process_query(request: QueryRequest): # # """ # # Main query endpoint - processes farmer questions through MCP pipeline # # """ # # try: # # start_time = time.time() # # # Use custom location if provided, otherwise default # # location = request.location if request.location else DEFAULT_LOCATION # # # Update pipeline location if changed # # if request.location: # # pipeline.location = location # # # Process query through pipeline # # result = await pipeline.process_query(request.query, verbose=False) # # execution_time = time.time() - start_time # # return QueryResponse( # # success=True, # # query=request.query, # # advice=result["advice"], # # routing=result["routing"], # # data=result["compiled_data"], # # execution_time_seconds=round(execution_time, 2), # # timestamp=datetime.now().isoformat() # # ) # # except Exception as e: # # raise HTTPException(status_code=500, detail=str(e)) # # @app.post("/api/export-pdf") # # async def export_pdf(request: QueryRequest): # # """ # # Export query result as PDF # # """ # # try: # # # Process query # # result = await pipeline.process_query(request.query, verbose=False) # # # Generate PDF # # pdf_path = generate_pdf_report( # # query=request.query, # # advice=result["advice"], # # data=result["compiled_data"], # # location=pipeline.location # # ) # # # Return PDF file # # return FileResponse( # # pdf_path, # # media_type="application/pdf", # # filename=f"farmer-chat-report-{int(time.time())}.pdf" # # ) # # except Exception as e: # # raise HTTPException(status_code=500, detail=str(e)) # # # Error handlers # # @app.exception_handler(404) # # async def not_found_handler(request, exc): # # return JSONResponse( # # status_code=404, # # content={"error": "Endpoint not found", "path": str(request.url)} # # ) # # @app.exception_handler(500) # # async def server_error_handler(request, exc): # # return JSONResponse( # # status_code=500, # # content={"error": "Internal server error", "detail": str(exc)} # # ) # # if __name__ == "__main__": # # import uvicorn # # uvicorn.run(app, host="0.0.0.0", port=7860) # """ # Alert Summary Generator Backend - Standalone Version # FastAPI app with embedded pipeline and location data # """ # from fastapi import FastAPI, HTTPException # from fastapi.middleware.cors import CORSMiddleware # from pydantic import BaseModel # from typing import Optional, Dict, Any, List # import os # from datetime import datetime # from openai import OpenAI # import httpx # # ============================================================================ # # OPENAI CLIENT SETUP # # ============================================================================ # def get_openai_client(): # """Initialize OpenAI client with API key from environment""" # api_key = os.getenv("OPENAI_API_KEY") # if not api_key: # raise ValueError("OPENAI_API_KEY environment variable not set") # http_client = httpx.Client( # timeout=httpx.Timeout(60.0, connect=10.0), # limits=httpx.Limits(max_keepalive_connections=10, max_connections=20) # ) # return OpenAI(api_key=api_key, http_client=http_client) # # ============================================================================ # # PIPELINE COMPONENTS # # ============================================================================ # class QueryRouter: # """Routes queries to appropriate MCP servers""" # def __init__(self, client): # self.client = client # def route_query(self, query: str, location: Dict[str, float]) -> Dict[str, Any]: # """Determine which servers to call based on query""" # prompt = f"""Given the farmer query: "{query}" # Location: {location['latitude']}, {location['longitude']} # Determine which data sources are needed. Return JSON: # {{ # "weather": true/false, # "soil": true/false, # "water": true/false, # "elevation": true/false, # "pests": true/false # }}""" # response = self.client.chat.completions.create( # model="gpt-4", # messages=[{"role": "user", "content": prompt}], # temperature=0 # ) # # Parse routing decision # import json # try: # routing = json.loads(response.choices[0].message.content) # except: # # Default: query all servers for alerts # routing = { # "weather": True, # "soil": True, # "water": True, # "elevation": True, # "pests": True # } # return routing # class MCPExecutor: # """Executes parallel calls to MCP servers""" # def __init__(self, client): # self.client = client # def execute_parallel(self, routing: Dict[str, bool], location: Dict[str, float]) -> Dict[str, str]: # """Execute MCP server calls in parallel""" # results = {} # # Simulate MCP server calls (replace with actual server calls) # if routing.get("weather"): # results["weather"] = f"Weather data for {location['latitude']}, {location['longitude']}" # if routing.get("soil"): # results["soil"] = f"Soil data for {location['latitude']}, {location['longitude']}" # if routing.get("water"): # results["water"] = f"Water availability for {location['latitude']}, {location['longitude']}" # if routing.get("elevation"): # results["elevation"] = f"Elevation data for {location['latitude']}, {location['longitude']}" # if routing.get("pests"): # results["pests"] = f"Pest risk data for {location['latitude']}, {location['longitude']}" # return results # class ResponseCompiler: # """Compiles MCP results into coherent response""" # def __init__(self, client): # self.client = client # def compile_response(self, query: str, mcp_results: Dict[str, str], location: Dict[str, float]) -> str: # """Compile MCP results into final response""" # # Format MCP results for context # context = "\n\n".join([f"{k.upper()}: {v}" for k, v in mcp_results.items()]) # prompt = f"""You are an agricultural assistant. Compile this data into a comprehensive alert summary. # FARMER QUERY: {query} # LOCATION: {location['latitude']}, {location['longitude']} # DATA FROM SOURCES: # {context} # Provide a comprehensive agricultural alert covering: # 1. Current weather conditions and forecast # 2. Soil health and recommendations # 3. Water availability status # 4. Elevation/topography considerations # 5. Pest risks and preventive measures # Be specific, actionable, and farmer-friendly.""" # response = self.client.chat.completions.create( # model="gpt-4", # messages=[{"role": "user", "content": prompt}], # temperature=0.7 # ) # return response.choices[0].message.content # class HindiTranslator: # """Translates responses to Hindi""" # def __init__(self, client): # self.client = client # def translate(self, text: str, target_lang: str = "en") -> str: # """Translate text if needed""" # if target_lang == "en": # return text # # Add Hindi translation logic here # return text # class FarmerChatPipeline: # """Main pipeline orchestrating all stages""" # def __init__(self, client, location: Dict[str, float]): # self.client = client # self.location = location # self.router = QueryRouter(client) # self.executor = MCPExecutor(client) # self.compiler = ResponseCompiler(client) # self.translator = HindiTranslator(client) # def process_query(self, query: str, language: str = "en") -> str: # """Process query through full pipeline""" # # Stage 1: Route query # routing = self.router.route_query(query, self.location) # # Stage 2: Execute MCP calls # mcp_results = self.executor.execute_parallel(routing, self.location) # # Stage 3: Compile response # response = self.compiler.compile_response(query, mcp_results, self.location) # # Stage 4: Translate if needed # final_response = self.translator.translate(response, language) # return final_response # # ============================================================================ # # BIHAR LOCATION DATA # # ============================================================================ # BIHAR_DATA = { # "Araria": ["Araria", "Forbesganj", "Jokihat", "Raniganj"], # "Arwal": ["Arwal", "Kaler", "Karpi", "Kurtha"], # "Aurangabad": ["Aurangabad", "Daudnagar", "Obra", "Nabinagar"], # "Banka": ["Banka", "Amarpur", "Barahat", "Belhar"], # "Begusarai": ["Begusarai", "Bakhri", "Barauni", "Teghra"], # "Bhagalpur": ["Bhagalpur", "Sabour", "Nathnagar", "Kahalgaon"], # "Bhojpur": ["Arrah", "Jagdishpur", "Piro", "Shahpur"], # "Buxar": ["Buxar", "Dumraon", "Chausa", "Simri"], # "Darbhanga": ["Darbhanga", "Baheri", "Jale", "Benipur"], # "East Champaran": ["Motihari", "Raxaul", "Chakia", "Dhaka"], # "Gaya": ["Gaya", "Bodh Gaya", "Tekari", "Sherghati"], # "Gopalganj": ["Gopalganj", "Barauli", "Baikunthpur", "Kateya"], # "Jamui": ["Jamui", "Jhajha", "Sikandra", "Sono"], # "Jehanabad": ["Jehanabad", "Ghoshi", "Makhdumpur", "Modanganj"], # "Kaimur": ["Bhabua", "Mohania", "Ramgarh", "Chainpur"], # "Katihar": ["Katihar", "Barsoi", "Manihari", "Pranpur"], # "Khagaria": ["Khagaria", "Parbatta", "Alauli", "Beldaur"], # "Kishanganj": ["Kishanganj", "Bahadurganj", "Thakurganj", "Dighalbank"], # "Lakhisarai": ["Lakhisarai", "Halsi", "Suryagarha", "Pipariya"], # "Madhepura": ["Madhepura", "Udakishanganj", "Murliganj", "Alamnagar"], # "Madhubani": ["Madhubani", "Jhanjharpur", "Benipatti", "Jainagar"], # "Munger": ["Munger", "Jamalpur", "Asarganj", "Tarapur"], # "Muzaffarpur": ["Muzaffarpur", "Sitamarhi", "Minapur", "Bochaha"], # "Nalanda": ["Bihar Sharif", "Rajgir", "Hilsa", "Biharsharif"], # "Nawada": ["Nawada", "Rajauli", "Akbarpur", "Hisua"], # "Patna": ["Patna", "Danapur", "Fatuha", "Khagaul"], # "Purnia": ["Purnia", "Dhamdaha", "Kasba", "Banmankhi"], # "Rohtas": ["Sasaram", "Dehri", "Bikramganj", "Nasriganj"], # "Saharsa": ["Saharsa", "Sonbarsa", "Simri Bakhtiarpur", "Mahishi"], # "Samastipur": ["Samastipur", "Rosera", "Dalsinghsarai", "Pusa"], # "Saran": ["Chapra", "Marhaura", "Amnour", "Sonepur"], # "Sheikhpura": ["Sheikhpura", "Barbigha", "Ariari", "Shekhopur"], # "Sheohar": ["Sheohar", "Dumri Katsari", "Piprahi", "Tariyani"], # "Sitamarhi": ["Sitamarhi", "Pupri", "Belsand", "Bathnaha"], # "Siwan": ["Siwan", "Maharajganj", "Mairwa", "Darauli"], # "Supaul": ["Supaul", "Nirmali", "Triveniganj", "Chhatapur"], # "Vaishali": ["Hajipur", "Mahua", "Lalganj", "Desri"], # "West Champaran": ["Bettiah", "Bagaha", "Narkatiaganj", "Lauriya"] # } # LOCATIONS = { # "Araria": {"latitude": 26.1523, "longitude": 87.5167}, # "Forbesganj": {"latitude": 26.3023, "longitude": 87.2664}, # "Jokihat": {"latitude": 25.8998, "longitude": 87.2686}, # "Raniganj": {"latitude": 26.0537, "longitude": 87.5333}, # "Arwal": {"latitude": 25.2560, "longitude": 84.6819}, # "Kaler": {"latitude": 25.1960, "longitude": 84.6219}, # "Karpi": {"latitude": 25.2360, "longitude": 84.7019}, # "Kurtha": {"latitude": 25.3160, "longitude": 84.6619}, # "Aurangabad": {"latitude": 24.7521, "longitude": 84.3742}, # "Daudnagar": {"latitude": 25.0337, "longitude": 84.4007}, # "Obra": {"latitude": 24.9923, "longitude": 84.4342}, # "Nabinagar": {"latitude": 24.6087, "longitude": 84.1269}, # "Banka": {"latitude": 24.8893, "longitude": 86.9220}, # "Amarpur": {"latitude": 25.0393, "longitude": 86.9020}, # "Barahat": {"latitude": 24.8393, "longitude": 87.0020}, # "Belhar": {"latitude": 24.9393, "longitude": 86.9620}, # "Begusarai": {"latitude": 25.4182, "longitude": 86.1347}, # "Bakhri": {"latitude": 25.4582, "longitude": 86.0547}, # "Barauni": {"latitude": 25.4751, "longitude": 86.0458}, # "Teghra": {"latitude": 25.5082, "longitude": 85.9347}, # "Bhagalpur": {"latitude": 25.2425, "longitude": 86.9842}, # "Sabour": {"latitude": 25.2375, "longitude": 87.0542}, # "Nathnagar": {"latitude": 25.1225, "longitude": 87.0042}, # "Kahalgaon": {"latitude": 25.1925, "longitude": 87.2142}, # "Arrah": {"latitude": 25.5560, "longitude": 84.6631}, # "Jagdishpur": {"latitude": 25.4660, "longitude": 84.4231}, # "Piro": {"latitude": 25.3260, "longitude": 84.4031}, # "Shahpur": {"latitude": 25.6060, "longitude": 84.4031}, # "Buxar": {"latitude": 25.5641, "longitude": 83.9778}, # "Dumraon": {"latitude": 25.5541, "longitude": 84.1478}, # "Chausa": {"latitude": 25.5241, "longitude": 83.9178}, # "Simri": {"latitude": 25.6141, "longitude": 84.0478}, # "Darbhanga": {"latitude": 26.1542, "longitude": 85.8978}, # "Baheri": {"latitude": 26.0442, "longitude": 85.8378}, # "Jale": {"latitude": 26.2042, "longitude": 85.8578}, # "Benipur": {"latitude": 26.1142, "longitude": 85.9478}, # "Motihari": {"latitude": 26.6484, "longitude": 84.9194}, # "Raxaul": {"latitude": 26.9784, "longitude": 84.8494}, # "Chakia": {"latitude": 26.4184, "longitude": 85.0494}, # "Dhaka": {"latitude": 26.6784, "longitude": 85.1694}, # "Gaya": {"latitude": 24.7955, "longitude": 85.0002}, # "Bodh Gaya": {"latitude": 24.6955, "longitude": 84.9902}, # "Tekari": {"latitude": 24.9455, "longitude": 85.0402}, # "Sherghati": {"latitude": 24.5655, "longitude": 84.7902}, # "Gopalganj": {"latitude": 26.4685, "longitude": 84.4388}, # "Barauli": {"latitude": 26.3785, "longitude": 84.5788}, # "Baikunthpur": {"latitude": 26.5285, "longitude": 84.3588}, # "Kateya": {"latitude": 26.4285, "longitude": 84.6388}, # "Jamui": {"latitude": 24.9272, "longitude": 86.2231}, # "Jhajha": {"latitude": 24.7772, "longitude": 86.3731}, # "Sikandra": {"latitude": 24.9672, "longitude": 86.0631}, # "Sono": {"latitude": 24.8372, "longitude": 86.1431}, # "Jehanabad": {"latitude": 25.2078, "longitude": 84.9869}, # "Ghoshi": {"latitude": 25.1478, "longitude": 84.9269}, # "Makhdumpur": {"latitude": 25.2478, "longitude": 85.0469}, # "Modanganj": {"latitude": 25.2678, "longitude": 84.9069}, # "Bhabua": {"latitude": 25.0405, "longitude": 83.6074}, # "Mohania": {"latitude": 25.1305, "longitude": 83.4774}, # "Ramgarh": {"latitude": 24.9505, "longitude": 83.6874}, # "Chainpur": {"latitude": 25.2005, "longitude": 83.7474}, # "Katihar": {"latitude": 25.5394, "longitude": 87.5839}, # "Barsoi": {"latitude": 25.3794, "longitude": 87.8839}, # "Manihari": {"latitude": 25.3394, "longitude": 87.6239}, # "Pranpur": {"latitude": 25.6894, "longitude": 87.7239}, # "Khagaria": {"latitude": 25.5022, "longitude": 86.4665}, # "Parbatta": {"latitude": 25.5422, "longitude": 86.5865}, # "Alauli": {"latitude": 25.4622, "longitude": 86.3465}, # "Beldaur": {"latitude": 25.5622, "longitude": 86.4265}, # "Kishanganj": {"latitude": 26.1046, "longitude": 87.9475}, # "Bahadurganj": {"latitude": 26.2646, "longitude": 88.1175}, # "Thakurganj": {"latitude": 26.0446, "longitude": 87.8275}, # "Dighalbank": {"latitude": 25.9046, "longitude": 87.9875}, # "Lakhisarai": {"latitude": 25.1678, "longitude": 86.0927}, # "Halsi": {"latitude": 25.2278, "longitude": 86.0327}, # "Suryagarha": {"latitude": 25.1078, "longitude": 86.1527}, # "Pipariya": {"latitude": 25.2078, "longitude": 86.1327}, # "Madhepura": {"latitude": 25.9207, "longitude": 86.7940}, # "Udakishanganj": {"latitude": 25.9807, "longitude": 86.6740}, # "Murliganj": {"latitude": 25.8907, "longitude": 86.9940}, # "Alamnagar": {"latitude": 25.9607, "longitude": 86.7340}, # "Madhubani": {"latitude": 26.3561, "longitude": 86.0644}, # "Jhanjharpur": {"latitude": 26.2661, "longitude": 86.2844}, # "Benipatti": {"latitude": 26.5961, "longitude": 86.1444}, # "Jainagar": {"latitude": 26.2061, "longitude": 86.1644}, # "Munger": {"latitude": 25.3753, "longitude": 86.4734}, # "Jamalpur": {"latitude": 25.3153, "longitude": 86.4934}, # "Asarganj": {"latitude": 25.1453, "longitude": 86.6834}, # "Tarapur": {"latitude": 25.0253, "longitude": 86.6334}, # "Muzaffarpur": {"latitude": 26.1225, "longitude": 85.3906}, # "Sitamarhi": {"latitude": 26.5925, "longitude": 85.4806}, # "Minapur": {"latitude": 26.0625, "longitude": 85.2906}, # "Bochaha": {"latitude": 26.0025, "longitude": 85.5306}, # "Bihar Sharif": {"latitude": 25.1979, "longitude": 85.5238}, # "Rajgir": {"latitude": 25.0279, "longitude": 85.4238}, # "Hilsa": {"latitude": 25.3179, "longitude": 85.2838}, # "Biharsharif": {"latitude": 25.1979, "longitude": 85.5238}, # "Nawada": {"latitude": 24.8834, "longitude": 85.5387}, # "Rajauli": {"latitude": 25.0634, "longitude": 85.6387}, # "Akbarpur": {"latitude": 24.8234, "longitude": 85.4587}, # "Hisua": {"latitude": 24.8334, "longitude": 85.4187}, # "Patna": {"latitude": 25.5941, "longitude": 85.1376}, # "Danapur": {"latitude": 25.6341, "longitude": 85.0476}, # "Fatuha": {"latitude": 25.5041, "longitude": 85.3076}, # "Khagaul": {"latitude": 25.5741, "longitude": 85.0476}, # "Purnia": {"latitude": 25.7771, "longitude": 87.4753}, # "Dhamdaha": {"latitude": 25.8871, "longitude": 87.5853}, # "Kasba": {"latitude": 25.8471, "longitude": 87.5353}, # "Banmankhi": {"latitude": 25.8871, "longitude": 87.1953}, # "Sasaram": {"latitude": 24.9520, "longitude": 84.0328}, # "Dehri": {"latitude": 24.9020, "longitude": 84.1828}, # "Bikramganj": {"latitude": 25.2120, "longitude": 84.2628}, # "Nasriganj": {"latitude": 25.0520, "longitude": 84.1228}, # "Saharsa": {"latitude": 25.8769, "longitude": 86.5956}, # "Sonbarsa": {"latitude": 25.9269, "longitude": 86.7356}, # "Simri Bakhtiarpur": {"latitude": 25.9569, "longitude": 86.3556}, # "Mahishi": {"latitude": 25.9969, "longitude": 86.4756}, # "Samastipur": {"latitude": 25.8647, "longitude": 85.7817}, # "Rosera": {"latitude": 25.7947, "longitude": 85.9317}, # "Dalsinghsarai": {"latitude": 25.6647, "longitude": 85.8317}, # "Pusa": {"latitude": 25.9847, "longitude": 85.6717}, # "Chapra": {"latitude": 25.7805, "longitude": 84.7477}, # "Marhaura": {"latitude": 25.9705, "longitude": 84.8677}, # "Amnour": {"latitude": 25.8905, "longitude": 84.9077}, # "Sonepur": {"latitude": 25.6905, "longitude": 85.1777}, # "Sheikhpura": {"latitude": 25.1391, "longitude": 85.8354}, # "Barbigha": {"latitude": 25.2191, "longitude": 85.7354}, # "Ariari": {"latitude": 25.0591, "longitude": 85.9554}, # "Shekhopur": {"latitude": 25.1391, "longitude": 85.8354}, # "Sheohar": {"latitude": 26.5184, "longitude": 85.2959}, # "Dumri Katsari": {"latitude": 26.5784, "longitude": 85.1959}, # "Piprahi": {"latitude": 26.4684, "longitude": 85.4159}, # "Tariyani": {"latitude": 26.5584, "longitude": 85.2359}, # "Sitamarhi": {"latitude": 26.5925, "longitude": 85.4806}, # "Pupri": {"latitude": 26.4725, "longitude": 85.7006}, # "Belsand": {"latitude": 26.4425, "longitude": 85.4006}, # "Bathnaha": {"latitude": 26.5925, "longitude": 85.5306}, # "Siwan": {"latitude": 26.2195, "longitude": 84.3564}, # "Maharajganj": {"latitude": 26.1095, "longitude": 84.5064}, # "Mairwa": {"latitude": 26.2295, "longitude": 84.1664}, # "Darauli": {"latitude": 26.1595, "longitude": 84.1464}, # "Supaul": {"latitude": 26.1260, "longitude": 86.6050}, # "Nirmali": {"latitude": 26.3160, "longitude": 86.5850}, # "Triveniganj": {"latitude": 26.2160, "longitude": 87.0250}, # "Chhatapur": {"latitude": 26.2160, "longitude": 86.9050}, # "Hajipur": {"latitude": 25.6851, "longitude": 85.2095}, # "Mahua": {"latitude": 25.9651, "longitude": 85.2895}, # "Lalganj": {"latitude": 25.8751, "longitude": 85.1695}, # "Desri": {"latitude": 25.6051, "longitude": 85.4895}, # "Bettiah": {"latitude": 26.8022, "longitude": 84.5025}, # "Bagaha": {"latitude": 27.0922, "longitude": 84.0925}, # "Narkatiaganj": {"latitude": 26.4322, "longitude": 84.7925}, # "Lauriya": {"latitude": 26.9822, "longitude": 84.3125} # } # # ============================================================================ # # FASTAPI APP # # ============================================================================ # app = FastAPI(title="Farmer.chat Alert Summary API") # # CORS middleware # app.add_middleware( # CORSMiddleware, # allow_origins=["*"], # allow_credentials=True, # allow_methods=["*"], # allow_headers=["*"], # ) # # Initialize pipeline # openai_client = None # pipeline = None # @app.on_event("startup") # async def startup_event(): # """Initialize OpenAI client and pipeline on startup""" # global openai_client, pipeline # try: # openai_client = get_openai_client() # DEFAULT_LOCATION = {"latitude": 25.5941, "longitude": 85.1376} # pipeline = FarmerChatPipeline(openai_client, DEFAULT_LOCATION) # print("✓ Pipeline initialized successfully") # except Exception as e: # print(f"✗ Pipeline initialization failed: {e}") # raise # # ============================================================================ # # API MODELS # # ============================================================================ # class LocationRequest(BaseModel): # location_name: str # district: Optional[str] = None # class AlertResponse(BaseModel): # location: str # coordinates: Dict[str, float] # district: Optional[str] = None # alert_summary: str # timestamp: str # # ============================================================================ # # API ENDPOINTS # # ============================================================================ # @app.get("/") # async def root(): # """Root endpoint""" # return { # "message": "Farmer.chat Alert Summary API", # "version": "1.0.0", # "endpoints": { # "/locations": "GET - List all districts and villages", # "/generate-alert": "POST - Generate alert for location", # "/health": "GET - Health check" # } # } # @app.get("/health") # async def health_check(): # """Health check endpoint""" # return { # "status": "healthy", # "pipeline": "initialized" if pipeline else "not_initialized", # "timestamp": datetime.now().isoformat() # } # @app.get("/locations") # async def get_locations(): # """Get all districts and villages""" # return {"districts": BIHAR_DATA} # @app.post("/generate-alert", response_model=AlertResponse) # async def generate_alert(request: LocationRequest): # """Generate alert summary for selected location""" # if not pipeline: # raise HTTPException(status_code=500, detail="Pipeline not initialized") # # Find location coordinates # location_name = request.location_name.strip() # coordinates = None # # Case-insensitive search # for loc_key, loc_coords in LOCATIONS.items(): # if loc_key.lower() == location_name.lower(): # coordinates = loc_coords # break # if not coordinates: # raise HTTPException( # status_code=404, # detail=f"Location '{location_name}' not found in database" # ) # # Update pipeline location # pipeline.location = coordinates # # Create comprehensive query for all MCP servers # query = f"""Generate a comprehensive agricultural alert summary for {location_name} covering: # 1. Current weather conditions and 7-day forecast # 2. Soil health analysis and fertilizer recommendations # 3. Groundwater availability and irrigation status # 4. Elevation and topography considerations for farming # 5. Current pest risks and preventive measures # Provide actionable insights for farmers.""" # try: # # Generate alert # alert_summary = pipeline.process_query(query) # return AlertResponse( # location=location_name, # coordinates=coordinates, # district=request.district, # alert_summary=alert_summary, # timestamp=datetime.now().isoformat() # ) # except Exception as e: # raise HTTPException( # status_code=500, # detail=f"Error generating alert: {str(e)}" # ) # # ============================================================================ # # RUN SERVER # # ============================================================================ # if __name__ == "__main__": # import uvicorn # port = int(os.getenv("PORT", 7860)) # uvicorn.run(app, host="0.0.0.0", port=port) """ Farmer.chat Alert Summary Generator - FastAPI Application Modular architecture with alert-focused intelligence """ from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import Optional, Dict from datetime import datetime import os # Import pipeline components from src.pipeline import FarmerChatPipeline # Import MCP servers (assuming they exist in src/servers/) from src.servers.weather import WeatherServer from src.servers.soil import SoilPropertiesServer from src.servers.water import WaterServer from src.servers.elevation import ElevationServer from src.servers.pests import PestsServer # ============================================================================ # PYDANTIC MODELS # ============================================================================ class LocationRequest(BaseModel): """Request model for alert generation""" location_name: str district: Optional[str] = None class AlertResponse(BaseModel): """Response model for generated alerts""" location: str coordinates: Dict[str, float] district: Optional[str] alert_summary: str timestamp: str class QueryRequest(BaseModel): """Request model for specific queries""" query: str location_name: Optional[str] = None district: Optional[str] = None # ============================================================================ # LOCATION DATA # ============================================================================ BIHAR_DATA = { "Araria": ["Araria", "Forbesganj", "Jokihat", "Raniganj"], "Arwal": ["Arwal", "Kaler", "Karpi", "Kurtha"], "Aurangabad": ["Aurangabad", "Daudnagar", "Obra", "Nabinagar"], "Banka": ["Banka", "Amarpur", "Barahat", "Belhar"], "Begusarai": ["Begusarai", "Bakhri", "Barauni", "Teghra"], "Bhagalpur": ["Bhagalpur", "Sabour", "Nathnagar", "Kahalgaon"], "Bhojpur": ["Arrah", "Jagdishpur", "Piro", "Shahpur"], "Buxar": ["Buxar", "Dumraon", "Chausa", "Simri"], "Darbhanga": ["Darbhanga", "Baheri", "Jale", "Benipur"], "East Champaran": ["Motihari", "Raxaul", "Chakia", "Dhaka"], "Gaya": ["Gaya", "Bodh Gaya", "Tekari", "Sherghati"], "Gopalganj": ["Gopalganj", "Barauli", "Baikunthpur", "Kateya"], "Jamui": ["Jamui", "Jhajha", "Sikandra", "Sono"], "Jehanabad": ["Jehanabad", "Ghoshi", "Makhdumpur", "Modanganj"], "Kaimur": ["Bhabua", "Mohania", "Ramgarh", "Chainpur"], "Katihar": ["Katihar", "Barsoi", "Manihari", "Pranpur"], "Khagaria": ["Khagaria", "Parbatta", "Alauli", "Beldaur"], "Kishanganj": ["Kishanganj", "Bahadurganj", "Thakurganj", "Dighalbank"], "Lakhisarai": ["Lakhisarai", "Halsi", "Suryagarha", "Pipariya"], "Madhepura": ["Madhepura", "Udakishanganj", "Murliganj", "Alamnagar"], "Madhubani": ["Madhubani", "Jhanjharpur", "Benipatti", "Jainagar"], "Munger": ["Munger", "Jamalpur", "Asarganj", "Tarapur"], "Muzaffarpur": ["Muzaffarpur", "Sitamarhi", "Minapur", "Bochaha"], "Nalanda": ["Bihar Sharif", "Rajgir", "Hilsa", "Biharsharif"], "Nawada": ["Nawada", "Rajauli", "Akbarpur", "Hisua"], "Patna": ["Patna", "Danapur", "Fatuha", "Khagaul"], "Purnia": ["Purnia", "Dhamdaha", "Kasba", "Banmankhi"], "Rohtas": ["Sasaram", "Dehri", "Bikramganj", "Nasriganj"], "Saharsa": ["Saharsa", "Sonbarsa", "Simri Bakhtiarpur", "Mahishi"], "Samastipur": ["Samastipur", "Rosera", "Dalsinghsarai", "Pusa"], "Saran": ["Chapra", "Marhaura", "Amnour", "Sonepur"], "Sheikhpura": ["Sheikhpura", "Barbigha", "Ariari", "Shekhopur"], "Sheohar": ["Sheohar", "Dumri Katsari", "Piprahi", "Tariyani"], "Sitamarhi": ["Sitamarhi", "Pupri", "Belsand", "Bathnaha"], "Siwan": ["Siwan", "Maharajganj", "Mairwa", "Darauli"], "Supaul": ["Supaul", "Nirmali", "Triveniganj", "Chhatapur"], "Vaishali": ["Hajipur", "Mahua", "Lalganj", "Desri"], "West Champaran": ["Bettiah", "Bagaha", "Narkatiaganj", "Lauriya"] } LOCATIONS = { "Araria": {"latitude": 26.1523, "longitude": 87.5167}, "Forbesganj": {"latitude": 26.3023, "longitude": 87.2664}, "Jokihat": {"latitude": 25.8998, "longitude": 87.2686}, "Raniganj": {"latitude": 26.0537, "longitude": 87.5333}, "Arwal": {"latitude": 25.2560, "longitude": 84.6819}, "Kaler": {"latitude": 25.1960, "longitude": 84.6219}, "Karpi": {"latitude": 25.2360, "longitude": 84.7019}, "Kurtha": {"latitude": 25.3160, "longitude": 84.6619}, "Aurangabad": {"latitude": 24.7521, "longitude": 84.3742}, "Daudnagar": {"latitude": 25.0337, "longitude": 84.4007}, "Obra": {"latitude": 24.9923, "longitude": 84.4342}, "Nabinagar": {"latitude": 24.6087, "longitude": 84.1269}, "Banka": {"latitude": 24.8893, "longitude": 86.9220}, "Amarpur": {"latitude": 25.0393, "longitude": 86.9020}, "Barahat": {"latitude": 24.8393, "longitude": 87.0020}, "Belhar": {"latitude": 24.9393, "longitude": 86.9620}, "Begusarai": {"latitude": 25.4182, "longitude": 86.1347}, "Bakhri": {"latitude": 25.4582, "longitude": 86.0547}, "Barauni": {"latitude": 25.4751, "longitude": 86.0458}, "Teghra": {"latitude": 25.5082, "longitude": 85.9347}, "Bhagalpur": {"latitude": 25.2425, "longitude": 86.9842}, "Sabour": {"latitude": 25.2375, "longitude": 87.0542}, "Nathnagar": {"latitude": 25.1225, "longitude": 87.0042}, "Kahalgaon": {"latitude": 25.1925, "longitude": 87.2142}, "Arrah": {"latitude": 25.5560, "longitude": 84.6631}, "Jagdishpur": {"latitude": 25.4660, "longitude": 84.4231}, "Piro": {"latitude": 25.3260, "longitude": 84.4031}, "Shahpur": {"latitude": 25.6060, "longitude": 84.4031}, "Buxar": {"latitude": 25.5641, "longitude": 83.9778}, "Dumraon": {"latitude": 25.5541, "longitude": 84.1478}, "Chausa": {"latitude": 25.5241, "longitude": 83.9178}, "Simri": {"latitude": 25.6141, "longitude": 84.0478}, "Darbhanga": {"latitude": 26.1542, "longitude": 85.8978}, "Baheri": {"latitude": 26.0442, "longitude": 85.8378}, "Jale": {"latitude": 26.2042, "longitude": 85.8578}, "Benipur": {"latitude": 26.1142, "longitude": 85.9478}, "Motihari": {"latitude": 26.6484, "longitude": 84.9194}, "Raxaul": {"latitude": 26.9784, "longitude": 84.8494}, "Chakia": {"latitude": 26.4184, "longitude": 85.0494}, "Dhaka": {"latitude": 26.6784, "longitude": 85.1694}, "Gaya": {"latitude": 24.7955, "longitude": 85.0002}, "Bodh Gaya": {"latitude": 24.6955, "longitude": 84.9902}, "Tekari": {"latitude": 24.9455, "longitude": 85.0402}, "Sherghati": {"latitude": 24.5655, "longitude": 84.7902}, "Gopalganj": {"latitude": 26.4685, "longitude": 84.4388}, "Barauli": {"latitude": 26.3785, "longitude": 84.5788}, "Baikunthpur": {"latitude": 26.5285, "longitude": 84.3588}, "Kateya": {"latitude": 26.4285, "longitude": 84.6388}, "Jamui": {"latitude": 24.9272, "longitude": 86.2231}, "Jhajha": {"latitude": 24.7772, "longitude": 86.3731}, "Sikandra": {"latitude": 24.9672, "longitude": 86.0631}, "Sono": {"latitude": 24.8372, "longitude": 86.1431}, "Jehanabad": {"latitude": 25.2078, "longitude": 84.9869}, "Ghoshi": {"latitude": 25.1478, "longitude": 84.9269}, "Makhdumpur": {"latitude": 25.2478, "longitude": 85.0469}, "Modanganj": {"latitude": 25.2678, "longitude": 84.9069}, "Bhabua": {"latitude": 25.0405, "longitude": 83.6074}, "Mohania": {"latitude": 25.1305, "longitude": 83.4774}, "Ramgarh": {"latitude": 24.9505, "longitude": 83.6874}, "Chainpur": {"latitude": 25.2005, "longitude": 83.7474}, "Katihar": {"latitude": 25.5394, "longitude": 87.5839}, "Barsoi": {"latitude": 25.3794, "longitude": 87.8839}, "Manihari": {"latitude": 25.3394, "longitude": 87.6239}, "Pranpur": {"latitude": 25.6894, "longitude": 87.7239}, "Khagaria": {"latitude": 25.5022, "longitude": 86.4665}, "Parbatta": {"latitude": 25.5422, "longitude": 86.5865}, "Alauli": {"latitude": 25.4622, "longitude": 86.3465}, "Beldaur": {"latitude": 25.5622, "longitude": 86.4265}, "Kishanganj": {"latitude": 26.1046, "longitude": 87.9475}, "Bahadurganj": {"latitude": 26.2646, "longitude": 88.1175}, "Thakurganj": {"latitude": 26.0446, "longitude": 87.8275}, "Dighalbank": {"latitude": 25.9046, "longitude": 87.9875}, "Lakhisarai": {"latitude": 25.1678, "longitude": 86.0927}, "Halsi": {"latitude": 25.2278, "longitude": 86.0327}, "Suryagarha": {"latitude": 25.1078, "longitude": 86.1527}, "Pipariya": {"latitude": 25.2078, "longitude": 86.1327}, "Madhepura": {"latitude": 25.9207, "longitude": 86.7940}, "Udakishanganj": {"latitude": 25.9807, "longitude": 86.6740}, "Murliganj": {"latitude": 25.8907, "longitude": 86.9940}, "Alamnagar": {"latitude": 25.9607, "longitude": 86.7340}, "Madhubani": {"latitude": 26.3561, "longitude": 86.0644}, "Jhanjharpur": {"latitude": 26.2661, "longitude": 86.2844}, "Benipatti": {"latitude": 26.5961, "longitude": 86.1444}, "Jainagar": {"latitude": 26.2061, "longitude": 86.1644}, "Munger": {"latitude": 25.3753, "longitude": 86.4734}, "Jamalpur": {"latitude": 25.3153, "longitude": 86.4934}, "Asarganj": {"latitude": 25.1453, "longitude": 86.6834}, "Tarapur": {"latitude": 25.0253, "longitude": 86.6334}, "Muzaffarpur": {"latitude": 26.1225, "longitude": 85.3906}, "Sitamarhi": {"latitude": 26.5925, "longitude": 85.4806}, "Minapur": {"latitude": 26.0625, "longitude": 85.2906}, "Bochaha": {"latitude": 26.0025, "longitude": 85.5306}, "Bihar Sharif": {"latitude": 25.1979, "longitude": 85.5238}, "Rajgir": {"latitude": 25.0279, "longitude": 85.4238}, "Hilsa": {"latitude": 25.3179, "longitude": 85.2838}, "Biharsharif": {"latitude": 25.1979, "longitude": 85.5238}, "Nawada": {"latitude": 24.8834, "longitude": 85.5387}, "Rajauli": {"latitude": 25.0634, "longitude": 85.6387}, "Akbarpur": {"latitude": 24.8234, "longitude": 85.4587}, "Hisua": {"latitude": 24.8334, "longitude": 85.4187}, "Patna": {"latitude": 25.5941, "longitude": 85.1376}, "Danapur": {"latitude": 25.6341, "longitude": 85.0476}, "Fatuha": {"latitude": 25.5041, "longitude": 85.3076}, "Khagaul": {"latitude": 25.5741, "longitude": 85.0476}, "Purnia": {"latitude": 25.7771, "longitude": 87.4753}, "Dhamdaha": {"latitude": 25.8871, "longitude": 87.5853}, "Kasba": {"latitude": 25.8471, "longitude": 87.5353}, "Banmankhi": {"latitude": 25.8871, "longitude": 87.1953}, "Sasaram": {"latitude": 24.9520, "longitude": 84.0328}, "Dehri": {"latitude": 24.9020, "longitude": 84.1828}, "Bikramganj": {"latitude": 25.2120, "longitude": 84.2628}, "Nasriganj": {"latitude": 25.0520, "longitude": 84.1228}, "Saharsa": {"latitude": 25.8769, "longitude": 86.5956}, "Sonbarsa": {"latitude": 25.9269, "longitude": 86.7356}, "Simri Bakhtiarpur": {"latitude": 25.9569, "longitude": 86.3556}, "Mahishi": {"latitude": 25.9969, "longitude": 86.4756}, "Samastipur": {"latitude": 25.8647, "longitude": 85.7817}, "Rosera": {"latitude": 25.7947, "longitude": 85.9317}, "Dalsinghsarai": {"latitude": 25.6647, "longitude": 85.8317}, "Pusa": {"latitude": 25.9847, "longitude": 85.6717}, "Chapra": {"latitude": 25.7805, "longitude": 84.7477}, "Marhaura": {"latitude": 25.9705, "longitude": 84.8677}, "Amnour": {"latitude": 25.8905, "longitude": 84.9077}, "Sonepur": {"latitude": 25.6905, "longitude": 85.1777}, "Sheikhpura": {"latitude": 25.1391, "longitude": 85.8354}, "Barbigha": {"latitude": 25.2191, "longitude": 85.7354}, "Ariari": {"latitude": 25.0591, "longitude": 85.9554}, "Shekhopur": {"latitude": 25.1391, "longitude": 85.8354}, "Sheohar": {"latitude": 26.5184, "longitude": 85.2959}, "Dumri Katsari": {"latitude": 26.5784, "longitude": 85.1959}, "Piprahi": {"latitude": 26.4684, "longitude": 85.4159}, "Tariyani": {"latitude": 26.5584, "longitude": 85.2359}, "Sitamarhi": {"latitude": 26.5925, "longitude": 85.4806}, "Pupri": {"latitude": 26.4725, "longitude": 85.7006}, "Belsand": {"latitude": 26.4425, "longitude": 85.4006}, "Bathnaha": {"latitude": 26.5925, "longitude": 85.5306}, "Siwan": {"latitude": 26.2195, "longitude": 84.3564}, "Maharajganj": {"latitude": 26.1095, "longitude": 84.5064}, "Mairwa": {"latitude": 26.2295, "longitude": 84.1664}, "Darauli": {"latitude": 26.1595, "longitude": 84.1464}, "Supaul": {"latitude": 26.1260, "longitude": 86.6050}, "Nirmali": {"latitude": 26.3160, "longitude": 86.5850}, "Triveniganj": {"latitude": 26.2160, "longitude": 87.0250}, "Chhatapur": {"latitude": 26.2160, "longitude": 86.9050}, "Hajipur": {"latitude": 25.6851, "longitude": 85.2095}, "Mahua": {"latitude": 25.9651, "longitude": 85.2895}, "Lalganj": {"latitude": 25.8751, "longitude": 85.1695}, "Desri": {"latitude": 25.6051, "longitude": 85.4895}, "Bettiah": {"latitude": 26.8022, "longitude": 84.5025}, "Bagaha": {"latitude": 27.0922, "longitude": 84.0925}, "Narkatiaganj": {"latitude": 26.4322, "longitude": 84.7925}, "Lauriya": {"latitude": 26.9822, "longitude": 84.3125} } # ============================================================================ # FASTAPI APPLICATION # ============================================================================ app = FastAPI( title="Farmer.chat Alert Summary API", description="Agricultural intelligence system with alert-focused MCP pipeline", version="2.0.0" ) # CORS configuration app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ============================================================================ # GLOBAL STATE # ============================================================================ pipeline: Optional[FarmerChatPipeline] = None servers: Dict = {} # ============================================================================ # STARTUP & SHUTDOWN # ============================================================================ @app.on_event("startup") async def startup_event(): """Initialize MCP servers and pipeline on startup""" global pipeline, servers try: print("\n" + "="*60) print("Initializing Farmer.chat Alert System") print("="*60 + "\n") # Initialize MCP servers print("Initializing MCP servers...") # Note: Your servers don't need arguments in their constructors # WaterServer hardcodes cache_dir internally servers = { "weather": WeatherServer(), "soil": SoilPropertiesServer(), "water": WaterServer(), "elevation": ElevationServer(), "pests": PestsServer() } print("✓ All MCP servers initialized successfully\n") # Initialize pipeline with default location (Patna) default_location = LOCATIONS.get("patna", {"latitude": 25.6093, "longitude": 85.1235}) pipeline = FarmerChatPipeline( servers=servers, location=default_location ) print("\n" + "="*60) print("✓ Farmer.chat Alert System Ready") print("="*60 + "\n") except Exception as e: print(f"✗ Pipeline initialization failed: {str(e)}") raise @app.on_event("shutdown") async def shutdown_event(): """Cleanup on shutdown""" print("\nShutting down Farmer.chat Alert System...") # ============================================================================ # API ENDPOINTS # ============================================================================ @app.get("/") async def root(): """Root endpoint with API information""" return { "service": "Farmer.chat Alert Summary Generator", "version": "2.0.0", "status": "operational", "architecture": "Modular MCP Pipeline", "endpoints": { "locations": "/locations - Get all available locations", "generate_alert": "/generate-alert - Generate alert summary for location", "query": "/query - Process specific farmer query", "health": "/health - Health check", "server_status": "/server-status - Check MCP server status" } } @app.get("/health") async def health_check(): """Health check endpoint""" if pipeline is None: raise HTTPException(status_code=503, detail="Pipeline not initialized") return { "status": "healthy", "pipeline": "operational", "servers": len(servers), "timestamp": datetime.utcnow().isoformat() } @app.get("/server-status") async def server_status(): """Get status of all MCP servers""" if pipeline is None: raise HTTPException(status_code=503, detail="Pipeline not initialized") status = pipeline.get_server_status() return { "servers": status, "timestamp": datetime.utcnow().isoformat() } @app.get("/locations") async def get_locations(): """Get all available districts and villages""" return { "districts": BIHAR_DATA, "total_locations": len(LOCATIONS), "coverage": "Bihar, India" } @app.post("/generate-alert", response_model=AlertResponse) async def generate_alert(request: LocationRequest): """ Generate comprehensive alert summary for a location. Queries all MCP servers and compiles an actionable alert summary focusing on critical information and recommendations. """ if pipeline is None: raise HTTPException(status_code=503, detail="Pipeline not initialized") # Lookup location coordinates location_key = request.location_name.lower().strip() if location_key not in LOCATIONS: # Try case-insensitive partial match matches = [k for k in LOCATIONS.keys() if location_key in k.lower()] if matches: location_key = matches[0] else: raise HTTPException( status_code=404, detail=f"Location '{request.location_name}' not found in database" ) coordinates = LOCATIONS[location_key] try: # Generate alert using pipeline result = pipeline.generate_alert( location=coordinates, location_name=request.location_name ) return AlertResponse( location=request.location_name, coordinates=coordinates, district=request.district, alert_summary=result["alert_summary"], timestamp=datetime.utcnow().isoformat() ) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to generate alert: {str(e)}" ) @app.post("/query") async def process_query(request: QueryRequest): """ Process a specific farmer query. Queries all MCP servers and returns a response focusing on information relevant to the question. """ if pipeline is None: raise HTTPException(status_code=503, detail="Pipeline not initialized") # Determine location location = None if request.location_name: location_key = request.location_name.lower().strip() if location_key in LOCATIONS: location = LOCATIONS[location_key] else: matches = [k for k in LOCATIONS.keys() if location_key in k.lower()] if matches: location = LOCATIONS[matches[0]] try: # Process query through pipeline result = pipeline.process_query( query=request.query, location=location ) return { "query": request.query, "response": result["response"], "location": result["location"], "servers_queried": list(result["mcp_results"].keys()), "timestamp": datetime.utcnow().isoformat() } except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to process query: {str(e)}" ) # ============================================================================ # MAIN (for local testing) # ============================================================================ if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7680)