arre99 commited on
Commit
35aeee0
·
1 Parent(s): 72eea61

renaming of the fastf1 tools

Browse files
Files changed (3) hide show
  1. app.py +9 -8
  2. fastf1_tools.py +239 -0
  3. mcp_client.py +64 -15
app.py CHANGED
@@ -1,7 +1,7 @@
1
  import gradio as gr
2
 
3
  # Local modules
4
- import tools
5
  from utils.constants import (
6
  DRIVER_NAMES,
7
  CONSTRUCTOR_NAMES,
@@ -12,7 +12,7 @@ from utils.constants import (
12
  )
13
 
14
  iface_driver_championship_standings = gr.Interface(
15
- fn=tools.driver_championship_standings,
16
  inputs=[
17
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
18
  gr.Dropdown(label="Driver", choices=DRIVER_NAMES)
@@ -23,7 +23,7 @@ iface_driver_championship_standings = gr.Interface(
23
  )
24
 
25
  iface_constructor_championship_standings = gr.Interface(
26
- fn=tools.constructor_championship_standings,
27
  inputs=[
28
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
29
  gr.Dropdown(label="Constructor", choices=CONSTRUCTOR_NAMES)
@@ -34,7 +34,7 @@ iface_constructor_championship_standings = gr.Interface(
34
  )
35
 
36
  iface_event_info = gr.Interface(
37
- fn=tools.get_event_info,
38
  inputs=[
39
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
40
  gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
@@ -46,7 +46,7 @@ iface_event_info = gr.Interface(
46
  )
47
 
48
  iface_season_calendar = gr.Interface(
49
- fn=tools.get_season_calendar,
50
  inputs=[
51
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
52
  ],
@@ -56,7 +56,7 @@ iface_season_calendar = gr.Interface(
56
  )
57
 
58
  iface_track_visualization = gr.Interface(
59
- fn=tools.track_visualization,
60
  inputs=[
61
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
62
  gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
@@ -69,7 +69,7 @@ iface_track_visualization = gr.Interface(
69
  )
70
 
71
  iface_session_results = gr.Interface(
72
- fn=tools.get_session_results,
73
  inputs=[
74
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
75
  gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
@@ -81,7 +81,7 @@ iface_session_results = gr.Interface(
81
  )
82
 
83
  iface_driver_info = gr.Interface(
84
- fn=tools.get_driver_info,
85
  inputs=[
86
  gr.Dropdown(label="Driver", choices=DRIVER_NAMES)
87
  ],
@@ -127,6 +127,7 @@ named_interfaces = {
127
  "Track Visualizations": iface_track_visualization,
128
  "Session Results": iface_session_results,
129
  "Driver Info": iface_driver_info,
 
130
  }
131
 
132
  # Tab names and interfaces
 
1
  import gradio as gr
2
 
3
  # Local modules
4
+ import fastf1_tools
5
  from utils.constants import (
6
  DRIVER_NAMES,
7
  CONSTRUCTOR_NAMES,
 
12
  )
13
 
14
  iface_driver_championship_standings = gr.Interface(
15
+ fn=fastf1_tools.driver_championship_standings,
16
  inputs=[
17
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
18
  gr.Dropdown(label="Driver", choices=DRIVER_NAMES)
 
23
  )
24
 
25
  iface_constructor_championship_standings = gr.Interface(
26
+ fn=fastf1_tools.constructor_championship_standings,
27
  inputs=[
28
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
29
  gr.Dropdown(label="Constructor", choices=CONSTRUCTOR_NAMES)
 
34
  )
35
 
36
  iface_event_info = gr.Interface(
37
+ fn=fastf1_tools.get_event_info,
38
  inputs=[
39
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
40
  gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
 
46
  )
47
 
48
  iface_season_calendar = gr.Interface(
49
+ fn=fastf1_tools.get_season_calendar,
50
  inputs=[
51
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
52
  ],
 
56
  )
57
 
58
  iface_track_visualization = gr.Interface(
59
+ fn=fastf1_tools.track_visualization,
60
  inputs=[
61
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
62
  gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
 
69
  )
70
 
71
  iface_session_results = gr.Interface(
72
+ fn=fastf1_tools.get_session_results,
73
  inputs=[
74
  gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
75
  gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
 
81
  )
82
 
83
  iface_driver_info = gr.Interface(
84
+ fn=fastf1_tools.get_driver_info,
85
  inputs=[
86
  gr.Dropdown(label="Driver", choices=DRIVER_NAMES)
87
  ],
 
127
  "Track Visualizations": iface_track_visualization,
128
  "Session Results": iface_session_results,
129
  "Driver Info": iface_driver_info,
130
+ "Test": iface_test
131
  }
132
 
133
  # Tab names and interfaces
fastf1_tools.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module provides tools for interacting with Formula 1 data using the FastF1 library.
3
+
4
+ Tools to implement
5
+ - driver info
6
+ - compare drivers
7
+ """
8
+
9
+ import json
10
+ import fastf1
11
+ import gradio as gr
12
+ import pandas as pd
13
+ from PIL import Image
14
+ from typing import Union
15
+ from fastf1.core import Session
16
+
17
+ # Local modules
18
+ from utils import parser_utils, track_utils
19
+ from utils.constants import (
20
+ AVAILABLE_SESSION_TYPES,
21
+ DRIVER_DETAILS
22
+ )
23
+
24
+ # Custom types
25
+ gp = Union[str, int]
26
+ session_type = Union[str, int, None]
27
+
28
+ ### FastF1 tools ###
29
+
30
+ def get_session(year: int, round: gp, session_type: session_type) -> Session:
31
+ """Retrieve a specific Formula 1 session.
32
+
33
+ Args:
34
+ year (int): The season year (e.g., 2024)
35
+ round (str | int): The race round number or name (e.g., 1 or 'Monaco')
36
+ session_type (str | int | None): Type of session (e.g., 'FP1', 'Q', 'R')
37
+
38
+ Returns:
39
+ Session: A FastF1 Session object for the specified parameters
40
+
41
+ Note:
42
+ If session_type is a string and not in AVAILABLE_SESSION_TYPES,
43
+ returns an error message string instead.
44
+ """
45
+
46
+ # Check if session type is valid
47
+ if isinstance(session_type, str):
48
+ if session_type.lower() not in AVAILABLE_SESSION_TYPES:
49
+ return f"Session type {session_type} is not available. Supported session types: {AVAILABLE_SESSION_TYPES}"
50
+
51
+ return fastf1.get_session(year, round, session_type)
52
+
53
+ def get_season_calendar(year: int) -> str:
54
+ """Get the complete race calendar for a specific F1 season.
55
+
56
+ Args:
57
+ year (int): The season year to get the calendar for
58
+
59
+ Returns:
60
+ str: Formatted string containing the season calendar
61
+ """
62
+ season_calendar = fastf1.get_event_schedule(year)
63
+ return parser_utils.parse_season_calendar(season_calendar)
64
+
65
+ def get_event_info(year: int, round: gp, format: str) -> str:
66
+ """Retrieve information about a specific Formula 1 event.
67
+
68
+ Args:
69
+ year (int): The season year
70
+ round (str | int): The race round number or name
71
+ format (str): Output format ('human' for readable text, 'LLM' for structured data)
72
+
73
+ Returns:
74
+ str: Formatted event information based on the specified format
75
+ """
76
+
77
+ event = fastf1.get_session(year, round, "race").event # Event object is the same for all sessions, so hardcode "race"
78
+ if format == "human":
79
+ data_interval = f"{event['Session1DateUtc'].date()} - {event['Session5DateUtc'].date()}"
80
+ event_string = f"Round {event['RoundNumber']} : {event['EventName']} - {event['Location']}, {event['Country']} ({data_interval})"
81
+ return event_string
82
+ elif format == "LLM":
83
+ return parser_utils.parse_event_info(event)
84
+
85
+
86
+ def get_constructor_standings(year: int) -> str:
87
+ """Retrieve constructor championship standings for a given year.
88
+
89
+ Args:
90
+ year (int): The season year
91
+
92
+ Returns:
93
+ str: Constructor championship standings
94
+ """
95
+ pass
96
+
97
+ def get_driver_standings(year: int) -> str:
98
+ """Retrieve driver championship standings for a given year.
99
+
100
+ Args:
101
+ year (int): The season year
102
+
103
+ Returns:
104
+ str: Driver championship standings
105
+ """
106
+ pass
107
+
108
+ def driver_championship_standings(year: int, driver_name: str) -> str:
109
+ """Get the championship standing for a specific driver in a given year.
110
+
111
+ Args:
112
+ year (int): The season year
113
+ driver_name (str): Full name of the driver (e.g., 'Lewis Hamilton')
114
+
115
+ Returns:
116
+ str: Formatted string with driver's position, points, and wins
117
+ """
118
+
119
+ with open("assets/driver_abbreviations.json") as f:
120
+ driver_abbreviations = json.load(f)
121
+ driver_abbreviation = driver_abbreviations[driver_name]
122
+ ergast = fastf1.ergast.Ergast()
123
+ driver_standings = ergast.get_driver_standings(year).content[0]
124
+ driver_standing = driver_standings[["position", "points", "wins", "driverCode"]].reset_index(drop=True)
125
+ driver_standing = driver_standing[driver_standing["driverCode"] == driver_abbreviation]
126
+ suffix = "st" if driver_standing['position'].iloc[0] == 1 else "nd" if driver_standing['position'].iloc[0] == 2 else "rd" if driver_standing['position'].iloc[0] == 3 else "th"
127
+ standings_string = f"{driver_name} is {driver_standing['position'].iloc[0]}{suffix} with {driver_standing['points'].iloc[0]} points and {driver_standing['wins'].iloc[0]} wins"
128
+ return standings_string
129
+
130
+ def constructor_championship_standings(year: int, constructor_name: str) -> str:
131
+ """Get the championship standing for a specific constructor in a given year.
132
+
133
+ Args:
134
+ year (int): The season year
135
+ constructor_name (str): Name of the constructor team (e.g., 'Mercedes')
136
+
137
+ Returns:
138
+ str: Formatted string with constructor's position, points, and wins
139
+ """
140
+
141
+ team_mapping = {
142
+ "McLaren": "McLaren",
143
+ "Ferrari": "Ferrari",
144
+ "Red Bull Racing": "Red Bull",
145
+ "Mercedes": "Mercedes",
146
+ "Aston Martin": "Aston Martin",
147
+ "Alpine": "Alpine F1 Team",
148
+ "Haas": "Haas F1 Team",
149
+ "Racing Bulls": "RB F1 Team",
150
+ "Williams": "Williams",
151
+ "Kick Sauber": "Sauber"
152
+ }
153
+
154
+ ergast = fastf1.ergast.Ergast()
155
+ constructor_standings = ergast.get_constructor_standings(year).content[0]
156
+ constructor_standing = constructor_standings[["position", "points", "wins", "constructorName"]].reset_index(drop=True)
157
+ mapped_name = team_mapping[constructor_name]
158
+ constructor_standing = constructor_standing[constructor_standing["constructorName"] == mapped_name]
159
+ suffix = "st" if constructor_standing['position'].iloc[0] == 1 else "nd" if constructor_standing['position'].iloc[0] == 2 else "rd" if constructor_standing['position'].iloc[0] == 3 else "th"
160
+ standings_string = f"{constructor_name} are {constructor_standing['position'].iloc[0]}{suffix} with {constructor_standing['points'].iloc[0]} points and {constructor_standing['wins'].iloc[0]} wins"
161
+ return standings_string
162
+
163
+ def track_visualization(year: int, round: gp, visualization_type: str, driver_name: str) -> Image.Image:
164
+ """Generate a visualization of the track with specified data.
165
+
166
+ Args:
167
+ year (int): The season year
168
+ round (str | int): The race round number or name
169
+ visualization_type (str): Type of visualization ('speed', 'corners', or 'gear')
170
+ driver_name (str): Name of the driver for driver-specific visualizations
171
+
172
+ Returns:
173
+ Image.Image: A PIL Image object containing the visualization
174
+ """
175
+
176
+ session = get_session(year, round, "race")
177
+ session.load()
178
+
179
+ if visualization_type == "speed":
180
+ return track_utils.create_track_speed_visualization(session, driver_name)
181
+ elif visualization_type == "corners":
182
+ return track_utils.create_track_corners_visualization(session)
183
+ elif visualization_type == "gear":
184
+ return track_utils.create_track_gear_visualization(session)
185
+
186
+ def get_session_results(year: int, round: gp, session_type: session_type) -> pd.DataFrame:
187
+ """Retrieve and format the results of a specific session.
188
+
189
+ Args:
190
+ year (int): The season year
191
+ round (str | int): The race round number or name
192
+ session_type (str | int | None): Type of session (e.g., 'Q', 'R', 'Sprint')
193
+
194
+ Returns:
195
+ pd.DataFrame: DataFrame containing the session results
196
+
197
+ Raises:
198
+ gr.Error: If the session type is not supported for the specified round
199
+ """
200
+
201
+ try:
202
+ session = get_session(year, round, session_type)
203
+ session.load(telemetry=False)
204
+ results = session.results
205
+ except ValueError as e:
206
+ raise gr.Error(f"Session type {session_type} is not supported for the specified round. This Grand Prix most likely did not include a sprint race/quali.")
207
+
208
+ df = results[["DriverNumber", "Abbreviation", "FullName", "Position", "GridPosition", "Points", "Status", "Q1", "Q2", "Q3"]]
209
+ df["Name"] = df.apply(lambda row: f"{row['FullName']} ({row['Abbreviation']} • {row['DriverNumber']})", axis=1)
210
+ df = df.drop(columns=["FullName", "Abbreviation", "DriverNumber"])
211
+ df = df.rename(columns={"Position": "Pos", "GridPosition": "Grid Pos"})
212
+
213
+ # Process results based on session type
214
+ if session_type in ["race", "sprint"]:
215
+ df = df[["Pos", "Name", "Points", "Grid Pos", "Status"]]
216
+ elif "qualifying" in session_type:
217
+ df[["Q1", "Q2", "Q3"]] = df[["Q1", "Q2", "Q3"]].apply(lambda x: x.dt.total_seconds().apply(lambda y: f"{int(y//60):02d}:{int(y%60):02d}.{int(y%1*1000):03d}" if pd.notna(y) else "-"))
218
+ df = df[["Pos", "Name", "Q1", "Q2", "Q3"]]
219
+ return df
220
+
221
+ def get_driver_info(driver_name: str) -> str:
222
+ """Retrieve detailed information about a specific driver.
223
+
224
+ Args:
225
+ driver_name (str): Full name of the driver (e.g., 'Max Verstappen')
226
+
227
+ Returns:
228
+ str: Formatted string with driver's details including name, team, number,
229
+ nationality, and a brief summary
230
+ """
231
+ driver = DRIVER_DETAILS[driver_name]
232
+ driver_info_string = f"{driver_name} ({driver['birth_date']})\n{driver['team']} #{driver['number']}\n{driver['nationality']}\n\n{driver['summary']}"
233
+ return driver_info_string
234
+
235
+
236
+
237
+ if __name__ == "__main__":
238
+ session = get_session(2024, 1, "fp1")
239
+ session.load()
mcp_client.py CHANGED
@@ -1,12 +1,49 @@
1
  import os
2
  import gradio as gr
3
- from mcp import StdioServerParameters
4
- from smolagents import InferenceClientModel, CodeAgent, ToolCollection, MCPClient
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
 
7
  if __name__ == "__main__":
8
 
9
- only_list_tools = True
 
10
 
11
  try:
12
 
@@ -15,30 +52,42 @@ if __name__ == "__main__":
15
  tools = mcp_client.get_tools()
16
 
17
  print("### MCP tools ### ")
18
- print("\n".join(f"{t.name}: {t.description}" for t in tools))
19
 
20
  if only_list_tools:
21
- mcp_client.close()
22
  exit(0)
23
 
 
24
  # Define model
25
- model = InferenceClientModel(
26
- model_id="Qwen/Qwen2.5-32B-Instruct",
27
- token=os.getenv("HF_TOKEN")
28
- )
 
 
 
 
 
 
 
 
29
 
30
- agent = CodeAgent(tools=[*tools], model=model)
31
 
32
 
33
  chat_interface = gr.ChatInterface(
34
- fn=lambda message, history: str(agent.run(message)),
35
  type="messages",
36
- examples=["Prime factorization of 68"],
37
- title="Agent with MCP Tools",
38
- description="This is a simple agent that uses MCP tools to answer questions."
 
 
 
39
  )
40
 
41
  chat_interface.launch()
42
 
43
  finally:
44
- mcp_client.close()
 
1
  import os
2
  import gradio as gr
3
+ from typing import List, Dict
4
+ from smolagents import InferenceClientModel, LiteLLMModel, ToolCallingAgent, MCPClient
5
+
6
+ SYSTEM_PROMPT = """You are a helpful Formula 1 assistant and strategist. You have access to various F1 data and tools to help answer questions about races, drivers, teams, and more.
7
+ Be concise and accurate in your responses. If you don't know something, use the available tools to find the information.
8
+ In addition, you will be asked to act as a live race engineer strategist during a Formula 1 race, making crucial calls during the event."""
9
+
10
+ def format_messages(history: List[List[str]], message: str) -> List[Dict[str, str]]:
11
+ """Format the conversation history and new message for the agent."""
12
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
13
+
14
+ # Add conversation history
15
+ for user_msg, bot_msg in history:
16
+ messages.append({"role": "user", "content": user_msg})
17
+ messages.append({"role": "assistant", "content": bot_msg})
18
+
19
+ # Add the new message
20
+ messages.append({"role": "user", "content": message})
21
+ return messages
22
+
23
+ def agent_chat(message: str, history: List[List[str]]) -> str:
24
+ """Handle chat messages with conversation history."""
25
+
26
+ # Format messages with system prompt and history
27
+ formatted_messages = format_messages(history, message)
28
+
29
+ # Convert messages to a single string with role indicators
30
+ chat_history = "\n".join(
31
+ f"{msg['role'].capitalize()}: {msg['content']}"
32
+ for msg in formatted_messages[1:] # Skip system prompt in the chat history
33
+ )
34
+
35
+ # Include system prompt at the beginning
36
+ full_prompt = f"{SYSTEM_PROMPT}\n\n{chat_history}"
37
+
38
+ # Get agent response
39
+ response = str(agent.run(full_prompt))
40
+ return response
41
 
42
 
43
  if __name__ == "__main__":
44
 
45
+ only_list_tools = False # Set to True to only list tools (used for debugging)
46
+ local_model = True # If you have Ollama installed, set this to True
47
 
48
  try:
49
 
 
52
  tools = mcp_client.get_tools()
53
 
54
  print("### MCP tools ### ")
55
+ print("\n".join(f"{i}: {t.name}: {t.description}" for i,t in enumerate(tools)))
56
 
57
  if only_list_tools:
58
+ mcp_client.disconnect()
59
  exit(0)
60
 
61
+
62
  # Define model
63
+ if local_model:
64
+ model = LiteLLMModel(
65
+ model_id="ollama_chat/qwen3:8b",
66
+ api_base="http://127.0.0.1:11434", # Default ollama server
67
+ num_ctx=32768,
68
+ )
69
+ else:
70
+ model = InferenceClientModel(
71
+ model_id="Qwen/Qwen2.5-32B-Instruct",
72
+ provider="",
73
+ token=os.getenv("HF_TOKEN")
74
+ )
75
 
76
+ agent = ToolCallingAgent(tools=[*tools], model=model)
77
 
78
 
79
  chat_interface = gr.ChatInterface(
80
+ fn=agent_chat,
81
  type="messages",
82
+ examples=[
83
+ "What are the driver standings for the 2024 Formula 1 season?",
84
+ "What is the calendar for the 2024 Formula 1 season?"
85
+ ],
86
+ title="🏎️ Formula 1 Assistant",
87
+ description="This is a simple agent that uses MCP tools to answer questions about Formula 1."
88
  )
89
 
90
  chat_interface.launch()
91
 
92
  finally:
93
+ mcp_client.disconnect()