Spaces:
Running
Running
Commit
·
0c78cd5
1
Parent(s):
aaf477c
Added main file of the gradio app
Browse files
app.py
ADDED
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import pandas as pd
|
4 |
+
import numpy as np
|
5 |
+
import gradio as gr
|
6 |
+
from gradio_folium import Folium
|
7 |
+
from smolagents import CodeAgent, LiteLLMModel, HfApiModel
|
8 |
+
from src.gradio_utils import ( create_map_from_markers,
|
9 |
+
update_map_on_selection,
|
10 |
+
stream_to_gradio,
|
11 |
+
interact_with_agent,
|
12 |
+
toggle_visibility,
|
13 |
+
FINAL_MESSAGE_HEADER,
|
14 |
+
MAP_URL)
|
15 |
+
|
16 |
+
from src.prompts import SKI_TOURING_ASSISTANT_PROMPT
|
17 |
+
from src.tools import (RefugeTool,
|
18 |
+
MountainRangesTool,
|
19 |
+
ForecastTool,
|
20 |
+
GetRoutesTool,
|
21 |
+
DescribeRouteTool)
|
22 |
+
from folium import Map, TileLayer, Marker, Icon
|
23 |
+
from dotenv import load_dotenv
|
24 |
+
|
25 |
+
# Load environment variables
|
26 |
+
load_dotenv()
|
27 |
+
|
28 |
+
|
29 |
+
required_variables = [
|
30 |
+
"HF_TOKEN",
|
31 |
+
"GOOGLE_MAPS_API_KEY",
|
32 |
+
"SKITOUR_API_TOKEN",
|
33 |
+
"METEO_FRANCE_API_TOKEN",
|
34 |
+
"HUGGINGFACE_ENDPOINT_ID_QWEN"
|
35 |
+
]
|
36 |
+
|
37 |
+
# Find missing variables
|
38 |
+
missing_variables = [var for var in required_variables if var not in os.environ]
|
39 |
+
|
40 |
+
if missing_variables:
|
41 |
+
raise EnvironmentError(f"Missing required environment variables: {', '.join(missing_variables)}")
|
42 |
+
|
43 |
+
print("All required variables are set.")
|
44 |
+
|
45 |
+
# Load the summit clusters
|
46 |
+
# Useful for assigning locations to mountain ranges
|
47 |
+
with open("data/summit_clusters.json", "r") as f:
|
48 |
+
summit_clusters = json.load(f)
|
49 |
+
|
50 |
+
with open("data/skitour2mf_lookup.json", "r") as f:
|
51 |
+
skitour2mf_lookup = json.load(f)
|
52 |
+
|
53 |
+
def get_tools(llm_engine):
|
54 |
+
refuge_tool = RefugeTool()
|
55 |
+
mountain_ranges_tool = MountainRangesTool(summit_clusters)
|
56 |
+
forecast_tool = ForecastTool(
|
57 |
+
llm_engine=llm_engine,
|
58 |
+
clusters=summit_clusters,
|
59 |
+
skitour2meteofrance=skitour2mf_lookup
|
60 |
+
)
|
61 |
+
get_routes_tool = GetRoutesTool()
|
62 |
+
description_route_tool = DescribeRouteTool(
|
63 |
+
skitour2meteofrance=skitour2mf_lookup,
|
64 |
+
llm_engine=llm_engine
|
65 |
+
)
|
66 |
+
return [mountain_ranges_tool, forecast_tool, get_routes_tool, description_route_tool]
|
67 |
+
|
68 |
+
# Initialize the default agent
|
69 |
+
def init_default_agent(llm_engine):
|
70 |
+
return CodeAgent(
|
71 |
+
tools = get_tools(llm_engine),
|
72 |
+
model = llm_engine,
|
73 |
+
additional_authorized_imports=["pandas"],
|
74 |
+
max_steps=20,
|
75 |
+
)
|
76 |
+
|
77 |
+
# Initialize the default agent prompt
|
78 |
+
def init_default_agent_prompt():
|
79 |
+
return {"specific_agent_role_prompt": SKI_TOURING_ASSISTANT_PROMPT.format(language="French")}
|
80 |
+
|
81 |
+
def create_llm_engine(type_engine: str, api_key: str = None):
|
82 |
+
if type_engine == "openai/gpt-4o" and api_key:
|
83 |
+
llm_engine = LiteLLMModel(model_id="openai/gpt-4o", api_key=api_key)
|
84 |
+
return llm_engine
|
85 |
+
elif type_engine == "openai/gpt-4o" and not api_key:
|
86 |
+
raise ValueError("You need to provide an API key to use the the model engine.")
|
87 |
+
elif type_engine == "Qwen/Qwen2.5-Coder-32B-Instruct":
|
88 |
+
llm_engine = HfApiModel(model_id=os.environ["HUGGINGFACE_ENDPOINT_ID_QWEN"])
|
89 |
+
return llm_engine
|
90 |
+
elif type_engine == "meta-llama/Llama-3.3-70B-Instruct":
|
91 |
+
llm_engine = HfApiModel(model_id=os.environ["HUGGINGFACE_ENDPOINT_ID_LLAMA"])
|
92 |
+
return llm_engine
|
93 |
+
else:
|
94 |
+
raise ValueError("Invalid engine type. Please choose either 'openai/gpt-4o' or 'Qwen/Qwen2.5-Coder-32B-Instruct'.")
|
95 |
+
|
96 |
+
def initialize_new_agent(engine_type, api_key):
|
97 |
+
try:
|
98 |
+
llm_engine = create_llm_engine(engine_type, api_key)
|
99 |
+
tools = get_tools(llm_engine)
|
100 |
+
skier_agent = CodeAgent(
|
101 |
+
tools = tools,
|
102 |
+
model = llm_engine,
|
103 |
+
additional_authorized_imports=["pandas"],
|
104 |
+
max_steps=20,
|
105 |
+
)
|
106 |
+
return skier_agent, [], gr.Chatbot([], label="Agent Thoughts", type="messages")
|
107 |
+
except ValueError as e:
|
108 |
+
return str(e)
|
109 |
+
|
110 |
+
# Sample data for demonstration
|
111 |
+
sample_data = {
|
112 |
+
"id": [0],
|
113 |
+
"Name": "Mont Blanc, Par les Grands Mulets",
|
114 |
+
"Latitude": [45.90181],
|
115 |
+
"Longitude": [6.86153],
|
116 |
+
"Route Link": ["https://skitour.fr/topos/770"],
|
117 |
+
}
|
118 |
+
df_sample_routes = pd.DataFrame(sample_data)
|
119 |
+
|
120 |
+
# Default engine
|
121 |
+
if os.environ.get("OPENAI_API_KEY"):
|
122 |
+
default_engine = create_llm_engine("openai/gpt-4o", os.environ.get("OPENAI_API_KEY"))
|
123 |
+
else:
|
124 |
+
default_engine = create_llm_engine("Qwen/Qwen2.5-Coder-32B-Instruct")
|
125 |
+
|
126 |
+
|
127 |
+
# Gradio UI
|
128 |
+
def build_ui():
|
129 |
+
with gr.Blocks() as demo:
|
130 |
+
gr.Markdown("<center><h1>Ski Touring Agent Planner</h1></center>")
|
131 |
+
|
132 |
+
gr.Image(value="./data/skitourai.jpeg", height=400, width=400)
|
133 |
+
with gr.Accordion("About the App❓", open=False):
|
134 |
+
gr.Markdown("""
|
135 |
+
**🇬🇧 English Version**
|
136 |
+
|
137 |
+
The Ski Touring Assistant is built with the **[Smolagents](https://github.com/huggingface/smolagents) library by Hugging Face** and relies on data from [Skitour.fr](https://skitour.fr) and [Météo France - Montagne](https://meteofrance.com/meteo-montagne).
|
138 |
+
It is designed specifically to help plan ski touring routes **in the Alps and the Pyrenees, in France only**. While the app provides AI-generated suggestions, it is essential to **always verify snow and avalanche conditions directly on Météo-France for your safety.**
|
139 |
+
|
140 |
+
#### Key Features
|
141 |
+
|
142 |
+
- **Interactive Maps**: Plan routes with data from [Skitour.fr](https://skitour.fr), covering ski touring trails in the Alps and the Pyrenees.
|
143 |
+
- **AI Assistance**: Get route recommendations, hazard insights, and metrics like elevation and travel time.
|
144 |
+
- **Snow & Avalanche Conditions**: Access real-time information via [Météo-France](https://meteofrance.com/meteo-montagne).
|
145 |
+
- **Multilingual Support**: Available in English and French.
|
146 |
+
|
147 |
+
Enjoy your ski touring adventures in France, but always double-check official sources for safety!
|
148 |
+
|
149 |
+
---
|
150 |
+
**🇫🇷 Version Française**
|
151 |
+
|
152 |
+
L'assistant de ski de randonnée est construit avec la bibliothèque **[Smolagents](https://github.com/huggingface/smolagents) de Hugging Face** et repose sur les données de [Skitour.fr](https://skitour.fr) et [Météo France - Montagne](https://meteofrance.com/meteo-montagne).
|
153 |
+
Il est conçu spécifiquement pour aider à planifier des itinéraires de ski de randonnée **dans les Alpes et les Pyrénées, uniquement en France**. Bien que l'application fournisse des suggestions générées par IA, il est essentiel de **toujours vérifier la météo et le bulletin d'estimation des risques d'avalanche (BERA) directement sur Météo-France pour votre sécurité**.
|
154 |
+
|
155 |
+
#### Principales Fonctionnalités
|
156 |
+
|
157 |
+
- **Cartes interactives** : Planifiez des itinéraires avec des données de [Skitour.fr](https://skitour.fr), couvrant les sentiers de ski de randonnée dans les Alpes et les Pyrénées.
|
158 |
+
- **Assistance IA** : Obtenez des recommandations d'itinéraires, des informations sur les risques et des métriques comme l'altitude et le temps de trajet.
|
159 |
+
- **Conditions de neige et d'avalanche** : Accédez à des informations en temps réel via [Météo-France](https://meteofrance.com/meteo-montagne).
|
160 |
+
- **Support multilingue** : Disponible en anglais et en français.
|
161 |
+
|
162 |
+
Profitez de vos aventures en ski de randonnée en France, mais vérifiez toujours les sources officielles pour votre sécurité !
|
163 |
+
""", container=True)
|
164 |
+
|
165 |
+
|
166 |
+
skier_agent = gr.State(lambda: init_default_agent(default_engine))
|
167 |
+
with gr.Row():
|
168 |
+
with gr.Column():
|
169 |
+
language = gr.Radio(["English", "French"], value="French", label="Language")
|
170 |
+
skier_agent_prompt = gr.State(init_default_agent_prompt)
|
171 |
+
language_button = gr.Button("Update language")
|
172 |
+
model_type = gr.Dropdown(choices = ["Qwen/Qwen2.5-Coder-32B-Instruct", "meta-llama/Llama-3.3-70B-Instruct", "openai/gpt-4o", ],
|
173 |
+
value="Qwen/Qwen2.5-Coder-32B-Instruct",
|
174 |
+
label="Model Type",
|
175 |
+
info="If you choose openai/gpt-4o, you need to provide an API key.",
|
176 |
+
interactive=True
|
177 |
+
)
|
178 |
+
api_key_textbox = gr.Textbox(label="API Key", placeholder="Enter your API key", type="password", visible=False)
|
179 |
+
|
180 |
+
|
181 |
+
model_type.change(
|
182 |
+
lambda x: toggle_visibility(True) if x =='openai/gpt-4o' else toggle_visibility(False),
|
183 |
+
[model_type],
|
184 |
+
[api_key_textbox]
|
185 |
+
)
|
186 |
+
update_engine = gr.Button("Update LLM Engine")
|
187 |
+
|
188 |
+
|
189 |
+
stored_message = gr.State([])
|
190 |
+
chatbot = gr.Chatbot(label="Agent Thoughts", type="messages")
|
191 |
+
warning = gr.Warning("The agent can take few seconds to minutes to respond.", visible=True)
|
192 |
+
text_output = gr.Markdown(value=FINAL_MESSAGE_HEADER, container=True)
|
193 |
+
warning = gr.Markdown("⚠️ The agent can take few seconds to minutes to respond.", container=True)
|
194 |
+
text_input = gr.Textbox(lines=1, label="Chat Message", submit_btn=True)
|
195 |
+
gr.Examples(["Can you provide an itinerary near Grenoble?"], text_input)
|
196 |
+
|
197 |
+
with gr.Column():
|
198 |
+
f_map = Folium(value=Map(
|
199 |
+
location=[45.9237, 6.8694],
|
200 |
+
zoom_start=10,
|
201 |
+
tiles= TileLayer(
|
202 |
+
tiles=MAP_URL,
|
203 |
+
attr="Google",
|
204 |
+
name="Google Maps",
|
205 |
+
overlay=True,
|
206 |
+
control=True )
|
207 |
+
)
|
208 |
+
)
|
209 |
+
|
210 |
+
df_routes = gr.State(pd.DataFrame(df_sample_routes))
|
211 |
+
data = gr.DataFrame(value=df_routes.value[["Name", "Route Link"]], datatype="markdown", interactive=False)
|
212 |
+
|
213 |
+
language_button.click(lambda s: {"specific_agent_role_prompt": SKI_TOURING_ASSISTANT_PROMPT.format(language=s)}, [language], [skier_agent_prompt])
|
214 |
+
update_engine.click(
|
215 |
+
fn=initialize_new_agent,
|
216 |
+
inputs=[model_type, api_key_textbox],
|
217 |
+
outputs=[skier_agent, stored_message, chatbot]
|
218 |
+
)
|
219 |
+
|
220 |
+
text_input.submit(lambda s: (s, ""), [text_input], [stored_message, text_input]) \
|
221 |
+
.then(interact_with_agent, [skier_agent, stored_message, chatbot, df_routes, skier_agent_prompt], [chatbot, df_routes, text_output])
|
222 |
+
|
223 |
+
df_routes.change(create_map_from_markers, [df_routes], [f_map]).then(lambda s: gr.DataFrame(s[["Name", "Route Link"]], datatype="markdown", interactive=False), [df_routes], [data])
|
224 |
+
data.select(
|
225 |
+
update_map_on_selection, [data, df_routes],[f_map]
|
226 |
+
)
|
227 |
+
|
228 |
+
demo.launch()
|
229 |
+
|
230 |
+
# Launch the app
|
231 |
+
if __name__ == "__main__":
|
232 |
+
build_ui()
|