renaming of the fastf1 tools
Browse files- app.py +9 -8
- fastf1_tools.py +239 -0
- mcp_client.py +64 -15
app.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
import gradio as gr
|
2 |
|
3 |
# Local modules
|
4 |
-
import
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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
|
4 |
-
from smolagents import InferenceClientModel,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
22 |
exit(0)
|
23 |
|
|
|
24 |
# Define model
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
-
agent =
|
31 |
|
32 |
|
33 |
chat_interface = gr.ChatInterface(
|
34 |
-
fn=
|
35 |
type="messages",
|
36 |
-
examples=[
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
39 |
)
|
40 |
|
41 |
chat_interface.launch()
|
42 |
|
43 |
finally:
|
44 |
-
mcp_client.
|
|
|
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()
|