Gemini’s Power for Tourism Apps: A Function Calling Tutorial

Nathaly Alarcon Torrico
Google Cloud - Community
6 min readAug 30, 2024

In this tutorial, we will create a Tourism web app where users can query the weather for future days in a specific location.

We will use two different methods from https://open-meteo.com

Open-Meteo Weather API

We will use the following methods:

First we will define our own functions to call the APIs:

  1. get_location_data : Retrieves information of a particular place. We mainly want to retrieve the latitude and longitude.
def get_location_data(location_name):
"""Retrieves location data from the Open-Meteo Geocoding API.

This function takes a location name as input and returns
a JSON object containing location data from the Open-Meteo API.

Args:
location_name: The name of the location to search for. eg. "La Paz, Bolivia"

Returns:
A JSON object containing location data.
"""

location_url = f"https://geocoding-api.open-meteo.com/v1/search?name={location_name}&count=10&language=en&format=json"
response = requests.get(location_url)
response_json = json.loads(response.text)
return response_json

Sample call:

get_location_data("La Paz, Bolivia")

Result:

{'results': [{'id': 3911925,
'name': 'La Paz',
'latitude': -16.5,
'longitude': -68.15,
'elevation': 3782.0,
'feature_code': 'PPLG',
'country_code': 'BO',
'admin1_id': 3911924,
'timezone': 'America/La_Paz',
'population': 812799,
'country_id': 3923057,
'country': 'Bolivia',
'admin1': 'Departamento de La Paz'}],
'generationtime_ms': 5.160928}

2. get_weather_forecast_data: To generate the weather forecast, to identify the location this function requires the latitude and longitude provided from get_location_data().

def get_weather_forecast(latitude, longitude, timezone = "America/La_Paz", forecast_days = 3):
"""Retrieves weather forecast data from the Open-Meteo Weather API.

This function takes latitude, longitude, timezone, and forecast days as input,
and returns a json object containing the weather forecast data.

Args:
latitude: The latitude of the location.
longitude: The longitude of the location.
timezone: The timezone of the location (default: "America/La_Paz").
forecast_days: The number of forecast days (default: 3).

Returns:
A Json object containing the weather forecast data.
"""


weather_url = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&hourly=temperature_2m&timezone={timezone}&forecast_days={forecast_days}"
response = requests.get(weather_url)
response_json = json.loads(response.text)

# Basic Transformation of results
df = pd.DataFrame()
df["DateTime"] = response_json["hourly"]["time"]
df["Temperatura"] = response_json["hourly"]["temperature_2m"]

# px.line(df, x="DateTime", y="Temperatura")
return df.to_json(orient="records")

Sample call:

get_weather_forecast(latitude = "-16.5", longitude="-68.15")

Sample Response

[{"DateTime":"2024-08-28T00:00","Temperatura":5.1},
{"DateTime":"2024-08-28T01:00","Temperatura":5.9},
{"DateTime":"2024-08-28T02:00","Temperatura":5.3},
{"DateTime":"2024-08-28T03:00","Temperatura":4.9},
{"DateTime":"2024-08-28T04:00","Temperatura":5.3},
{"DateTime":"2024-08-28T05:00","Temperatura":4.7},
{"DateTime":"2024-08-28T06:00","Temperatura":4.4},
...]

Now we need to indicate to Gemini the goal, purpose and parameters of the functions defined.

# Function Declaration for get_location_data

get_location_data_func = FunctionDeclaration(
name="get_location_data",
description="Get latitude, longitude and population of a particular location. ",
parameters={
"type": "object",
"properties": {
"location_name": {
"type": "string",
"description": "Name of a country or city. If available provide: City, Country"
},
},
"required": [
"location_name"
]
},
)


# Function Declaration for get_weather_forecast

get_weather_forecast_func = FunctionDeclaration(
name="get_weather_forecast",
description="Provide the hourly forecasting of weather for a particular place based on latitud and longitud",
parameters={
"type": "object",
"properties": {
"latitude": {
"type": "string",
"description": "Latitude of the location desired, this can come from get_location_data function."
},
"longitude": {
"type": "string",
"description": "Longitude of the location desired, this can come from get_location_data function."
},
"timezone": {
"type": "string",
"description": "Timezone for the hourly forecast in format similar to: America/La_Paz, If not specified, this can come from get_location_data function."
},
"forecast_days": {
"type": "string",
"description": "Amount of days to forecast weather"
},
},
"required": [
"latitude", "longitude"
]
},
)

