Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- alert.py +405 -0
- easy_agents.py +685 -0
- weatherr.py +79 -0
alert.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# alert.py
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
from typing import Any, Dict, List
|
| 7 |
+
from weatherr import get_weather
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
from openai import OpenAI
|
| 10 |
+
import requests
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class WeatherAlertAPI:
|
| 15 |
+
def __init__(self):
|
| 16 |
+
self.api_url = "http://sachet.ndma.gov.in/cap_public_website/FetchAllAlertDetails"
|
| 17 |
+
|
| 18 |
+
def fetch_alerts(self) -> List[Dict[str, Any]]:
|
| 19 |
+
"""Fetch all weather alerts from the NDMA API"""
|
| 20 |
+
try:
|
| 21 |
+
response = requests.get(self.api_url, timeout=30)
|
| 22 |
+
response.raise_for_status()
|
| 23 |
+
data = response.json()
|
| 24 |
+
|
| 25 |
+
if isinstance(data, list):
|
| 26 |
+
return data
|
| 27 |
+
elif isinstance(data, dict) and "nowcastDetails" in data:
|
| 28 |
+
return data["nowcastDetails"]
|
| 29 |
+
else:
|
| 30 |
+
return []
|
| 31 |
+
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f"Error fetching alerts: {e}")
|
| 34 |
+
return []
|
| 35 |
+
|
| 36 |
+
def normalize_text(self, text: str) -> str:
|
| 37 |
+
"""Normalize text for better matching"""
|
| 38 |
+
return re.sub(r'\s+', ' ', text.strip().lower())
|
| 39 |
+
|
| 40 |
+
def location_matches(self, area_description: str, search_terms: List[str]) -> bool:
|
| 41 |
+
"""Check if any search terms match the area description"""
|
| 42 |
+
normalized_area = self.normalize_text(area_description)
|
| 43 |
+
for term in search_terms:
|
| 44 |
+
normalized_term = self.normalize_text(term)
|
| 45 |
+
if normalized_term in normalized_area:
|
| 46 |
+
return True
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
# Initialize the weather API
|
| 50 |
+
weather_api = WeatherAlertAPI()
|
| 51 |
+
|
| 52 |
+
# OpenAI Tool Definitions (Updated format)
|
| 53 |
+
WEATHER_TOOLS = [
|
| 54 |
+
{
|
| 55 |
+
"type": "function",
|
| 56 |
+
"function": {
|
| 57 |
+
"name": "get_weather_alerts",
|
| 58 |
+
"description": "Get weather alerts for one or more locations in India. Can search by city, state, district, or region name.",
|
| 59 |
+
"parameters": {
|
| 60 |
+
"type": "object",
|
| 61 |
+
"properties": {
|
| 62 |
+
"locations": {
|
| 63 |
+
"type": "array",
|
| 64 |
+
"items": {
|
| 65 |
+
"type": "string"
|
| 66 |
+
},
|
| 67 |
+
"description": "List of locations to search for (up to 5). Can be city names, state names, districts, etc.",
|
| 68 |
+
"maxItems": 5,
|
| 69 |
+
"minItems": 1
|
| 70 |
+
},
|
| 71 |
+
"include_details": {
|
| 72 |
+
"type": "boolean",
|
| 73 |
+
"description": "Whether to include detailed alert information like warning messages and coordinates",
|
| 74 |
+
"default": True
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
"required": ["locations"]
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
},{
|
| 81 |
+
"type": "function",
|
| 82 |
+
"function": {
|
| 83 |
+
"name": "get_weather",
|
| 84 |
+
"description": "Fetch current weather using address, coordinates, or district/state.",
|
| 85 |
+
"parameters": {
|
| 86 |
+
"type": "object",
|
| 87 |
+
"properties": {
|
| 88 |
+
"latitude": {"type": "number", "description": "Latitude coordinate"},
|
| 89 |
+
"longitude": {"type": "number", "description": "Longitude coordinate"},
|
| 90 |
+
"address": {"type": "string", "description": "Full address or location name"},
|
| 91 |
+
"district": {"type": "string", "description": "District name"},
|
| 92 |
+
"state": {"type": "string", "description": "State name"}
|
| 93 |
+
},
|
| 94 |
+
"required": [] # none are strictly required; function will validate
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"type": "function",
|
| 100 |
+
"function": {
|
| 101 |
+
"name": "get_alert_summary",
|
| 102 |
+
"description": "Get a summary of all current weather alerts by severity level",
|
| 103 |
+
"parameters": {
|
| 104 |
+
"type": "object",
|
| 105 |
+
"properties": {},
|
| 106 |
+
"required": []
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"type": "function",
|
| 112 |
+
"function": {
|
| 113 |
+
"name": "get_available_locations",
|
| 114 |
+
"description": "Get a list of all locations that currently have weather alerts",
|
| 115 |
+
"parameters": {
|
| 116 |
+
"type": "object",
|
| 117 |
+
"properties": {
|
| 118 |
+
"limit": {
|
| 119 |
+
"type": "integer",
|
| 120 |
+
"description": "Maximum number of locations to return",
|
| 121 |
+
"default": 50,
|
| 122 |
+
"minimum": 1,
|
| 123 |
+
"maximum": 200
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"required": []
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
]
|
| 131 |
+
|
| 132 |
+
def get_current_weather(latitude: float = None, longitude: float = None, address: str = None, district: str = None, state: str = None):
|
| 133 |
+
return get_weather(latitude=latitude, longitude=longitude, address=address, district=district, state=state)
|
| 134 |
+
|
| 135 |
+
# Function Implementations
|
| 136 |
+
def get_weather_alerts(locations: List[str], include_details: bool = True) -> Dict[str, Any]:
|
| 137 |
+
"""
|
| 138 |
+
Get weather alerts for specified locations
|
| 139 |
+
"""
|
| 140 |
+
try:
|
| 141 |
+
all_alerts = weather_api.fetch_alerts()
|
| 142 |
+
|
| 143 |
+
if not all_alerts:
|
| 144 |
+
return {
|
| 145 |
+
"status": "error",
|
| 146 |
+
"message": "No alerts found or error fetching data",
|
| 147 |
+
"data": {}
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
results = {}
|
| 151 |
+
total_alerts = 0
|
| 152 |
+
|
| 153 |
+
for location in locations:
|
| 154 |
+
search_terms = [term.strip() for term in location.split(',') if term.strip()]
|
| 155 |
+
location_alerts = []
|
| 156 |
+
|
| 157 |
+
for alert in all_alerts:
|
| 158 |
+
area_desc = alert.get("area_description", "")
|
| 159 |
+
if weather_api.location_matches(area_desc, search_terms):
|
| 160 |
+
if include_details:
|
| 161 |
+
# Full alert details
|
| 162 |
+
alert_data = {
|
| 163 |
+
"alert_id": alert.get("identifier"),
|
| 164 |
+
"severity": alert.get("severity"),
|
| 165 |
+
"severity_level": alert.get("severity_level"),
|
| 166 |
+
"severity_color": alert.get("severity_color"),
|
| 167 |
+
"disaster_type": alert.get("disaster_type"),
|
| 168 |
+
"area_description": alert.get("area_description"),
|
| 169 |
+
"start_time": alert.get("effective_start_time"),
|
| 170 |
+
"end_time": alert.get("effective_end_time"),
|
| 171 |
+
"source": alert.get("alert_source"),
|
| 172 |
+
"area_covered": alert.get("area_covered"),
|
| 173 |
+
"language": alert.get("actual_lang"),
|
| 174 |
+
"warning_message": alert.get("warning_message"),
|
| 175 |
+
"coordinates": alert.get("centroid"),
|
| 176 |
+
"disseminated": alert.get("disseminated")
|
| 177 |
+
}
|
| 178 |
+
else:
|
| 179 |
+
# Summary only
|
| 180 |
+
alert_data = {
|
| 181 |
+
"alert_id": alert.get("identifier"),
|
| 182 |
+
"severity": alert.get("severity"),
|
| 183 |
+
"disaster_type": alert.get("disaster_type"),
|
| 184 |
+
"area_description": alert.get("area_description"),
|
| 185 |
+
"start_time": alert.get("effective_start_time"),
|
| 186 |
+
"end_time": alert.get("effective_end_time")
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
location_alerts.append(alert_data)
|
| 190 |
+
|
| 191 |
+
results[location] = location_alerts
|
| 192 |
+
total_alerts += len(location_alerts)
|
| 193 |
+
|
| 194 |
+
return {
|
| 195 |
+
"status": "success",
|
| 196 |
+
"total_alerts": total_alerts,
|
| 197 |
+
"locations_searched": len(locations),
|
| 198 |
+
"data": results
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
except Exception as e:
|
| 202 |
+
return {
|
| 203 |
+
"status": "error",
|
| 204 |
+
"message": f"Error processing request: {str(e)}",
|
| 205 |
+
"data": {}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
def get_alert_summary() -> Dict[str, Any]:
|
| 209 |
+
"""
|
| 210 |
+
Get summary of all current alerts by severity
|
| 211 |
+
"""
|
| 212 |
+
try:
|
| 213 |
+
all_alerts = weather_api.fetch_alerts()
|
| 214 |
+
|
| 215 |
+
if not all_alerts:
|
| 216 |
+
return {
|
| 217 |
+
"status": "error",
|
| 218 |
+
"message": "No alerts found or error fetching data",
|
| 219 |
+
"summary": {}
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
summary = {"WARNING": 0, "ALERT": 0, "WATCH": 0, "Yellow": 0, "Orange": 0, "Other": 0}
|
| 223 |
+
disaster_types = {}
|
| 224 |
+
states = {}
|
| 225 |
+
|
| 226 |
+
for alert in all_alerts:
|
| 227 |
+
# Count by severity
|
| 228 |
+
severity = alert.get("severity", "Other")
|
| 229 |
+
if severity in summary:
|
| 230 |
+
summary[severity] += 1
|
| 231 |
+
else:
|
| 232 |
+
summary["Other"] += 1
|
| 233 |
+
|
| 234 |
+
# Count by disaster type
|
| 235 |
+
disaster_type = alert.get("disaster_type", "Unknown")
|
| 236 |
+
disaster_types[disaster_type] = disaster_types.get(disaster_type, 0) + 1
|
| 237 |
+
|
| 238 |
+
# Count by state (extract from area_description)
|
| 239 |
+
area_desc = alert.get("area_description", "")
|
| 240 |
+
if "districts of" in area_desc:
|
| 241 |
+
state = area_desc.split("districts of")[-1].strip()
|
| 242 |
+
states[state] = states.get(state, 0) + 1
|
| 243 |
+
|
| 244 |
+
total = sum(summary.values())
|
| 245 |
+
|
| 246 |
+
return {
|
| 247 |
+
"status": "success",
|
| 248 |
+
"total_alerts": total,
|
| 249 |
+
"severity_breakdown": summary,
|
| 250 |
+
"top_disaster_types": dict(sorted(disaster_types.items(), key=lambda x: x[1], reverse=True)[:10]),
|
| 251 |
+
"top_affected_states": dict(sorted(states.items(), key=lambda x: x[1], reverse=True)[:10])
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
except Exception as e:
|
| 255 |
+
return {
|
| 256 |
+
"status": "error",
|
| 257 |
+
"message": f"Error getting summary: {str(e)}",
|
| 258 |
+
"summary": {}
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
def get_available_locations(limit: int = 50) -> Dict[str, Any]:
|
| 262 |
+
"""
|
| 263 |
+
Get list of locations with current alerts
|
| 264 |
+
"""
|
| 265 |
+
try:
|
| 266 |
+
all_alerts = weather_api.fetch_alerts()
|
| 267 |
+
|
| 268 |
+
if not all_alerts:
|
| 269 |
+
return {
|
| 270 |
+
"status": "error",
|
| 271 |
+
"message": "No alerts found or error fetching data",
|
| 272 |
+
"locations": []
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
locations = set()
|
| 276 |
+
for alert in all_alerts:
|
| 277 |
+
area_desc = alert.get("area_description", "").strip()
|
| 278 |
+
if area_desc:
|
| 279 |
+
locations.add(area_desc)
|
| 280 |
+
|
| 281 |
+
sorted_locations = sorted(list(locations))[:limit]
|
| 282 |
+
|
| 283 |
+
return {
|
| 284 |
+
"status": "success",
|
| 285 |
+
"total_available": len(locations),
|
| 286 |
+
"returned": len(sorted_locations),
|
| 287 |
+
"locations": sorted_locations
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
except Exception as e:
|
| 291 |
+
return {
|
| 292 |
+
"status": "error",
|
| 293 |
+
"message": f"Error getting locations: {str(e)}",
|
| 294 |
+
"locations": []
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
# Function dispatcher for OpenAI (Updated for tool_calls)
|
| 298 |
+
def execute_function(function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
| 299 |
+
"""
|
| 300 |
+
Execute the appropriate function based on OpenAI's tool call
|
| 301 |
+
"""
|
| 302 |
+
if function_name == "get_weather_alerts":
|
| 303 |
+
return get_weather_alerts(**arguments)
|
| 304 |
+
elif function_name == "get_weather":
|
| 305 |
+
return get_weather(**arguments)
|
| 306 |
+
elif function_name == "get_alert_summary":
|
| 307 |
+
return get_alert_summary(**arguments)
|
| 308 |
+
elif function_name == "get_available_locations":
|
| 309 |
+
return get_available_locations(**arguments)
|
| 310 |
+
else:
|
| 311 |
+
return {
|
| 312 |
+
"status": "error",
|
| 313 |
+
"message": f"Unknown function: {function_name}"
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
def chat_with_weather_assistant(user_query: str = "", loop: bool = False, api_key: str = os.getenv("API_KEY"), api_url: str = os.getenv("API_URL"), model_name: str = os.getenv("MODEL_NAME")) -> None:
|
| 317 |
+
"""
|
| 318 |
+
Chat with the weather alert assistant.
|
| 319 |
+
|
| 320 |
+
Args:
|
| 321 |
+
user_query (str, optional): The user's query about weather alerts. If empty, will prompt for input.
|
| 322 |
+
loop (bool, optional): Whether to continue the conversation in a loop.
|
| 323 |
+
If True, will keep asking for new queries until 'quit' is entered.
|
| 324 |
+
If False, will process a single query and return. Defaults to False.
|
| 325 |
+
"""
|
| 326 |
+
if not api_key or not api_url or not model_name:
|
| 327 |
+
raise ValueError("API key, API URL, and model name are required.")
|
| 328 |
+
|
| 329 |
+
client = OpenAI(api_key=api_key, base_url=api_url)
|
| 330 |
+
|
| 331 |
+
while True:
|
| 332 |
+
# Get user input if not provided or in loop mode
|
| 333 |
+
if not user_query:
|
| 334 |
+
user_input = input("\nAsk about weather alerts (or 'quit' to exit): ")
|
| 335 |
+
if user_input.lower() == 'quit':
|
| 336 |
+
break
|
| 337 |
+
else:
|
| 338 |
+
user_input = user_query
|
| 339 |
+
user_query = None # Clear the query after first use
|
| 340 |
+
|
| 341 |
+
try:
|
| 342 |
+
# Initial API call
|
| 343 |
+
response = client.chat.completions.create(
|
| 344 |
+
model=model_name,
|
| 345 |
+
messages=[
|
| 346 |
+
{
|
| 347 |
+
"role": "system",
|
| 348 |
+
"content": "You are a weather information/alert assistant for India. Use the provided functions to help users get weather alert information. Always provide helpful and accurate information about weather alerts. Your respose should be in same language as user query. Response only should in paragraph only there should not be any table or list till user say to show table or list and response should be in easy language which can understand by uneducated person."
|
| 349 |
+
},
|
| 350 |
+
{"role": "user", "content": user_input}
|
| 351 |
+
],
|
| 352 |
+
tools=WEATHER_TOOLS,
|
| 353 |
+
tool_choice="auto"
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
message = response.choices[0].message
|
| 357 |
+
|
| 358 |
+
if hasattr(message, 'tool_calls') and message.tool_calls:
|
| 359 |
+
# Prepare messages for function execution
|
| 360 |
+
messages = [
|
| 361 |
+
{
|
| 362 |
+
"role": "system",
|
| 363 |
+
"content": "You are a weather information/alert assistant for India. Use the provided functions to help users get weather alert information. Always provide helpful and accurate information about weather alerts. Your response should be in the same language as the user query. Response should be in paragraph format only (no tables or lists unless explicitly requested) and in simple language that can be understood by everyone."
|
| 364 |
+
},
|
| 365 |
+
{"role": "user", "content": user_input},
|
| 366 |
+
message # Add the assistant message with tool calls
|
| 367 |
+
]
|
| 368 |
+
|
| 369 |
+
# Execute all tool calls
|
| 370 |
+
for tool_call in message.tool_calls:
|
| 371 |
+
function_name = tool_call.function.name
|
| 372 |
+
function_args = json.loads(tool_call.function.arguments)
|
| 373 |
+
function_result = execute_function(function_name, function_args)
|
| 374 |
+
|
| 375 |
+
messages.append({
|
| 376 |
+
"role": "tool",
|
| 377 |
+
"tool_call_id": tool_call.id,
|
| 378 |
+
"content": json.dumps(function_result)
|
| 379 |
+
})
|
| 380 |
+
|
| 381 |
+
# Get final response
|
| 382 |
+
final_response = client.chat.completions.create(
|
| 383 |
+
model=model_name,
|
| 384 |
+
messages=messages
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
final_message = final_response.choices[0].message.content
|
| 388 |
+
print(f"\n🤖 Assistant: {final_message}")
|
| 389 |
+
|
| 390 |
+
else:
|
| 391 |
+
print(f"\n🤖 Assistant: {message.content}")
|
| 392 |
+
|
| 393 |
+
# If not in loop mode, break after one iteration
|
| 394 |
+
if not loop:
|
| 395 |
+
break
|
| 396 |
+
|
| 397 |
+
except Exception as e:
|
| 398 |
+
print(f"❌ Error: {e}")
|
| 399 |
+
if not loop:
|
| 400 |
+
break
|
| 401 |
+
|
| 402 |
+
if __name__ == "__main__":
|
| 403 |
+
chat_with_weather_assistant()
|
| 404 |
+
# print(WEATHER_TOOLS)
|
| 405 |
+
# get me weather alert of mumbai
|
easy_agents.py
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# easy_agents.py
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import requests
|
| 5 |
+
import logging
|
| 6 |
+
from typing import Optional, Dict, Any, Union, List
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
# Configure logging
|
| 11 |
+
logging.basicConfig(level=logging.INFO)
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
class EasyFarmsAgent:
|
| 15 |
+
"""
|
| 16 |
+
EasyFarms AI Agent optimized for function calling in AI systems.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(self, timeout: int = 30, max_retries: int = 3):
|
| 20 |
+
"""Initialize the agent with configuration options."""
|
| 21 |
+
self.timeout = timeout
|
| 22 |
+
self.max_retries = max_retries
|
| 23 |
+
|
| 24 |
+
# Create a session for connection pooling
|
| 25 |
+
self.session = requests.Session()
|
| 26 |
+
self.session.headers.update({
|
| 27 |
+
'User-Agent': 'EasyFarms-Agent/1.0',
|
| 28 |
+
'Accept': 'application/json'
|
| 29 |
+
})
|
| 30 |
+
|
| 31 |
+
# Load mappings
|
| 32 |
+
self._load_mappings()
|
| 33 |
+
|
| 34 |
+
def _load_mappings(self):
|
| 35 |
+
"""Pre-load all mappings for faster access."""
|
| 36 |
+
self.soil_mapping = {
|
| 37 |
+
"black": 0, "clayey": 1, "loamy": 2, "red": 3, "sandy": 4
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
self.fertilizer_crop_mapping = {
|
| 41 |
+
"barley": 0, "cotton": 1, "ground nuts": 2, "maize": 3, "millets": 4,
|
| 42 |
+
"oil seeds": 5, "paddy": 6, "pulses": 7, "sugarcane": 8, "tobacco": 9, "wheat": 10
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
self.disease_crop_mapping = {
|
| 46 |
+
"almond": "almond", "aloe": "aloe", "apple": "apple", "apricot": "apricot",
|
| 47 |
+
"avocado": "avocado", "bamboo": "bamboo", "banana": "banana", "barley": "barley",
|
| 48 |
+
"bean": "bean", "bitter_gourd": "bitter_gourd", "black_plum": "black_plum",
|
| 49 |
+
"blackberry": "blackberry", "bottle_gourd": "bottle_gourd", "cabbage": "cabbage",
|
| 50 |
+
"canola": "canola", "carrot": "carrot", "cashew": "cashew", "cauliflower": "cauliflower",
|
| 51 |
+
"chard": "chard", "cherry": "cherry", "chickpea": "chickpea", "citrus": "citrus",
|
| 52 |
+
"cocoa": "cocoa", "coconut": "coconut", "coffee": "coffee", "cotton": "cotton",
|
| 53 |
+
"cucumber": "cucumber", "currant": "currant", "curry_leaf_tree": "curry_leaf_tree",
|
| 54 |
+
"date": "date", "eggplant": "eggplant", "fig": "fig", "garlic": "garlic",
|
| 55 |
+
"ginger": "ginger", "gram": "gram", "grape": "grape", "guava": "guava",
|
| 56 |
+
"herb": "herb", "jackfruit": "jackfruit", "leek": "leek", "lentil": "lentil",
|
| 57 |
+
"lettuce": "lettuce", "maize": "maize", "mango": "mango", "manioc": "manioc",
|
| 58 |
+
"melon": "melon", "millet": "millet", "mustard": "mustard", "okra": "okra",
|
| 59 |
+
"olive": "olive", "onion": "onion", "papaya": "papaya", "pea": "pea",
|
| 60 |
+
"peach": "peach", "peanut": "peanut", "pear": "pear", "pepper": "pepper",
|
| 61 |
+
"pigeonpea": "pigeonpea", "pineapple": "pineapple", "pistachio": "pistachio",
|
| 62 |
+
"plum": "plum", "pomegranate": "pomegranate", "potato": "potato",
|
| 63 |
+
"pumpkin": "pumpkin", "radish": "radish", "raspberry": "raspberry",
|
| 64 |
+
"rice": "rice", "rose": "rose", "rye": "rye", "sorghum": "sorghum",
|
| 65 |
+
"soybean": "soybean", "strawberry": "strawberry", "sugarbeet": "sugarbeet",
|
| 66 |
+
"sugarcane": "sugarcane", "sunflower": "sunflower", "sweetpotato": "sweetpotato",
|
| 67 |
+
"tea": "tea", "tamarind": "tamarind", "tobacco": "tobacco", "tomato": "tomato",
|
| 68 |
+
"turmeric": "turmeric", "turnip": "turnip", "wheat": "wheat", "zucchini": "zucchini"
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
self.endpoints = {
|
| 72 |
+
"crop": "https://nitinbot001-agrigo.hf.space/crop-recommendation",
|
| 73 |
+
"fertilizer": "https://nitinbot001-agrigo.hf.space/fertilizer-recommendation",
|
| 74 |
+
"disease": "https://api-for-disease-detection.vercel.app/analyze-plant"
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
def _make_request_with_retry(self, method: str, url: str, **kwargs) -> Dict[str, Any]:
|
| 78 |
+
"""Make HTTP request with retry logic and handle both JSON and HTML responses."""
|
| 79 |
+
last_exception = None
|
| 80 |
+
|
| 81 |
+
for attempt in range(self.max_retries):
|
| 82 |
+
try:
|
| 83 |
+
response = self.session.request(method, url, timeout=self.timeout, **kwargs)
|
| 84 |
+
response.raise_for_status()
|
| 85 |
+
|
| 86 |
+
content_type = response.headers.get('content-type', '')
|
| 87 |
+
|
| 88 |
+
if 'application/json' in content_type:
|
| 89 |
+
try:
|
| 90 |
+
return response.json()
|
| 91 |
+
except ValueError as e:
|
| 92 |
+
error_msg = f"Invalid JSON response: {response.text[:200]}..."
|
| 93 |
+
logger.error(error_msg)
|
| 94 |
+
raise ValueError(error_msg) from e
|
| 95 |
+
|
| 96 |
+
elif 'text/html' in content_type:
|
| 97 |
+
# Handle HTML response
|
| 98 |
+
logger.warning(f"Received HTML response from {url}, attempting to parse...")
|
| 99 |
+
return self._parse_html_response(response.text)
|
| 100 |
+
|
| 101 |
+
else:
|
| 102 |
+
error_msg = f"Unexpected content type: {content_type}. Response: {response.text[:200]}..."
|
| 103 |
+
logger.error(error_msg)
|
| 104 |
+
raise ValueError(error_msg)
|
| 105 |
+
|
| 106 |
+
except requests.exceptions.RequestException as e:
|
| 107 |
+
last_exception = e
|
| 108 |
+
if attempt < self.max_retries - 1:
|
| 109 |
+
wait_time = (attempt + 1) * 2
|
| 110 |
+
logger.warning(f"Request failed (attempt {attempt + 1}), retrying in {wait_time}s: {e}")
|
| 111 |
+
time.sleep(wait_time)
|
| 112 |
+
|
| 113 |
+
raise last_exception or Exception("Failed to get valid response after retries")
|
| 114 |
+
|
| 115 |
+
def _parse_html_response(self, html_content: str) -> Dict[str, Any]:
|
| 116 |
+
"""
|
| 117 |
+
Parse HTML response to extract the recommendation from the specified selector path.
|
| 118 |
+
"""
|
| 119 |
+
try:
|
| 120 |
+
from bs4 import BeautifulSoup
|
| 121 |
+
except ImportError:
|
| 122 |
+
return {
|
| 123 |
+
'status': 'error',
|
| 124 |
+
'message': 'BeautifulSoup4 is required for HTML parsing. Install it with: pip install beautifulsoup4',
|
| 125 |
+
'raw_html': html_content[:500] + '...'
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
try:
|
| 129 |
+
soup = BeautifulSoup(html_content, 'html.parser')
|
| 130 |
+
result = {}
|
| 131 |
+
|
| 132 |
+
# Extract recommendation using the specified CSS selector
|
| 133 |
+
recommendation = soup.select_one("body > div > div > h3 > b > span")
|
| 134 |
+
|
| 135 |
+
if recommendation:
|
| 136 |
+
result['prediction'] = recommendation.text.strip()
|
| 137 |
+
result['status'] = 'success'
|
| 138 |
+
else:
|
| 139 |
+
# Try to find any error messages if the recommendation isn't found
|
| 140 |
+
error_msg = soup.find('div', class_='error') or soup.find('p', class_='error')
|
| 141 |
+
if error_msg:
|
| 142 |
+
result['error'] = error_msg.text.strip()
|
| 143 |
+
else:
|
| 144 |
+
result['error'] = 'No recommendation found in the response'
|
| 145 |
+
|
| 146 |
+
result['status'] = 'error'
|
| 147 |
+
result['raw_html'] = html_content[:500] + '...'
|
| 148 |
+
|
| 149 |
+
return result
|
| 150 |
+
|
| 151 |
+
except Exception as e:
|
| 152 |
+
return {
|
| 153 |
+
'status': 'error',
|
| 154 |
+
'message': f'Error parsing HTML response: {str(e)}',
|
| 155 |
+
'raw_html': html_content[:500] + '...'
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
# Global agent instance
|
| 159 |
+
_easyfarms_agent = EasyFarmsAgent()
|
| 160 |
+
|
| 161 |
+
# =============================================================================
|
| 162 |
+
# FUNCTION DEFINITIONS FOR AI AGENTS
|
| 163 |
+
# =============================================================================
|
| 164 |
+
|
| 165 |
+
# Function schemas for AI agent function calling
|
| 166 |
+
EASYFARMS_FUNCTION_SCHEMAS = [
|
| 167 |
+
{
|
| 168 |
+
"name": "get_crop_recommendation",
|
| 169 |
+
"description": "Get crop recommendation based on soil nutrients (N, P, K) and environmental conditions. Returns the best crop to grow given current soil and weather conditions.",
|
| 170 |
+
"parameters": {
|
| 171 |
+
"type": "object",
|
| 172 |
+
"properties": {
|
| 173 |
+
"N": {
|
| 174 |
+
"type": "integer",
|
| 175 |
+
"description": "Nitrogen content in soil (ppm)",
|
| 176 |
+
"minimum": 0,
|
| 177 |
+
"maximum": 200
|
| 178 |
+
},
|
| 179 |
+
"P": {
|
| 180 |
+
"type": "integer",
|
| 181 |
+
"description": "Phosphorous content in soil (ppm)",
|
| 182 |
+
"minimum": 0,
|
| 183 |
+
"maximum": 150
|
| 184 |
+
},
|
| 185 |
+
"K": {
|
| 186 |
+
"type": "integer",
|
| 187 |
+
"description": "Potassium content in soil (ppm)",
|
| 188 |
+
"minimum": 0,
|
| 189 |
+
"maximum": 300
|
| 190 |
+
},
|
| 191 |
+
"temperature": {
|
| 192 |
+
"type": "number",
|
| 193 |
+
"description": "Temperature in Celsius",
|
| 194 |
+
"minimum": -10,
|
| 195 |
+
"maximum": 50
|
| 196 |
+
},
|
| 197 |
+
"humidity": {
|
| 198 |
+
"type": "number",
|
| 199 |
+
"description": "Relative humidity percentage",
|
| 200 |
+
"minimum": 0,
|
| 201 |
+
"maximum": 100
|
| 202 |
+
},
|
| 203 |
+
"ph": {
|
| 204 |
+
"type": "number",
|
| 205 |
+
"description": "Soil pH level",
|
| 206 |
+
"minimum": 0,
|
| 207 |
+
"maximum": 14
|
| 208 |
+
},
|
| 209 |
+
"rainfall": {
|
| 210 |
+
"type": "number",
|
| 211 |
+
"description": "Average rainfall in mm (default: 100)",
|
| 212 |
+
"minimum": 0,
|
| 213 |
+
"maximum": 500,
|
| 214 |
+
"default": 100
|
| 215 |
+
}
|
| 216 |
+
},
|
| 217 |
+
"required": ["N", "P", "K", "temperature", "humidity"]
|
| 218 |
+
}
|
| 219 |
+
},
|
| 220 |
+
{
|
| 221 |
+
"name": "get_fertilizer_recommendation",
|
| 222 |
+
"description": "Get fertilizer recommendation for a specific crop based on soil conditions and nutrient levels. Returns the best fertilizer type to use.",
|
| 223 |
+
"parameters": {
|
| 224 |
+
"type": "object",
|
| 225 |
+
"properties": {
|
| 226 |
+
"crop": {
|
| 227 |
+
"type": "string",
|
| 228 |
+
"description": "Crop type",
|
| 229 |
+
"enum": ["barley", "cotton", "ground nuts", "maize", "millets", "oil seeds", "paddy", "pulses", "sugarcane", "tobacco", "wheat"]
|
| 230 |
+
},
|
| 231 |
+
"soil": {
|
| 232 |
+
"type": "string",
|
| 233 |
+
"description": "Soil type",
|
| 234 |
+
"enum": ["black", "clayey", "loamy", "red", "sandy"]
|
| 235 |
+
},
|
| 236 |
+
"temperature": {
|
| 237 |
+
"type": "number",
|
| 238 |
+
"description": "Temperature in Celsius",
|
| 239 |
+
"minimum": -10,
|
| 240 |
+
"maximum": 50
|
| 241 |
+
},
|
| 242 |
+
"humidity": {
|
| 243 |
+
"type": "number",
|
| 244 |
+
"description": "Relative humidity percentage",
|
| 245 |
+
"minimum": 0,
|
| 246 |
+
"maximum": 100
|
| 247 |
+
},
|
| 248 |
+
"moisture": {
|
| 249 |
+
"type": "number",
|
| 250 |
+
"description": "Soil moisture percentage",
|
| 251 |
+
"minimum": 0,
|
| 252 |
+
"maximum": 100
|
| 253 |
+
},
|
| 254 |
+
"N": {
|
| 255 |
+
"type": "integer",
|
| 256 |
+
"description": "Nitrogen content in soil (ppm)",
|
| 257 |
+
"minimum": 0,
|
| 258 |
+
"maximum": 200
|
| 259 |
+
},
|
| 260 |
+
"P": {
|
| 261 |
+
"type": "integer",
|
| 262 |
+
"description": "Phosphorous content in soil (ppm)",
|
| 263 |
+
"minimum": 0,
|
| 264 |
+
"maximum": 150
|
| 265 |
+
},
|
| 266 |
+
"K": {
|
| 267 |
+
"type": "integer",
|
| 268 |
+
"description": "Potassium content in soil (ppm)",
|
| 269 |
+
"minimum": 0,
|
| 270 |
+
"maximum": 300
|
| 271 |
+
}
|
| 272 |
+
},
|
| 273 |
+
"required": ["crop", "soil", "temperature", "humidity", "moisture", "N", "P", "K"]
|
| 274 |
+
}
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"name": "detect_plant_disease",
|
| 278 |
+
"description": "Detect plant diseases from an image of a plant leaf or crop. Analyzes the image to identify potential diseases and provides treatment recommendations.",
|
| 279 |
+
"parameters": {
|
| 280 |
+
"type": "object",
|
| 281 |
+
"properties": {
|
| 282 |
+
"crop": {
|
| 283 |
+
"type": "string",
|
| 284 |
+
"description": "Type of crop/plant in the image",
|
| 285 |
+
"enum": ["almond", "aloe", "apple", "apricot", "avocado", "bamboo", "banana", "barley", "bean", "bitter_gourd", "black_plum", "blackberry", "bottle_gourd", "cabbage", "canola", "carrot", "cashew", "cauliflower", "chard", "cherry", "chickpea", "citrus", "cocoa", "coconut", "coffee", "cotton", "cucumber", "currant", "curry_leaf_tree", "date", "eggplant", "fig", "garlic", "ginger", "gram", "grape", "guava", "herb", "jackfruit", "leek", "lentil", "lettuce", "maize", "mango", "manioc", "melon", "millet", "mustard", "okra", "olive", "onion", "papaya", "pea", "peach", "peanut", "pear", "pepper", "pigeonpea", "pineapple", "pistachio", "plum", "pomegranate", "potato", "pumpkin", "radish", "raspberry", "rice", "rose", "rye", "sorghum", "soybean", "strawberry", "sugarbeet", "sugarcane", "sunflower", "sweetpotato", "tea", "tamarind", "tobacco", "tomato", "turmeric", "turnip", "wheat", "zucchini"]
|
| 286 |
+
},
|
| 287 |
+
"image_path": {
|
| 288 |
+
"type": "string",
|
| 289 |
+
"description": "Path to the plant/leaf image file"
|
| 290 |
+
},
|
| 291 |
+
"language": {
|
| 292 |
+
"type": "string",
|
| 293 |
+
"description": "Response language code (default: 'en')",
|
| 294 |
+
"default": "en",
|
| 295 |
+
"enum": ["en", "hi", "es", "fr", "de"]
|
| 296 |
+
}
|
| 297 |
+
},
|
| 298 |
+
"required": ["crop", "image_path"]
|
| 299 |
+
}
|
| 300 |
+
},
|
| 301 |
+
{
|
| 302 |
+
"name": "get_supported_options",
|
| 303 |
+
"description": "Get lists of supported crops and soil types for different analysis modes. Useful for showing available options to users.",
|
| 304 |
+
"parameters": {
|
| 305 |
+
"type": "object",
|
| 306 |
+
"properties": {
|
| 307 |
+
"mode": {
|
| 308 |
+
"type": "string",
|
| 309 |
+
"description": "Mode to get supported options for",
|
| 310 |
+
"enum": ["fertilizer_crops", "disease_crops", "soil_types", "all"]
|
| 311 |
+
}
|
| 312 |
+
},
|
| 313 |
+
"required": ["mode"]
|
| 314 |
+
}
|
| 315 |
+
}
|
| 316 |
+
]
|
| 317 |
+
|
| 318 |
+
# =============================================================================
|
| 319 |
+
# FUNCTION IMPLEMENTATIONS
|
| 320 |
+
# =============================================================================
|
| 321 |
+
|
| 322 |
+
def get_crop_recommendation(
|
| 323 |
+
N: int,
|
| 324 |
+
P: int,
|
| 325 |
+
K: int,
|
| 326 |
+
temperature: float,
|
| 327 |
+
humidity: float,
|
| 328 |
+
ph: float = None,
|
| 329 |
+
rainfall: float = 100
|
| 330 |
+
) -> Dict[str, Any]:
|
| 331 |
+
try:
|
| 332 |
+
data = {
|
| 333 |
+
"N": N, "P": P, "K": K,
|
| 334 |
+
"temperature": temperature,
|
| 335 |
+
"humidity": humidity,
|
| 336 |
+
"ph": ph if ph is not None else 6.5,
|
| 337 |
+
"rainfall": rainfall
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
# The response will be parsed by _make_request_with_retry
|
| 341 |
+
response = _easyfarms_agent._make_request_with_retry(
|
| 342 |
+
"POST",
|
| 343 |
+
_easyfarms_agent.endpoints["crop"],
|
| 344 |
+
data=data
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
# Add input parameters to the response
|
| 348 |
+
if isinstance(response, dict):
|
| 349 |
+
response['input_parameters'] = data
|
| 350 |
+
if 'mode' not in response:
|
| 351 |
+
response['mode'] = 'crop_recommendation'
|
| 352 |
+
|
| 353 |
+
logger.info(f"Crop recommendation response: {response}")
|
| 354 |
+
return response
|
| 355 |
+
|
| 356 |
+
except Exception as e:
|
| 357 |
+
error_result = {
|
| 358 |
+
"error": str(e),
|
| 359 |
+
"status": "error",
|
| 360 |
+
"mode": "crop_recommendation"
|
| 361 |
+
}
|
| 362 |
+
logger.error(f"Crop recommendation failed: {e}")
|
| 363 |
+
return error_result
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def get_fertilizer_recommendation(
|
| 367 |
+
crop: str,
|
| 368 |
+
soil: str,
|
| 369 |
+
temperature: float,
|
| 370 |
+
humidity: float,
|
| 371 |
+
moisture: float,
|
| 372 |
+
N: int,
|
| 373 |
+
P: int,
|
| 374 |
+
K: int
|
| 375 |
+
) -> Dict[str, Any]:
|
| 376 |
+
try:
|
| 377 |
+
# Map soil and crop to codes
|
| 378 |
+
soil_code = _easyfarms_agent.soil_mapping.get(soil.lower(), soil)
|
| 379 |
+
crop_code = _easyfarms_agent.fertilizer_crop_mapping.get(crop.lower(), crop)
|
| 380 |
+
|
| 381 |
+
data = {
|
| 382 |
+
"temperature": temperature,
|
| 383 |
+
"humidity": humidity,
|
| 384 |
+
"moisture": moisture,
|
| 385 |
+
"N": N, "P": P, "K": K,
|
| 386 |
+
"soil": soil_code,
|
| 387 |
+
"crop": crop_code
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
# The response will be parsed by _make_request_with_retry
|
| 391 |
+
response = _easyfarms_agent._make_request_with_retry(
|
| 392 |
+
"POST",
|
| 393 |
+
_easyfarms_agent.endpoints["fertilizer"],
|
| 394 |
+
data=data
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
# Add input parameters to the response
|
| 398 |
+
if isinstance(response, dict):
|
| 399 |
+
response['input_parameters'] = {
|
| 400 |
+
**data,
|
| 401 |
+
"original_soil": soil,
|
| 402 |
+
"original_crop": crop
|
| 403 |
+
}
|
| 404 |
+
if 'mode' not in response:
|
| 405 |
+
response['mode'] = 'fertilizer_recommendation'
|
| 406 |
+
|
| 407 |
+
logger.info(f"Fertilizer recommendation response: {response}")
|
| 408 |
+
return response
|
| 409 |
+
|
| 410 |
+
except Exception as e:
|
| 411 |
+
error_result = {
|
| 412 |
+
"status": "error",
|
| 413 |
+
"error": str(e),
|
| 414 |
+
"mode": "fertilizer_recommendation"
|
| 415 |
+
}
|
| 416 |
+
logger.error(f"Fertilizer recommendation failed: {e}")
|
| 417 |
+
return error_result
|
| 418 |
+
|
| 419 |
+
def detect_plant_disease(
|
| 420 |
+
crop: str,
|
| 421 |
+
image_path: str,
|
| 422 |
+
language: str = "en"
|
| 423 |
+
) -> Dict[str, Any]:
|
| 424 |
+
"""
|
| 425 |
+
Detect plant diseases from an image using the exact API format from curl.
|
| 426 |
+
|
| 427 |
+
Args:
|
| 428 |
+
crop: Type of crop/plant in the image
|
| 429 |
+
image_path: Path to the plant/leaf image file
|
| 430 |
+
language: Response language code (default: 'en')
|
| 431 |
+
|
| 432 |
+
Returns:
|
| 433 |
+
Dictionary with disease detection results and metadata
|
| 434 |
+
"""
|
| 435 |
+
try:
|
| 436 |
+
# Map crop to disease API format
|
| 437 |
+
crop_key = _easyfarms_agent.disease_crop_mapping.get(crop.lower(), crop.lower())
|
| 438 |
+
|
| 439 |
+
# Check if file exists
|
| 440 |
+
import os
|
| 441 |
+
if not os.path.exists(image_path):
|
| 442 |
+
return {
|
| 443 |
+
"error": f"Image file not found: {image_path}",
|
| 444 |
+
"status": "error",
|
| 445 |
+
"mode": "disease_detection"
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
# Prepare headers exactly like the curl command
|
| 449 |
+
headers = {
|
| 450 |
+
'accept': '*/*',
|
| 451 |
+
'accept-language': 'en-IN,en-US;q=0.9,en;q=0.8,hi;q=0.7',
|
| 452 |
+
'origin': 'https://app.easyfarms.in',
|
| 453 |
+
'referer': 'https://app.easyfarms.in/',
|
| 454 |
+
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
| 455 |
+
'sec-ch-ua-mobile': '?1',
|
| 456 |
+
'sec-ch-ua-platform': '"Android"',
|
| 457 |
+
'sec-fetch-dest': 'empty',
|
| 458 |
+
'sec-fetch-mode': 'cors',
|
| 459 |
+
'sec-fetch-site': 'cross-site',
|
| 460 |
+
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36'
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
# Make request with image using the exact format
|
| 464 |
+
with open(image_path, "rb") as f:
|
| 465 |
+
files = {
|
| 466 |
+
'image': ('blob', f, 'image/jpeg')
|
| 467 |
+
}
|
| 468 |
+
data = {
|
| 469 |
+
'crop': crop_key,
|
| 470 |
+
'language': language
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
# Use direct requests instead of the agent's retry method for this specific API
|
| 474 |
+
response = _easyfarms_agent.session.post(
|
| 475 |
+
_easyfarms_agent.endpoints["disease"],
|
| 476 |
+
files=files,
|
| 477 |
+
data=data,
|
| 478 |
+
headers=headers,
|
| 479 |
+
timeout=_easyfarms_agent.timeout
|
| 480 |
+
)
|
| 481 |
+
response.raise_for_status()
|
| 482 |
+
|
| 483 |
+
# Try to parse JSON response
|
| 484 |
+
try:
|
| 485 |
+
result = response.json()
|
| 486 |
+
except ValueError:
|
| 487 |
+
# If JSON parsing fails, return the raw text
|
| 488 |
+
result = {
|
| 489 |
+
"raw_response": response.text,
|
| 490 |
+
"content_type": response.headers.get('content-type', ''),
|
| 491 |
+
"status_code": response.status_code
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
# Add metadata to result
|
| 495 |
+
if isinstance(result, dict):
|
| 496 |
+
result.update({
|
| 497 |
+
"status": "success",
|
| 498 |
+
"mode": "disease_detection",
|
| 499 |
+
"input_parameters": {
|
| 500 |
+
"crop": crop,
|
| 501 |
+
"crop_key": crop_key,
|
| 502 |
+
"language": language,
|
| 503 |
+
"image_path": image_path
|
| 504 |
+
}
|
| 505 |
+
})
|
| 506 |
+
else:
|
| 507 |
+
# If result is not a dict, wrap it
|
| 508 |
+
result = {
|
| 509 |
+
"detection_result": result,
|
| 510 |
+
"status": "success",
|
| 511 |
+
"mode": "disease_detection",
|
| 512 |
+
"input_parameters": {
|
| 513 |
+
"crop": crop,
|
| 514 |
+
"crop_key": crop_key,
|
| 515 |
+
"language": language,
|
| 516 |
+
"image_path": image_path
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
logger.info(f"Disease detection successful for {crop}")
|
| 521 |
+
return result
|
| 522 |
+
|
| 523 |
+
except Exception as e:
|
| 524 |
+
error_result = {
|
| 525 |
+
"error": str(e),
|
| 526 |
+
"status": "error",
|
| 527 |
+
"mode": "disease_detection",
|
| 528 |
+
"input_parameters": {
|
| 529 |
+
"crop": crop,
|
| 530 |
+
"language": language,
|
| 531 |
+
"image_path": image_path
|
| 532 |
+
}
|
| 533 |
+
}
|
| 534 |
+
logger.error(f"Disease detection failed: {e}")
|
| 535 |
+
return error_result
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
def get_supported_options(mode: str) -> Dict[str, Any]:
|
| 539 |
+
"""
|
| 540 |
+
Get lists of supported options for different modes.
|
| 541 |
+
|
| 542 |
+
Args:
|
| 543 |
+
mode: Mode to get options for ("fertilizer_crops", "disease_crops", "soil_types", "all")
|
| 544 |
+
|
| 545 |
+
Returns:
|
| 546 |
+
Dictionary with supported options
|
| 547 |
+
"""
|
| 548 |
+
try:
|
| 549 |
+
result = {"status": "success", "mode": "supported_options"}
|
| 550 |
+
|
| 551 |
+
if mode == "fertilizer_crops":
|
| 552 |
+
result["fertilizer_crops"] = list(_easyfarms_agent.fertilizer_crop_mapping.keys())
|
| 553 |
+
elif mode == "disease_crops":
|
| 554 |
+
result["disease_crops"] = list(_easyfarms_agent.disease_crop_mapping.keys())
|
| 555 |
+
elif mode == "soil_types":
|
| 556 |
+
result["soil_types"] = list(_easyfarms_agent.soil_mapping.keys())
|
| 557 |
+
elif mode == "all":
|
| 558 |
+
result.update({
|
| 559 |
+
"fertilizer_crops": list(_easyfarms_agent.fertilizer_crop_mapping.keys()),
|
| 560 |
+
"disease_crops": list(_easyfarms_agent.disease_crop_mapping.keys()),
|
| 561 |
+
"soil_types": list(_easyfarms_agent.soil_mapping.keys())
|
| 562 |
+
})
|
| 563 |
+
else:
|
| 564 |
+
return {
|
| 565 |
+
"error": f"Invalid mode: {mode}. Use 'fertilizer_crops', 'disease_crops', 'soil_types', or 'all'",
|
| 566 |
+
"status": "error"
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
return result
|
| 570 |
+
|
| 571 |
+
except Exception as e:
|
| 572 |
+
return {
|
| 573 |
+
"error": str(e),
|
| 574 |
+
"status": "error",
|
| 575 |
+
"mode": "supported_options"
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
# =============================================================================
|
| 579 |
+
# FUNCTION MAPPING FOR AI AGENTS
|
| 580 |
+
# =============================================================================
|
| 581 |
+
|
| 582 |
+
# Map function names to implementations
|
| 583 |
+
EASYFARMS_FUNCTIONS = {
|
| 584 |
+
"get_crop_recommendation": get_crop_recommendation,
|
| 585 |
+
"get_fertilizer_recommendation": get_fertilizer_recommendation,
|
| 586 |
+
"detect_plant_disease": detect_plant_disease,
|
| 587 |
+
"get_supported_options": get_supported_options
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
# =============================================================================
|
| 591 |
+
# UTILITY FUNCTIONS FOR AI AGENT INTEGRATION
|
| 592 |
+
# =============================================================================
|
| 593 |
+
|
| 594 |
+
def execute_easyfarms_function(function_name: str, **kwargs) -> Dict[str, Any]:
|
| 595 |
+
"""
|
| 596 |
+
Execute an EasyFarms function by name with given parameters.
|
| 597 |
+
|
| 598 |
+
Args:
|
| 599 |
+
function_name: Name of the function to execute
|
| 600 |
+
**kwargs: Function parameters
|
| 601 |
+
|
| 602 |
+
Returns:
|
| 603 |
+
Function execution result
|
| 604 |
+
"""
|
| 605 |
+
if function_name not in EASYFARMS_FUNCTIONS:
|
| 606 |
+
return {
|
| 607 |
+
"error": f"Unknown function: {function_name}. Available: {list(EASYFARMS_FUNCTIONS.keys())}",
|
| 608 |
+
"status": "error"
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
try:
|
| 612 |
+
return EASYFARMS_FUNCTIONS[function_name](**kwargs)
|
| 613 |
+
except TypeError as e:
|
| 614 |
+
return {
|
| 615 |
+
"error": f"Invalid parameters for {function_name}: {str(e)}",
|
| 616 |
+
"status": "error"
|
| 617 |
+
}
|
| 618 |
+
except Exception as e:
|
| 619 |
+
return {
|
| 620 |
+
"error": f"Execution failed for {function_name}: {str(e)}",
|
| 621 |
+
"status": "error"
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
def get_function_schemas() -> List[Dict]:
|
| 625 |
+
"""Get all function schemas for AI agent registration."""
|
| 626 |
+
return EASYFARMS_FUNCTION_SCHEMAS
|
| 627 |
+
|
| 628 |
+
def get_function_names() -> List[str]:
|
| 629 |
+
"""Get list of available function names."""
|
| 630 |
+
return list(EASYFARMS_FUNCTIONS.keys())
|
| 631 |
+
|
| 632 |
+
# =============================================================================
|
| 633 |
+
# EXAMPLE USAGE FOR AI AGENTS
|
| 634 |
+
# =============================================================================
|
| 635 |
+
|
| 636 |
+
if __name__ == "__main__":
|
| 637 |
+
# Example of how to use in an AI agent
|
| 638 |
+
|
| 639 |
+
print("=== EasyFarms Function Schemas ===")
|
| 640 |
+
schemas = get_function_schemas()
|
| 641 |
+
for schema in schemas:
|
| 642 |
+
print(f"Function: {schema['name']}")
|
| 643 |
+
print(f"Description: {schema['description']}")
|
| 644 |
+
print()
|
| 645 |
+
|
| 646 |
+
print("=== Example Function Calls ===")
|
| 647 |
+
|
| 648 |
+
# Crop recommendation
|
| 649 |
+
crop_result = execute_easyfarms_function(
|
| 650 |
+
"get_crop_recommendation",
|
| 651 |
+
N=90, P=42, K=43,
|
| 652 |
+
temperature=20.87,
|
| 653 |
+
humidity=82.0,
|
| 654 |
+
ph=6.5,
|
| 655 |
+
rainfall=202.9
|
| 656 |
+
)
|
| 657 |
+
print("Crop recommendation:", json.dumps(crop_result, indent=2))
|
| 658 |
+
|
| 659 |
+
# Get supported options
|
| 660 |
+
options_result = execute_easyfarms_function(
|
| 661 |
+
"get_supported_options",
|
| 662 |
+
mode="all"
|
| 663 |
+
)
|
| 664 |
+
print("Supported options:", json.dumps(options_result, indent=2))
|
| 665 |
+
|
| 666 |
+
# Fertilizer recommendation
|
| 667 |
+
fertilizer_result = execute_easyfarms_function(
|
| 668 |
+
"get_fertilizer_recommendation",
|
| 669 |
+
crop="paddy",
|
| 670 |
+
soil="loamy",
|
| 671 |
+
temperature=26.0,
|
| 672 |
+
humidity=52.0,
|
| 673 |
+
moisture=38.0,
|
| 674 |
+
N=37, P=0, K=0
|
| 675 |
+
)
|
| 676 |
+
print("Fertilizer recommendation:", json.dumps(fertilizer_result, indent=2))
|
| 677 |
+
|
| 678 |
+
# Test disease detection (if you have an image file)
|
| 679 |
+
disease_result = execute_easyfarms_function(
|
| 680 |
+
"detect_plant_disease",
|
| 681 |
+
crop="potato",
|
| 682 |
+
image_path="potato-diseases.jpg",
|
| 683 |
+
language="en"
|
| 684 |
+
)
|
| 685 |
+
print("Disease detection:", json.dumps(disease_result, indent=2))
|
weatherr.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# weatherr.py
|
| 2 |
+
|
| 3 |
+
import requests
|
| 4 |
+
import os
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
# Replace with your API keys
|
| 9 |
+
OPENCAGE_API_KEY = os.getenv("OPENCAGE_API_KEY")
|
| 10 |
+
OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY")
|
| 11 |
+
|
| 12 |
+
def get_weather(
|
| 13 |
+
latitude: float = None,
|
| 14 |
+
longitude: float = None,
|
| 15 |
+
address: str = None,
|
| 16 |
+
district: str = None,
|
| 17 |
+
state: str = None,
|
| 18 |
+
units: str = "metric" # "metric" or "imperial"
|
| 19 |
+
) -> dict:
|
| 20 |
+
"""
|
| 21 |
+
Fetch current weather using coordinates or location name (address/district/state).
|
| 22 |
+
|
| 23 |
+
Returns structured weather info from OpenWeatherMap.
|
| 24 |
+
"""
|
| 25 |
+
# Step 1: Determine coordinates
|
| 26 |
+
if latitude is None or longitude is None:
|
| 27 |
+
if address:
|
| 28 |
+
query = address
|
| 29 |
+
elif district and state:
|
| 30 |
+
query = f"{district}, {state}"
|
| 31 |
+
elif district:
|
| 32 |
+
query = district
|
| 33 |
+
else:
|
| 34 |
+
return {"error": "Provide either coordinates or location name (address/district+state)."}
|
| 35 |
+
|
| 36 |
+
# Use OpenCageData geocoding
|
| 37 |
+
geocode_url = "https://api.opencagedata.com/geocode/v1/json"
|
| 38 |
+
params = {
|
| 39 |
+
"q": query,
|
| 40 |
+
"key": OPENCAGE_API_KEY,
|
| 41 |
+
"limit": 1,
|
| 42 |
+
"no_annotations": 1
|
| 43 |
+
}
|
| 44 |
+
try:
|
| 45 |
+
resp = requests.get(geocode_url, params=params, timeout=10)
|
| 46 |
+
resp.raise_for_status()
|
| 47 |
+
data = resp.json()
|
| 48 |
+
if data["results"]:
|
| 49 |
+
latitude = data["results"][0]["geometry"]["lat"]
|
| 50 |
+
longitude = data["results"][0]["geometry"]["lng"]
|
| 51 |
+
else:
|
| 52 |
+
return {"error": f"Location not found: {query}"}
|
| 53 |
+
except Exception as e:
|
| 54 |
+
return {"error": f"Geocoding error: {e}"}
|
| 55 |
+
|
| 56 |
+
# Step 2: Fetch weather from OpenWeatherMap
|
| 57 |
+
weather_url = "https://api.openweathermap.org/data/2.5/weather"
|
| 58 |
+
params = {
|
| 59 |
+
"lat": latitude,
|
| 60 |
+
"lon": longitude,
|
| 61 |
+
"appid": OPENWEATHER_API_KEY,
|
| 62 |
+
"units": units
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
resp = requests.get(weather_url, params=params, timeout=10)
|
| 67 |
+
resp.raise_for_status()
|
| 68 |
+
return {"success": True, "data": resp.json()}
|
| 69 |
+
except Exception as e:
|
| 70 |
+
return {"error": f"Weather API error: {e}"}
|
| 71 |
+
|
| 72 |
+
# Example usage:
|
| 73 |
+
if __name__ == "__main__":
|
| 74 |
+
res = get_weather(district="Bilaspur", state="Chhattisgarh")
|
| 75 |
+
res2 = get_weather(latitude=22.0934, longitude=82.1564)
|
| 76 |
+
res3 = get_weather(address="Bilaspur, Chhattisgarh")
|
| 77 |
+
print(res)
|
| 78 |
+
print(res2)
|
| 79 |
+
print(res3)
|