Spaces:
Sleeping
Sleeping
File size: 6,593 Bytes
8421ec4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
import math
import requests
import ast
import operator
from typing import Union, Dict, Any
# ==============================
# MATH TOOL
# ==============================
def calculate_expression(expression: str) -> Union[float, str]:
"""
Safely evaluate a mathematical expression.
Supported operators: +, -, *, /, **, %, ^ (as power), sqrt, abs, round, sin, cos, tan, log, pi, e
"""
# Safe operators map
operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.Mod: operator.mod,
ast.USub: operator.neg,
ast.UAdd: operator.pos,
}
# Safe functions map
functions = {
"sqrt": math.sqrt,
"abs": abs,
"round": round,
"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"log": math.log,
"max": max,
"min": min,
"ceil": math.ceil,
"floor": math.floor,
"degrees": math.degrees,
"radians": math.radians,
}
# Safe constants
constants = {
"pi": math.pi,
"e": math.e,
"tau": math.tau,
}
def eval_node(node):
if isinstance(node, ast.Num): # < 3.8
return node.n
elif isinstance(node, ast.Constant): # >= 3.8
if isinstance(node.value, (int, float)):
return node.value
raise ValueError(f"Unsupported constant type: {type(node.value)}")
elif isinstance(node, ast.BinOp): # <left> <operator> <right>
op = type(node.op)
if op not in operators:
raise ValueError(f"Unsupported operator: {op}")
return operators[op](eval_node(node.left), eval_node(node.right))
elif isinstance(node, ast.UnaryOp): # <operator> <operand> (e.g., -1)
op = type(node.op)
if op not in operators:
raise ValueError(f"Unsupported unary operator: {op}")
return operators[op](eval_node(node.operand))
elif isinstance(node, ast.Call): # Function calls like sqrt(4)
if not isinstance(node.func, ast.Name):
raise ValueError("Only named functions are supported")
if node.func.id not in functions:
raise ValueError(f"Unsupported function: {node.func.id}")
args = [eval_node(arg) for arg in node.args]
return functions[node.func.id](*args)
elif isinstance(node, ast.Name): # Variables/Constants
if node.id in constants:
return constants[node.id]
raise ValueError(f"Unsupported name: {node.id}")
else:
raise TypeError(f"Unsupported expression node: {type(node)}")
try:
# Pre-process: replace ^ with ** for power
expression = expression.replace("^", "**")
node = ast.parse(expression, mode='eval')
result = eval_node(node.body)
return float(result)
except Exception as e:
return f"Error calculating '{expression}': {str(e)}"
# ==============================
# WEATHER TOOL
# ==============================
def get_current_weather(location: str) -> Dict[str, Any]:
"""
Get current weather for a specific city using Open-Meteo API.
Returns temperature (C), humidity, wind speed, etc.
"""
try:
# 1. Geocoding
geo_url = "https://geocoding-api.open-meteo.com/v1/search"
geo_params = {"name": location, "count": 1, "language": "en", "format": "json"}
geo_res = requests.get(geo_url, params=geo_params, timeout=5)
geo_data = geo_res.json()
if not geo_data.get("results"):
return {"error": f"City '{location}' not found."}
location = geo_data["results"][0]
lat = location["latitude"]
lon = location["longitude"]
city_name = location["name"]
country = location.get("country", "")
# 2. Weather Data
weather_url = "https://api.open-meteo.com/v1/forecast"
weather_params = {
"latitude": lat,
"longitude": lon,
"current": "temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,rain,showers,snowfall,weather_code,cloud_cover,wind_speed_10m",
"timezone": "auto"
}
w_res = requests.get(weather_url, params=weather_params, timeout=5)
w_data = w_res.json()
if "current" not in w_data:
return {"error": "{location} Weather data not available."}
current = w_data["current"]
current_units = w_data["current_units"]
# Decode WMO Weather Code
# source: https://open-meteo.com/en/docs
wmo_code = current["weather_code"]
condition = "Unknown"
if wmo_code == 0: condition = "Clear sky"
elif 1 <= wmo_code <= 3: condition = "Mainly clear, partly cloudy, and overcast"
elif 45 <= wmo_code <= 48: condition = "Fog and depositing rime fog"
elif 51 <= wmo_code <= 55: condition = "Drizzle: Light, moderate, and dense intensity"
elif 56 <= wmo_code <= 57: condition = "Freezing Drizzle: Light and dense intensity"
elif 61 <= wmo_code <= 65: condition = "Rain: Slight, moderate and heavy intensity"
elif 66 <= wmo_code <= 67: condition = "Freezing Rain: Light and heavy intensity"
elif 71 <= wmo_code <= 75: condition = "Snow fall: Slight, moderate, and heavy intensity"
elif 77: condition = "Snow grains"
elif 80 <= wmo_code <= 82: condition = "Rain showers: Slight, moderate, and violent"
elif 85 <= wmo_code <= 86: condition = "Snow showers slight and heavy"
elif 95: condition = "Thunderstorm: Slight or moderate"
elif 96 <= wmo_code <= 99: condition = "Thunderstorm with slight and heavy hail"
return {
"location": f"{city_name}, {country}",
"temperature": f"{current['temperature_2m']} {current_units['temperature_2m']}",
"feels_like": f"{current['apparent_temperature']} {current_units['apparent_temperature']}",
"humidity": f"{current['relative_humidity_2m']} {current_units['relative_humidity_2m']}",
"wind_speed": f"{current['wind_speed_10m']} {current_units['wind_speed_10m']}",
"condition": condition,
"cloud_cover": f"{current['cloud_cover']} {current_units['cloud_cover']}",
"timestamp": current["time"]
}
except Exception as e:
return {"error": f"Failed to fetch weather: {str(e)}"}
|