Let’s create the list of tools that Gemini will use for our tourism app.

tourism_tool = Tool(function_declarations=[
get_location_data_func, get_weather_forecast_func
]
)

Now let’s start Gemini. Please note that we are sending the “tools” parameter with the functions previously defined.

model = GenerativeModel("gemini-1.5-flash-001", 
generation_config=GenerationConfig(temperature=0),
tools=[tourism_tool])

chat = model.start_chat()

Function Calling Is a Gemini Functionality, where we can deliver a set of functions and according to the prompt received, Gemini will decide if it is necessary to call the provided function(s) to generate the response. The final response will be also in a friendly natural language. You can find more information of Function calling here.

In our particular case, Gemini should call our defined functions only when a weather request is included in the prompt, otherwise Gemini should generate a simple response as usual.

Function Calling logic for our Tourism web-app.

To handle this logic, let’s create a generic function to call Gemini with each prompt.

def call_gemini_api(prompt):
"""Calls the Gemini API with the given prompt.

Args:
prompt: The prompt to send to the Gemini API.

Returns:
The text response from the Gemini API.
"""

# Send the prompt to the Gemini API
response = chat.send_message(prompt)


# Check if the response contains a function call
if response.candidates[0].content.parts[0].function_call:
# Here we define the logic to call the weather and location functions when needed.
function_calling_result = call_a_function(response)
# Return the final text from the function call
return function_calling_result.text

else:
# Return the text from the model response for a regular call
# (Without function calling.)
return response.text

In our case, if the weather forecasting is requested, we need to call get_location_data and then get_weather_forecast in a recursive way. To do so, let’s create a function that can analyze the responses and call functions when needed.

def call_a_function(response):
"""
This function parses a response object containing a function call,
constructs the function call string, executes it using eval,
and returns the API response or makes another function call if necessary.

Args:
response: A response object containing the function call information.

Returns:
The API response or the response from another function call (recursive).
"""


# Extract the function name from the response object
func_name = response.candidates[0].content.parts[0].function_call.name
calling_parameters_function = ""


# Loop through function call arguments and construct the argument string
for param_name in response.candidates[0].content.parts[0].function_call.args:
param_value = response.candidates[0].content.parts[0].function_call.args[param_name]
calling_parameters_function += f"{param_name} = '{param_value}',"

# Remove the trailing comma from the argument string
# and build final function call statement
calling_function_string = f"{func_name}({calling_parameters_function[:-1]})"
print(calling_function_string)

# Execute the call to the api within the defined function
response_api = eval(calling_function_string)
print(response_api)

# Return the API response back to Gemini, so it can generate a model response or request another function call
response = chat.send_message(
Part.from_function_response(
name= func_name,
response={
"content": response_api,
},
),
)

# print(response)
# Check if the response contains another function call
if response.candidates[0].content.parts[0].function_call:
# Make another recursive function call if there's another function call
response_function = call_a_function(response)
return response_function
else:
# If no more function calls, return the response
# print("No more API calls")
return response

Let’s test the model with our defined functions.

If we ask the model: “What are the main places to visit in Bolivia”. Gemini will identify that a location is mentioned, so it will try to call get_location_data function. As no relevant info is found, Gemini generates it’s own result.

As we identify Uyuni as a place of interest, let’s ask for the weather. In this case, Gemini needs to use both functions: get_location_data and get_weather_forecast to provide an answer. The output provided for Gemini is a nice text with the most important information from the weather forecasted data.

Now let’s make a generic question. Gemini won’t call any function and will generate the response.

Gemini is a really smart model that can identify when a function needs to be called to provide the response.

You can find the complete notebook here: https://github.com/nathalyAlarconT/GenAI_Workshops/blob/main/FunctionCalling.ipynb

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Google AI/ML Developer Programs team supported this work by providing Google Cloud Credits

--

--

Nathaly Alarcon Torrico
Google Cloud - Community

I code in my sleep - ♡ I love Coffee ♡ - Data Scientist — Google Developer Expert in Machine Learning - Google Cloud Champion Innovator