Build with AI

How do you build a weather assistant with function calling?

· By Robert Scales · 3 min read

Short answer

You describe a weather function to the model in JSON Schema, let gpt-3.5-turbo-0613 reply with the arguments it wants to use, run the real weather lookup in your own code, and send the result back so the model can write the answer. The model never fetches the weather itself; it only decides when to ask and with what values.

Since June 13, OpenAI's Chat Completions API accepts a list of functions: short descriptions of tools your program can run. When a question needs one of those tools, the model stops and replies with a structured request instead of prose: the function's name plus its arguments as JSON, a plain-text data format made of named fields. Your code runs the tool and hands the result back. A weather assistant is the classic first project, because the model cannot know today's weather on its own, and it is a good way to see every step of the loop.

This walkthrough uses Python, the 0.27 series of the openai library, the new gpt-3.5-turbo-0613 model, and Open-Meteo, a weather service that is free for non-commercial use under 10,000 calls a day and needs no API key.

Step 1: describe the function

The description is written in JSON Schema, a standard way to say which fields an object has and what type each one is.

functions = [{
    "name": "get_current_weather",
    "description": "Get the current weather for a place. Use it whenever the user asks about weather right now.",
    "parameters": {
        "type": "object",
        "properties": {
            "latitude": {"type": "number", "description": "Latitude of the place, in decimal degrees"},
            "longitude": {"type": "number", "description": "Longitude of the place, in decimal degrees"},
            "place_name": {"type": "string", "description": "City and country, e.g. Montreal, Canada"},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
        },
        "required": ["latitude", "longitude", "place_name", "unit"]
    }
}]

Open-Meteo works from coordinates, so the function asks for latitude and longitude. For well-known cities, the model can usually supply them; a production app would add a proper place-lookup step.

Step 2: let the model decide

import json, openai, requests

messages = [
    {"role": "system", "content": "You are a weather helper. If the place is unclear, ask which city the user means."},
    {"role": "user", "content": "Do I need a jacket in Montreal right now?"}
]
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-0613",
    messages=messages,
    functions=functions,
    function_call="auto",
)
message = response["choices"][0]["message"]

With function_call set to "auto", the model chooses whether a tool is needed. Here it should be. The reply's finish_reason becomes "function_call" and the message looks something like this (illustrative output):

{"role": "assistant", "content": null,
 "function_call": {"name": "get_current_weather",
  "arguments": "{\"latitude\": 45.5, \"longitude\": -73.57, \"place_name\": \"Montreal, Canada\", \"unit\": \"celsius\"}"}}

Notice that arguments is a string. The model wrote it, so treat it like user input: parse it and check it before use.

Step 3: run the real lookup

def get_current_weather(latitude, longitude, place_name, unit):
    r = requests.get("https://api.open-meteo.com/v1/forecast", params={
        "latitude": latitude, "longitude": longitude,
        "current_weather": "true", "temperature_unit": unit,
    }, timeout=10)
    r.raise_for_status()
    now = r.json()["current_weather"]
    return {"place": place_name, "temperature": now["temperature"], "unit": unit,
            "windspeed_kmh": now["windspeed"], "weathercode": now["weathercode"]}

if message.get("function_call"):
    args = json.loads(message["function_call"]["arguments"])
    result = get_current_weather(**args)

Open-Meteo's current_weather block holds the time, temperature, wind speed, wind direction and a weather code. Wind speed comes in km/h unless you ask otherwise. The codes follow a published table: 0 means clear sky, 1 to 3 run from mainly clear to overcast, 61 to 65 are rain from slight to heavy, and 95 is a thunderstorm.

Step 4: send the result back for the answer

messages.append(message)
messages.append({"role": "function", "name": "get_current_weather",
                 "content": json.dumps(result)})
final = openai.ChatCompletion.create(model="gpt-3.5-turbo-0613", messages=messages)
print(final["choices"][0]["message"]["content"])

The new function role tells the model this content is a tool's output, not something the user typed. A typical reply might read (illustrative): "It's 14 °C in Montreal with a light wind and overcast skies, so a light jacket is a good idea."

Try it: Run the four steps, then ask three different questions: one about a famous city, one about a small town with the same name as a bigger one, and one that isn't about weather at all. Note when the model calls the function, when it asks you to clarify, and when it simply answers.

How do you describe a function so the model calls it correctly?

Write the description for a reader who has never seen your code, because that is exactly what the model is. Say what the function does and when to use it. Give each parameter a description with an example, as OpenAI's cookbook does with "The city and state, e.g. San Francisco, CA". Use enum when only a few values are allowed, like the two temperature units, so the model cannot invent a third.

Mark as required only the fields you truly need, and keep names plain: latitude beats lat_dd. If the model keeps guessing, add a system message like the cookbook's, telling it not to assume values and to ask when a request is ambiguous. You can also force a call with function_call={"name": "get_current_weather"}, or block calls with "none".

Finally, guard the edges. OpenAI notes that its models were trained to produce JSON matching the signature, but your code should still validate types and ranges, and handle a 400 error from Open-Meteo, which returns a short JSON reason when a parameter is wrong. For anything with real-world effects, such as sending an email or making a purchase, OpenAI advises asking the user to confirm before your code acts.

Sources

  1. How to call functions with chat models — OpenAI Cookbook (GitHub), June 16, 2023
  2. Weather Forecast API (Docs) — Open-Meteo, June 14, 2023
  3. Function calling and other API updates — OpenAI, June 13, 2023

Get the free Guide to Artificial Intelligence

Sign up for the weekly newsletter and receive the free guide: what changed in AI this week, one lesson, one prompt to try. Unsubscribe in one click.

How we handle your address

Back to top

Display

Your choices are saved in this browser only. No data is sent.

Text size
100%
Colours
Reading
More settings
Accessibility statement