Spaces:
Running
Running
import os | |
import pandas as pd | |
from smolagents import Tool | |
from typing import List, Dict, Any, Union, Tuple | |
from meteofrance_api import MeteoFranceClient | |
from src.skitour_api import get_topos, get_refuges, get_details_topo, get_massifs, get_recent_outings | |
from src.meteo_france_api import get_massif_conditions | |
from src.utils import geocode_location, assign_location_to_clusters, haversine, llm_summarizer | |
class RefugeTool(Tool): | |
name = "refuge_recherche" | |
description = "Recherche d'un refuge dans un massif donné" | |
inputs = { | |
"massif_id": { | |
"description": "[Optional, default: None] Id du massif souhaité ", | |
"type": "string", | |
} | |
} | |
output_type = "string" | |
def forward(self, massif_id) -> List[Dict]: | |
return get_refuges(massif_id) | |
class GetRoutesTool(Tool): | |
name = "list_routes" | |
description = """ | |
Looking for a list of ski touring routes in a given list of mountain ranges. | |
Returns a list containing the information of the topos found. | |
Use `topo_details` immediately after this tool to get the details of a specific topo. | |
""" | |
inputs = { | |
"mountain_range_ids": { | |
"description": "List of mountain range ids", | |
"type": "string", | |
} | |
} | |
output_type = "any" | |
def forward(self, mountain_range_ids: str) -> List[Dict]: | |
topos = get_topos(mountain_range_ids) | |
return topos | |
class DescribeRouteTool(Tool): | |
name = "describe_route" | |
description = """ | |
Searches for key information about a specific ski touring route, including weather forecasts and associated avalanche risks. | |
Always use this tool after using the `list_routes` tool. | |
This tool returns a dictionary containing the route's information, the avalanche risk estimation bulletin, and the weather forecast for the coming days of the route. | |
""" | |
inputs = { | |
"id_route": { | |
"description": "id of the route", | |
"type": "string", | |
}, | |
"id_range": { | |
"description": "mountain range id of the route", | |
"type": "string"} | |
} | |
output_type = "any" | |
def __init__(self, skitour2meteofrance: dict, llm_engine: Any): | |
super().__init__() | |
self.massifs_infos = skitour2meteofrance | |
self.weather_client = MeteoFranceClient(access_token=os.getenv("METEO_FRANCE_API_KEY")) | |
self.llm_engine = llm_engine | |
def forward(self, id_route: str, id_range: str) -> dict: | |
topo_info = get_details_topo(str(id_route)) | |
avalanche_conditions = get_massif_conditions( | |
self.massifs_infos[str(id_range)]['meteofrance_id'] | |
) | |
lat, lon = topo_info["depart"]["latlon"] | |
weather_forecast = self.weather_client.get_forecast(float(lat), float(lon)) | |
daily_forecast = weather_forecast.forecast[:24] | |
for day_forecast in daily_forecast: | |
day_forecast["dt"] = weather_forecast.timestamp_to_locale_time(day_forecast["dt"]).isoformat() | |
forecast_summary = llm_summarizer(str(daily_forecast), self.llm_engine) | |
avalanche_summary = llm_summarizer(str(avalanche_conditions), self.llm_engine) | |
return { | |
"route_info": topo_info, | |
"avalanche_conditions": avalanche_summary, | |
"daily_weather_forecast": forecast_summary, | |
"route_link": f"https://skitour.fr/topos/{id_route}" | |
} | |
class RecentOutingsTool(Tool): | |
name = "recent_outings" | |
description = """ | |
Searches for recent outings in a given mountain range. | |
Returns a list of the most recent outings in the given range. | |
""" | |
inputs = { | |
"id_range": { | |
"description": "id of the mountain range", | |
"type": "string", | |
} | |
} | |
output_type = "any" | |
def forward(self, id_range: str) -> List[Dict]: | |
return get_recent_outings(id_range) | |
class MountainRangesTool(Tool): | |
name = "list_mountain_ranges" | |
description = """ Searches for the ID(s) of the mountain ranges closest to a given location. | |
If the location is too far from known ranges, the search returns None. | |
Should return a string with the massif IDs separated by commas. | |
""" | |
inputs = { | |
"location": { | |
"description": "Location to search for", | |
"type": "string", | |
}, | |
"num_ranges": { | |
"description": "[Optional, default: 3] Number of closest mountain ranges to return", | |
"type": "number", | |
} | |
} | |
output_type = "string" | |
def __init__(self, clusters: Dict[str, List[Tuple[float, float]]]): | |
super().__init__() | |
self.clusters = clusters | |
def forward(self, location: str, num_ranges: int) -> Union[str, None]: | |
coord_location = geocode_location(location) | |
if not location: | |
return None | |
matched_ranges = assign_location_to_clusters(coord_location, self.clusters, k=num_ranges) | |
list_ranges = [range[0] for range in matched_ranges if range[1] < 100] | |
if not list_ranges: | |
return '' | |
massifs= get_massifs() | |
massif_ids = [_massif['id'] for _massif in massifs if _massif['nom'] in list_ranges] | |
return ", ".join(massif_ids) | |
class ForecastTool(Tool): | |
name = "forecast" | |
description = """Searches for the weather forecast for a given location as well as the current avalanche risk estimation bulletin. | |
Unnecessary if the user is inquiring about a route, as `describe_route` already provides this information.""" | |
inputs = { | |
"location": { | |
"description": "Location to search for", | |
"type": "string", | |
}, | |
} | |
output_type = "any" | |
def __init__(self, llm_engine, clusters: Dict[str, List[Tuple[float, float]]], skitour2meteofrance: dict): | |
super().__init__() | |
self.clusters = clusters | |
self.massifs_infos = skitour2meteofrance | |
self.llm_engine = llm_engine | |
def forward(self, location: str) -> Union[Dict[str, Any], None]: | |
coord_location = geocode_location(location) | |
if not location: | |
return None | |
# Get the closest mountain range to the location to get the avalanche conditions | |
matched_ranges = assign_location_to_clusters(coord_location, self.clusters, k=1) | |
list_ranges = [range[0] for range in matched_ranges if range[1] < 100] | |
if not list_ranges: | |
return None | |
massifs= get_massifs() | |
massif_id = [_massif['id'] for _massif in massifs if _massif['nom'] in list_ranges] | |
avalanche_conditions = get_massif_conditions( | |
self.massifs_infos[str(massif_id[0])]['meteofrance_id'] | |
) | |
weather_client = MeteoFranceClient(access_token=os.getenv("METEO_FRANCE_API_KEY")) | |
forecast = weather_client.get_forecast(*coord_location) | |
daily_forecast = forecast.forecast[:24] | |
for day_forecast in daily_forecast: | |
day_forecast["dt"] = forecast.timestamp_to_locale_time(day_forecast["dt"]).isoformat() | |
forecast_summary = llm_summarizer(str(daily_forecast), self.llm_engine) | |
avalanche_summary = llm_summarizer(str(avalanche_conditions), self.llm_engine) | |
return {"forecast": forecast_summary, "avalanche_conditions": avalanche_summary} |