Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from smolagents import CodeAgent,DuckDuckGoSearchTool, HfApiModel,load_tool,tool
|
| 2 |
import datetime
|
|
|
|
| 3 |
import requests
|
| 4 |
import pytz
|
| 5 |
import yaml
|
|
@@ -18,6 +19,128 @@ def my_custom_tool(arg1:str, arg2:int)-> str: #it's import to specify the return
|
|
| 18 |
"""
|
| 19 |
return "What magic will you build ?"
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
@tool
|
| 22 |
def get_current_time_in_timezone(timezone: str) -> str:
|
| 23 |
"""A tool that fetches the current local time in a specified timezone.
|
|
|
|
| 1 |
from smolagents import CodeAgent,DuckDuckGoSearchTool, HfApiModel,load_tool,tool
|
| 2 |
import datetime
|
| 3 |
+
from difflib import get_close_matches
|
| 4 |
import requests
|
| 5 |
import pytz
|
| 6 |
import yaml
|
|
|
|
| 19 |
"""
|
| 20 |
return "What magic will you build ?"
|
| 21 |
|
| 22 |
+
MIN_YEAR = 2023
|
| 23 |
+
|
| 24 |
+
def sanitize_name(name: str) -> str | None:
|
| 25 |
+
"""Only allow letters, hyphens, apostrophes and spaces (covers names like O'Ward, Häkkinen)."""
|
| 26 |
+
if not re.match(r"^[a-zA-ZÀ-ÿ\s'\-]{2,50}$", name):
|
| 27 |
+
return None
|
| 28 |
+
return name.strip()
|
| 29 |
+
|
| 30 |
+
def sanitize_year(year: int) -> int | None:
|
| 31 |
+
"""Only allow integers within a valid range."""
|
| 32 |
+
if not isinstance(year, int) or isinstance(year, bool):
|
| 33 |
+
return None
|
| 34 |
+
if year < MIN_YEAR or year > datetime.now().year:
|
| 35 |
+
return None
|
| 36 |
+
return year
|
| 37 |
+
|
| 38 |
+
@tool
|
| 39 |
+
def driver_number(name: str, year: int) -> str:
|
| 40 |
+
"""A tool that give the number of a F1 driver for a specific session
|
| 41 |
+
Args:
|
| 42 |
+
name: the name of the driver
|
| 43 |
+
year: the year for which we want the number from 2023
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
# Sanitize inputs before anything else
|
| 47 |
+
clean_name = sanitize_name(name)
|
| 48 |
+
if clean_name is None:
|
| 49 |
+
return "Invalid driver name. Only letters, spaces, hyphens and apostrophes are allowed (2-100 characters)."
|
| 50 |
+
|
| 51 |
+
clean_year = sanitize_year(year)
|
| 52 |
+
if clean_year is None:
|
| 53 |
+
return f"Invalid year. Please provide a year between {MIN_YEAR} and {datetime.now().year}."
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
# Step 1: Get any session_key for the given year
|
| 57 |
+
sessions_resp = requests.get(
|
| 58 |
+
"https://api.openf1.org/v1/sessions",
|
| 59 |
+
params={"year": clean_year},
|
| 60 |
+
timeout=10
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
if sessions_resp.status_code != 200:
|
| 64 |
+
return f"Error fetching sessions: HTTP {sessions_resp.status_code}"
|
| 65 |
+
|
| 66 |
+
sessions = sessions_resp.json()
|
| 67 |
+
|
| 68 |
+
if not sessions:
|
| 69 |
+
return f"No sessions found for year {clean_year}."
|
| 70 |
+
|
| 71 |
+
session_key = sessions[0]["session_key"]
|
| 72 |
+
|
| 73 |
+
# Validate session_key is an integer before using it
|
| 74 |
+
if not isinstance(session_key, int):
|
| 75 |
+
return "Unexpected session data returned from API."
|
| 76 |
+
|
| 77 |
+
# Step 2: Search for the driver by last name within that session
|
| 78 |
+
drivers_resp = requests.get(
|
| 79 |
+
"https://api.openf1.org/v1/drivers",
|
| 80 |
+
params={"session_key": session_key, "last_name": clean_name},
|
| 81 |
+
timeout=10
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
if drivers_resp.status_code != 200:
|
| 85 |
+
return f"Error fetching drivers: HTTP {drivers_resp.status_code}"
|
| 86 |
+
|
| 87 |
+
drivers = drivers_resp.json()
|
| 88 |
+
|
| 89 |
+
# Fallback 1: try full name exact match
|
| 90 |
+
if not drivers:
|
| 91 |
+
drivers_resp = requests.get(
|
| 92 |
+
"https://api.openf1.org/v1/drivers",
|
| 93 |
+
params={"session_key": session_key, "full_name": clean_name.upper()},
|
| 94 |
+
timeout=10
|
| 95 |
+
)
|
| 96 |
+
drivers = drivers_resp.json() if drivers_resp.status_code == 200 else []
|
| 97 |
+
|
| 98 |
+
# Fallback 2: fuzzy match against all drivers in that session
|
| 99 |
+
if not drivers:
|
| 100 |
+
all_drivers_resp = requests.get(
|
| 101 |
+
"https://api.openf1.org/v1/drivers",
|
| 102 |
+
params={"session_key": session_key},
|
| 103 |
+
timeout=10
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
if all_drivers_resp.status_code == 200:
|
| 107 |
+
all_drivers = all_drivers_resp.json()
|
| 108 |
+
|
| 109 |
+
# Build a mapping of searchable names -> driver object
|
| 110 |
+
name_map = {}
|
| 111 |
+
for d in all_drivers:
|
| 112 |
+
name_map[d.get("last_name", "").lower()] = d
|
| 113 |
+
name_map[d.get("full_name", "").lower()] = d
|
| 114 |
+
name_map[d.get("broadcast_name", "").lower()] = d
|
| 115 |
+
name_map[d.get("name_acronym", "").lower()] = d
|
| 116 |
+
|
| 117 |
+
matches = get_close_matches(
|
| 118 |
+
clean_name.lower(),
|
| 119 |
+
name_map.keys(),
|
| 120 |
+
n=1,
|
| 121 |
+
cutoff=0.6
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
if matches:
|
| 125 |
+
drivers = [name_map[matches[0]]]
|
| 126 |
+
|
| 127 |
+
if not drivers:
|
| 128 |
+
return f"No driver found with name '{clean_name}' in {clean_year}."
|
| 129 |
+
|
| 130 |
+
# Validate the returned driver_number is actually an integer
|
| 131 |
+
driver_num = drivers[0].get("driver_number")
|
| 132 |
+
if not isinstance(driver_num, int):
|
| 133 |
+
return "Unexpected driver data returned from API."
|
| 134 |
+
|
| 135 |
+
return str(driver_num)
|
| 136 |
+
|
| 137 |
+
except requests.exceptions.Timeout:
|
| 138 |
+
return "Request timed out. The OpenF1 API may be slow or unavailable."
|
| 139 |
+
except requests.exceptions.ConnectionError:
|
| 140 |
+
return "Could not connect to the OpenF1 API. Check network connectivity."
|
| 141 |
+
except Exception as e:
|
| 142 |
+
return f"Unexpected error: {str(e)}"
|
| 143 |
+
|
| 144 |
@tool
|
| 145 |
def get_current_time_in_timezone(timezone: str) -> str:
|
| 146 |
"""A tool that fetches the current local time in a specified timezone.
|