NitinBot001 commited on
Commit
c6121d2
·
verified ·
1 Parent(s): 285ca12

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +717 -0
app.py ADDED
@@ -0,0 +1,717 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - FastAPI version
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ from typing import Dict, Any, Optional, List, Union
7
+ from dataclasses import dataclass, asdict
8
+ from contextlib import asynccontextmanager
9
+ import uuid
10
+ from datetime import datetime, timedelta
11
+
12
+ from fastapi import FastAPI, HTTPException, Depends, status, BackgroundTasks
13
+ from fastapi.middleware.cors import CORSMiddleware
14
+ from fastapi.responses import JSONResponse
15
+ from pydantic import BaseModel, Field, validator
16
+ from openai import OpenAI, AsyncOpenAI
17
+ from dotenv import load_dotenv
18
+ import asyncio
19
+ from cachetools import TTLCache
20
+
21
+ # Import your modules
22
+ from easy_agents import EASYFARMS_FUNCTION_SCHEMAS, EasyFarmsAgent, execute_easyfarms_function
23
+ from alert import chat_with_weather_assistant, WEATHER_TOOLS
24
+
25
+ # Configure logging
26
+ logging.basicConfig(level=logging.INFO)
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Load environment variables
30
+ load_dotenv()
31
+
32
+ # ===================== Configuration =====================
33
+
34
+ @dataclass
35
+ class Config:
36
+ """Configuration settings"""
37
+ api_key: str
38
+ api_url: str
39
+ model_name: str
40
+ max_retries: int = 3
41
+ temperature: float = 0.7
42
+
43
+ @classmethod
44
+ def from_env(cls):
45
+ """Load configuration from environment variables"""
46
+ return cls(
47
+ api_key=os.getenv("API_KEY"),
48
+ api_url=os.getenv("API_URL"),
49
+ model_name=os.getenv("MODEL_NAME", "gpt-4-turbo-preview")
50
+ )
51
+
52
+ # ===================== Pydantic Models =====================
53
+
54
+ class QueryRequest(BaseModel):
55
+ """Request model for query endpoint"""
56
+ message: str = Field(..., min_length=1, max_length=5000, description="User's query message")
57
+ session_id: Optional[str] = Field(None, description="Session ID for conversation continuity")
58
+ save_history: bool = Field(True, description="Whether to save this interaction in history")
59
+
60
+ class Config:
61
+ schema_extra = {
62
+ "example": {
63
+ "message": "What crop should I grow with N=80, P=45, K=120, temperature 25°C, humidity 70%?",
64
+ "session_id": "123e4567-e89b-12d3-a456-426614174000",
65
+ "save_history": True
66
+ }
67
+ }
68
+
69
+ class QueryResponse(BaseModel):
70
+ """Response model for query endpoint"""
71
+ response: str
72
+ session_id: str
73
+ timestamp: datetime
74
+ function_calls: Optional[List[Dict[str, Any]]] = None
75
+
76
+ class Config:
77
+ schema_extra = {
78
+ "example": {
79
+ "response": "Based on the soil conditions...",
80
+ "session_id": "123e4567-e89b-12d3-a456-426614174000",
81
+ "timestamp": "2024-01-15T10:30:00",
82
+ "function_calls": []
83
+ }
84
+ }
85
+
86
+ class CropRecommendationRequest(BaseModel):
87
+ """Request model for crop recommendation"""
88
+ nitrogen: int = Field(..., ge=0, le=200, description="Nitrogen content (N)")
89
+ phosphorus: int = Field(..., ge=0, le=200, description="Phosphorus content (P)")
90
+ potassium: int = Field(..., ge=0, le=200, description="Potassium content (K)")
91
+ temperature: float = Field(..., ge=-10, le=50, description="Temperature in Celsius")
92
+ humidity: float = Field(..., ge=0, le=100, description="Humidity percentage")
93
+ ph: float = Field(6.5, ge=0, le=14, description="Soil pH value")
94
+
95
+ class Config:
96
+ schema_extra = {
97
+ "example": {
98
+ "nitrogen": 80,
99
+ "phosphorus": 45,
100
+ "potassium": 120,
101
+ "temperature": 25.0,
102
+ "humidity": 70.0,
103
+ "ph": 6.5
104
+ }
105
+ }
106
+
107
+ class FertilizerRecommendationRequest(BaseModel):
108
+ """Request model for fertilizer recommendation"""
109
+ crop: str = Field(..., description="Crop type")
110
+ soil_type: str = Field(..., description="Type of soil")
111
+ nitrogen: int = Field(..., ge=0, le=200, description="Current nitrogen content")
112
+ phosphorus: int = Field(..., ge=0, le=200, description="Current phosphorus content")
113
+ potassium: int = Field(..., ge=0, le=200, description="Current potassium content")
114
+ temperature: Optional[float] = Field(None, description="Temperature in Celsius")
115
+ humidity: Optional[float] = Field(None, description="Humidity percentage")
116
+ moisture: Optional[float] = Field(None, description="Soil moisture percentage")
117
+
118
+ class WeatherAlertRequest(BaseModel):
119
+ """Request model for weather alerts"""
120
+ location: Optional[str] = Field(None, description="Location for weather alert")
121
+ include_forecast: bool = Field(True, description="Include weather forecast")
122
+
123
+ class PlantDiseaseRequest(BaseModel):
124
+ """Request model for plant disease detection"""
125
+ symptoms: str = Field(..., description="Description of plant symptoms")
126
+ crop_type: Optional[str] = Field(None, description="Type of crop affected")
127
+ image_url: Optional[str] = Field(None, description="URL to plant image")
128
+
129
+ class SessionInfo(BaseModel):
130
+ """Session information"""
131
+ session_id: str
132
+ created_at: datetime
133
+ last_activity: datetime
134
+ message_count: int
135
+
136
+ class HealthResponse(BaseModel):
137
+ """Health check response"""
138
+ status: str
139
+ version: str
140
+ timestamp: datetime
141
+
142
+ # ===================== Session Manager =====================
143
+
144
+ class SessionManager:
145
+ """Manages user sessions and conversation history"""
146
+
147
+ def __init__(self, ttl_hours: int = 24):
148
+ self.sessions: Dict[str, Dict[str, Any]] = {}
149
+ self.ttl = timedelta(hours=ttl_hours)
150
+
151
+ def create_session(self) -> str:
152
+ """Create a new session"""
153
+ session_id = str(uuid.uuid4())
154
+ self.sessions[session_id] = {
155
+ "id": session_id,
156
+ "created_at": datetime.now(),
157
+ "last_activity": datetime.now(),
158
+ "history": [],
159
+ "message_count": 0
160
+ }
161
+ return session_id
162
+
163
+ def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
164
+ """Get session by ID"""
165
+ if session_id in self.sessions:
166
+ session = self.sessions[session_id]
167
+ # Check if session has expired
168
+ if datetime.now() - session["last_activity"] > self.ttl:
169
+ del self.sessions[session_id]
170
+ return None
171
+ session["last_activity"] = datetime.now()
172
+ return session
173
+ return None
174
+
175
+ def update_session(self, session_id: str, history_entry: Dict[str, Any]):
176
+ """Update session history"""
177
+ if session_id in self.sessions:
178
+ self.sessions[session_id]["history"].append(history_entry)
179
+ self.sessions[session_id]["message_count"] += 1
180
+ self.sessions[session_id]["last_activity"] = datetime.now()
181
+
182
+ def clear_session(self, session_id: str):
183
+ """Clear session history"""
184
+ if session_id in self.sessions:
185
+ self.sessions[session_id]["history"] = []
186
+ self.sessions[session_id]["message_count"] = 0
187
+
188
+ def cleanup_expired_sessions(self):
189
+ """Remove expired sessions"""
190
+ current_time = datetime.now()
191
+ expired = [
192
+ sid for sid, session in self.sessions.items()
193
+ if current_time - session["last_activity"] > self.ttl
194
+ ]
195
+ for sid in expired:
196
+ del self.sessions[sid]
197
+ return len(expired)
198
+
199
+ # ===================== EasyFarms Assistant =====================
200
+
201
+ class EasyFarmsAssistant:
202
+ """Enhanced EasyFarms AI Assistant with async support"""
203
+
204
+ def __init__(self, config: Optional[Config] = None):
205
+ """Initialize the assistant with configuration"""
206
+ self.config = config or Config.from_env()
207
+
208
+ # Async client for FastAPI
209
+ self.async_client = AsyncOpenAI(
210
+ api_key=self.config.api_key,
211
+ base_url=self.config.api_url
212
+ )
213
+
214
+ # Sync client for backward compatibility
215
+ self.sync_client = OpenAI(
216
+ api_key=self.config.api_key,
217
+ base_url=self.config.api_url
218
+ )
219
+
220
+ # Initialize tools
221
+ self.tools = self._initialize_tools()
222
+
223
+ # System prompts
224
+ self.system_prompt = """
225
+ You are the HAl AI assistant for EasyForms Agritech Solutions. Your task is to provide users with clear, concise, and actionable responses regarding agriculture, crop management, production, treatment, weather alerts, and related queries.
226
+
227
+ Core Capabilities:
228
+ - Crop recommendations based on soil and weather conditions
229
+ - Fertilizer recommendations for specific crops
230
+ - Plant disease detection and treatment advice
231
+ - Weather alerts and forecasts for farming decisions
232
+ - General agricultural guidance
233
+
234
+ Rules:
235
+ 1. Check if any relevant function_tools or datasets are available for this query.
236
+ 2. If available, use the functions to fetch information and generate the final user-facing response.
237
+ 3. If the functions or data are unavailable, do not stop; instead, generate a general, well-reasoned response based on your own knowledge.
238
+ 4. Keep the response simple, smooth, well-pointed, and concise.
239
+ 5. Structure the response with bullet points or numbered steps where helpful.
240
+ 6. Provide practical, actionable advice a user can implement immediately.
241
+ 7. Use English or Hindi based on user preference.
242
+ 8. If any information is uncertain, mention it clearly and suggest alternatives.
243
+ 9. For weather-related queries, prioritize safety and timely alerts.
244
+ """
245
+
246
+ self.final_system = """
247
+ You are the final response assistant for EasyForms Agritech Solutions and your name is HAL AI. Use the outputs from previous function calls to generate a clear, concise, actionable response for the user.
248
+
249
+ Rules:
250
+ 1. Combine the function outputs and your own reasoning to answer the query.
251
+ 2. Keep responses simple, smooth, well-pointed, and concise.
252
+ 3. Structure response with headings or bullet points if helpful.
253
+ 4. Provide practical advice that a farmer or user can implement immediately.
254
+ 5. If some data is missing, clearly state it and offer alternatives.
255
+ 6. Use English or Hindi based on the user preference.
256
+ 7. For weather alerts, emphasize urgency and protective measures.
257
+ """
258
+
259
+ # Cache for function results
260
+ self.cache = TTLCache(maxsize=100, ttl=300) # 5 minute cache
261
+
262
+ def _initialize_tools(self) -> List[Dict]:
263
+ """Initialize and convert function schemas to new tools format"""
264
+ tools = []
265
+
266
+ # Convert EasyFarms schemas
267
+ for schema in EASYFARMS_FUNCTION_SCHEMAS:
268
+ tool = {
269
+ "type": "function",
270
+ "function": {
271
+ "name": schema["name"],
272
+ "description": schema["description"],
273
+ "parameters": schema["parameters"]
274
+ }
275
+ }
276
+ tools.append(tool)
277
+
278
+ # Add weather tools
279
+ tools.extend(WEATHER_TOOLS)
280
+
281
+ return tools
282
+
283
+ async def call_function_async(self, function_name: str, arguments: Dict) -> Any:
284
+ """Async version of function calling"""
285
+ try:
286
+ # Check cache first
287
+ cache_key = f"{function_name}:{json.dumps(arguments, sort_keys=True)}"
288
+ if cache_key in self.cache:
289
+ logger.info(f"Cache hit for {function_name}")
290
+ return self.cache[cache_key]
291
+
292
+ # Execute function in thread pool to avoid blocking
293
+ loop = asyncio.get_event_loop()
294
+
295
+ if function_name.startswith("get_weather"):
296
+ result = await loop.run_in_executor(
297
+ None,
298
+ self._execute_weather_function,
299
+ function_name,
300
+ arguments
301
+ )
302
+ else:
303
+ result = await loop.run_in_executor(
304
+ None,
305
+ execute_easyfarms_function,
306
+ function_name,
307
+ arguments
308
+ )
309
+
310
+ # Cache result
311
+ self.cache[cache_key] = result
312
+ return result
313
+
314
+ except Exception as e:
315
+ logger.error(f"Error executing function {function_name}: {e}")
316
+ return {"error": str(e)}
317
+
318
+ def _execute_weather_function(self, function_name: str, kwargs: Dict):
319
+ """Execute weather functions"""
320
+ from alert import execute_function
321
+ return execute_function(function_name, kwargs)
322
+
323
+ async def process_query_async(
324
+ self,
325
+ user_message: str,
326
+ conversation_history: List[Dict[str, Any]] = None
327
+ ) -> Dict[str, Any]:
328
+ """Process user query asynchronously"""
329
+ try:
330
+ messages = [
331
+ {"role": "system", "content": self.system_prompt},
332
+ {"role": "user", "content": user_message}
333
+ ]
334
+
335
+ # Add conversation history if exists
336
+ if conversation_history:
337
+ # Include last 5 interactions for context
338
+ messages = [messages[0]] + conversation_history[-10:] + [messages[1]]
339
+
340
+ # First API call
341
+ response = await self.async_client.chat.completions.create(
342
+ model=self.config.model_name,
343
+ messages=messages,
344
+ tools=self.tools,
345
+ tool_choice="auto",
346
+ temperature=self.config.temperature
347
+ )
348
+
349
+ message = response.choices[0].message
350
+ function_calls_made = []
351
+
352
+ # Check if tool needs to be called
353
+ if hasattr(message, 'tool_calls') and message.tool_calls:
354
+ messages.append(message)
355
+
356
+ # Execute all tool calls
357
+ for tool_call in message.tool_calls:
358
+ function_name = tool_call.function.name
359
+ function_args = json.loads(tool_call.function.arguments)
360
+
361
+ logger.info(f"Calling function: {function_name} with args: {function_args}")
362
+
363
+ # Call function asynchronously
364
+ function_result = await self.call_function_async(function_name, function_args)
365
+
366
+ function_calls_made.append({
367
+ "name": function_name,
368
+ "arguments": function_args,
369
+ "result": function_result
370
+ })
371
+
372
+ # Add function result to messages
373
+ messages.append({
374
+ "role": "tool",
375
+ "tool_call_id": tool_call.id,
376
+ "content": json.dumps(function_result)
377
+ })
378
+
379
+ # Add final system prompt
380
+ messages.append({
381
+ "role": "system",
382
+ "content": self.final_system
383
+ })
384
+
385
+ # Get final response
386
+ final_response = await self.async_client.chat.completions.create(
387
+ model=self.config.model_name,
388
+ messages=messages,
389
+ temperature=self.config.temperature
390
+ )
391
+
392
+ response_content = final_response.choices[0].message.content
393
+ else:
394
+ response_content = message.content
395
+
396
+ return {
397
+ "response": response_content,
398
+ "function_calls": function_calls_made
399
+ }
400
+
401
+ except Exception as e:
402
+ logger.error(f"Error processing query: {e}")
403
+ raise HTTPException(
404
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
405
+ detail=f"Error processing query: {str(e)}"
406
+ )
407
+
408
+ # ===================== FastAPI App =====================
409
+
410
+ # App lifespan manager
411
+ @asynccontextmanager
412
+ async def lifespan(app: FastAPI):
413
+ """Manage app lifecycle"""
414
+ # Startup
415
+ logger.info("Starting EasyFarms API...")
416
+ app.state.assistant = EasyFarmsAssistant()
417
+ app.state.session_manager = SessionManager()
418
+
419
+ # Background task to cleanup sessions
420
+ async def cleanup_sessions():
421
+ while True:
422
+ await asyncio.sleep(3600) # Run every hour
423
+ expired = app.state.session_manager.cleanup_expired_sessions()
424
+ if expired > 0:
425
+ logger.info(f"Cleaned up {expired} expired sessions")
426
+
427
+ # Start background task
428
+ cleanup_task = asyncio.create_task(cleanup_sessions())
429
+
430
+ yield
431
+
432
+ # Shutdown
433
+ cleanup_task.cancel()
434
+ logger.info("Shutting down EasyFarms API...")
435
+
436
+ # Create FastAPI app
437
+ app = FastAPI(
438
+ title="EasyFarms Agricultural Assistant API",
439
+ description="AI-powered agricultural assistant for crop management, fertilizer recommendations, and weather alerts",
440
+ version="1.0.0",
441
+ lifespan=lifespan
442
+ )
443
+
444
+ # Add CORS middleware
445
+ app.add_middleware(
446
+ CORSMiddleware,
447
+ allow_origins=["*"], # Configure appropriately for production
448
+ allow_credentials=True,
449
+ allow_methods=["*"],
450
+ allow_headers=["*"],
451
+ )
452
+
453
+ # ===================== Dependencies =====================
454
+
455
+ def get_assistant() -> EasyFarmsAssistant:
456
+ """Get assistant instance"""
457
+ return app.state.assistant
458
+
459
+ def get_session_manager() -> SessionManager:
460
+ """Get session manager instance"""
461
+ return app.state.session_manager
462
+
463
+ # ===================== API Endpoints =====================
464
+
465
+ @app.get("/", response_model=HealthResponse)
466
+ async def root():
467
+ """Root endpoint - health check"""
468
+ return HealthResponse(
469
+ status="healthy",
470
+ version="1.0.0",
471
+ timestamp=datetime.now()
472
+ )
473
+
474
+ @app.get("/health", response_model=HealthResponse)
475
+ async def health_check():
476
+ """Health check endpoint"""
477
+ return HealthResponse(
478
+ status="healthy",
479
+ version="1.0.0",
480
+ timestamp=datetime.now()
481
+ )
482
+
483
+ @app.post("/api/query", response_model=QueryResponse)
484
+ async def process_query(
485
+ request: QueryRequest,
486
+ assistant: EasyFarmsAssistant = Depends(get_assistant),
487
+ session_manager: SessionManager = Depends(get_session_manager)
488
+ ):
489
+ """
490
+ Process a general query to the agricultural assistant
491
+ """
492
+ # Handle session
493
+ if request.session_id:
494
+ session = session_manager.get_session(request.session_id)
495
+ if not session:
496
+ request.session_id = session_manager.create_session()
497
+ session = session_manager.get_session(request.session_id)
498
+ else:
499
+ request.session_id = session_manager.create_session()
500
+ session = session_manager.get_session(request.session_id)
501
+
502
+ # Process query
503
+ result = await assistant.process_query_async(
504
+ request.message,
505
+ conversation_history=session["history"] if session else None
506
+ )
507
+
508
+ # Update session history if requested
509
+ if request.save_history and session:
510
+ session_manager.update_session(
511
+ request.session_id,
512
+ {"role": "user", "content": request.message}
513
+ )
514
+ session_manager.update_session(
515
+ request.session_id,
516
+ {"role": "assistant", "content": result["response"]}
517
+ )
518
+
519
+ return QueryResponse(
520
+ response=result["response"],
521
+ session_id=request.session_id,
522
+ timestamp=datetime.now(),
523
+ function_calls=result.get("function_calls")
524
+ )
525
+
526
+ @app.post("/api/crop-recommendation")
527
+ async def get_crop_recommendation(
528
+ request: CropRecommendationRequest,
529
+ assistant: EasyFarmsAssistant = Depends(get_assistant)
530
+ ):
531
+ """
532
+ Get crop recommendation based on soil and weather conditions
533
+ """
534
+ query = f"What crop should I grow with N={request.nitrogen}, P={request.phosphorus}, K={request.potassium}, temperature {request.temperature}°C, humidity {request.humidity}%, pH {request.ph}?"
535
+
536
+ result = await assistant.process_query_async(query)
537
+
538
+ return {
539
+ "recommendation": result["response"],
540
+ "input_parameters": request.dict(),
541
+ "timestamp": datetime.now()
542
+ }
543
+
544
+ @app.post("/api/fertilizer-recommendation")
545
+ async def get_fertilizer_recommendation(
546
+ request: FertilizerRecommendationRequest,
547
+ assistant: EasyFarmsAssistant = Depends(get_assistant)
548
+ ):
549
+ """
550
+ Get fertilizer recommendation for specific crop and soil conditions
551
+ """
552
+ query_parts = [
553
+ f"I need fertilizer recommendation for {request.crop} in {request.soil_type} soil",
554
+ f"with N={request.nitrogen}, P={request.phosphorus}, K={request.potassium}"
555
+ ]
556
+
557
+ if request.temperature:
558
+ query_parts.append(f"temperature {request.temperature}°C")
559
+ if request.humidity:
560
+ query_parts.append(f"humidity {request.humidity}%")
561
+ if request.moisture:
562
+ query_parts.append(f"moisture {request.moisture}%")
563
+
564
+ query = ", ".join(query_parts)
565
+ result = await assistant.process_query_async(query)
566
+
567
+ return {
568
+ "recommendation": result["response"],
569
+ "crop": request.crop,
570
+ "soil_type": request.soil_type,
571
+ "timestamp": datetime.now()
572
+ }
573
+
574
+ @app.post("/api/weather-alert")
575
+ async def get_weather_alert(
576
+ request: WeatherAlertRequest,
577
+ assistant: EasyFarmsAssistant = Depends(get_assistant)
578
+ ):
579
+ """
580
+ Get weather alerts for farming
581
+ """
582
+ location_str = f" for {request.location}" if request.location else ""
583
+ query = f"What are the current weather alerts and conditions{location_str}? How will this affect farming?"
584
+
585
+ if request.include_forecast:
586
+ query += " Include the weather forecast."
587
+
588
+ result = await assistant.process_query_async(query)
589
+
590
+ return {
591
+ "alerts": result["response"],
592
+ "location": request.location or "Default location",
593
+ "timestamp": datetime.now()
594
+ }
595
+
596
+ @app.post("/api/plant-disease")
597
+ async def detect_plant_disease(
598
+ request: PlantDiseaseRequest,
599
+ assistant: EasyFarmsAssistant = Depends(get_assistant)
600
+ ):
601
+ """
602
+ Detect plant disease based on symptoms
603
+ """
604
+ query_parts = [f"My plants have these symptoms: {request.symptoms}"]
605
+
606
+ if request.crop_type:
607
+ query_parts.append(f"The crop is {request.crop_type}.")
608
+
609
+ query_parts.append("What could be the problem and how should I treat it?")
610
+
611
+ query = " ".join(query_parts)
612
+ result = await assistant.process_query_async(query)
613
+
614
+ return {
615
+ "diagnosis": result["response"],
616
+ "symptoms": request.symptoms,
617
+ "crop_type": request.crop_type,
618
+ "timestamp": datetime.now()
619
+ }
620
+
621
+ @app.get("/api/session/{session_id}")
622
+ async def get_session_info(
623
+ session_id: str,
624
+ session_manager: SessionManager = Depends(get_session_manager)
625
+ ):
626
+ """
627
+ Get information about a specific session
628
+ """
629
+ session = session_manager.get_session(session_id)
630
+
631
+ if not session:
632
+ raise HTTPException(
633
+ status_code=status.HTTP_404_NOT_FOUND,
634
+ detail="Session not found or expired"
635
+ )
636
+
637
+ return SessionInfo(
638
+ session_id=session["id"],
639
+ created_at=session["created_at"],
640
+ last_activity=session["last_activity"],
641
+ message_count=session["message_count"]
642
+ )
643
+
644
+ @app.delete("/api/session/{session_id}")
645
+ async def clear_session(
646
+ session_id: str,
647
+ session_manager: SessionManager = Depends(get_session_manager)
648
+ ):
649
+ """
650
+ Clear session history
651
+ """
652
+ session = session_manager.get_session(session_id)
653
+
654
+ if not session:
655
+ raise HTTPException(
656
+ status_code=status.HTTP_404_NOT_FOUND,
657
+ detail="Session not found or expired"
658
+ )
659
+
660
+ session_manager.clear_session(session_id)
661
+
662
+ return {"message": "Session history cleared", "session_id": session_id}
663
+
664
+ @app.get("/api/supported-options")
665
+ async def get_supported_options(
666
+ assistant: EasyFarmsAssistant = Depends(get_assistant)
667
+ ):
668
+ """
669
+ Get all supported options for crops, fertilizers, etc.
670
+ """
671
+ result = await assistant.process_query_async("Show me all supported crop types and options")
672
+
673
+ return {
674
+ "options": result["response"],
675
+ "timestamp": datetime.now()
676
+ }
677
+
678
+ # ===================== Error Handlers =====================
679
+
680
+ @app.exception_handler(HTTPException)
681
+ async def http_exception_handler(request, exc):
682
+ """Handle HTTP exceptions"""
683
+ return JSONResponse(
684
+ status_code=exc.status_code,
685
+ content={
686
+ "error": exc.detail,
687
+ "status_code": exc.status_code,
688
+ "timestamp": datetime.now().isoformat()
689
+ }
690
+ )
691
+
692
+ @app.exception_handler(Exception)
693
+ async def general_exception_handler(request, exc):
694
+ """Handle general exceptions"""
695
+ logger.error(f"Unhandled exception: {exc}")
696
+ return JSONResponse(
697
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
698
+ content={
699
+ "error": "Internal server error",
700
+ "status_code": 500,
701
+ "timestamp": datetime.now().isoformat()
702
+ }
703
+ )
704
+
705
+ # ===================== Main =====================
706
+
707
+ if __name__ == "__main__":
708
+ import uvicorn
709
+
710
+ # Run the FastAPI app
711
+ uvicorn.run(
712
+ "app:app",
713
+ host="0.0.0.0",
714
+ port=7860,
715
+ reload=True,
716
+ log_level="info"
717
+ )