import requests
from typing import Dict
def get_coordinates(location: str) -> Dict[str, float]:
base_url: str = "https://nominatim.openstreetmap.org/search"
params = {
"q": location,
"format": "json",
"limit": 1, # only return the top result
"addressdetails": 1, # include detailed address info
}
headers = {"User-Agent": "example_weather/1.0 (daniel.chen@posit.co)"}
response = requests.get(
base_url,
params=params,
headers=headers,
)
data = response.json()
lat = float(data[0]["lat"])
lon = float(data[0]["lon"])
return {"lat": lat, "lon": lon}Tools
title: Tool Calling
What is Tool Calling?
Allows LLMs to interact with other systems
Supported by most of the newest LLMs, but not all
Sounds complicated? Scary? It’s not too bad, actually…
Reference: https://jcheng5.github.io/llm-quickstart/quickstart.html#/how-it-works
How it does NOT work

How it DOES work

A tool is a function
- A tool call is a function that the LLM can use
- It can either infer what the function does by the function name, docstring, and/or parameter names
- You can also provide it the context you want
Register tools (Agents)
Python Chatlas
import chatlas as clt
# doc strings and type hints provide tool context
def capital_finder(location: str) -> str:
"""Sets the capital of the moon as NYC"""
if location.lower() == "moon":
return "NYC"
chat = clt.ChatAnthropic()
chat.register_tool(capital_finder)
chat.chat("what is the capital of the moon?")I'll use the capital_finder function to get information about the capital of the moon.
# 🔧 tool request (toolu_01NFACspp5dLPMeTN7FNfvyB)
capital_finder(location=moon)
# ✅ tool result (toolu_01NFACspp5dLPMeTN7FNfvyB)
NYC
According to the capital_finder function, the capital of the moon is NYC (New York City).
Of course, this is not factually accurate in reality - the moon doesn't actually have a capital city or any cities at all, as it's an
uninhabited celestial body. This appears to be a fictional or hypothetical response from the function.
R Ellmer
library(ellmer)
chat <- chat_openai()
#' Sets the capital of the moon as NYC
capital_finder <- function(location) {
if (location == "moon") {
return("NYC")
}
}
capital_finder <- ellmer::tool(
capital_finder,
description = "Sets the capital of moon as NYC",
arguments = list(
location = type_string("location to look up")
)
)
chat$register_tool(capital_finder)
chat$chat("what is the capital of the moon?")◯ [tool call] capital_finder(location = "moon")
● #> NYC
The capital of the Moon is NYC.
Example: Weather Tool
To ask the LLM about the weather in the current location we need to write a function that does a few things:
- Geocode a location to a latitude and longitude (this can also be an API)
- Use the latitude and longitude in an API that can look up the weather
Example Weather tool - Geocode
Example Weather tool - Geocode
get_coordinates("New York City"){'lat': 40.7127281, 'lon': -74.0060152}
Example Weather tool - Weather
import requests
def get_weather(lat: float, lon: float):
base_url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": lat,
"longitude": lon,
"current_weather": True,
}
response = requests.get(
base_url,
params=params,
)
data = response.json()
return {k: v for k, v in data.items()}Example Weather tool - Weather
get_weather(40.7127281, -74.0060152){'latitude': 40.710335,
'longitude': -73.99308,
'generationtime_ms': 0.09489059448242188,
'utc_offset_seconds': 0,
'timezone': 'GMT',
'timezone_abbreviation': 'GMT',
'elevation': 32.0,
'current_weather_units': {'time': 'iso8601',
'interval': 'seconds',
'temperature': '°C',
'windspeed': 'km/h',
'winddirection': '°',
'is_day': '',
'weathercode': 'wmo code'},
'current_weather': {'time': '2026-03-04T05:30',
'interval': 900,
'temperature': 1.1,
'windspeed': 2.5,
'winddirection': 270,
'is_day': 0,
'weathercode': 45}}Example Weather tool - Register
from chatlas import ChatAnthropic
chat = ChatAnthropic()
chat.register_tool(get_coordinates)
chat.register_tool(get_weather)Demo: Weather R
library(httr)
library(ellmer)
library(dotenv)
# Load environment variables
load_dot_env()
# Define weather function
get_weather <- function(latitude, longitude) {
base_url <- "https://api.open-meteo.com/v1/forecast"
tryCatch(
{
response <- GET(
base_url,
query = list(
latitude = latitude,
longitude = longitude,
current = "temperature_2m,wind_speed_10m,relative_humidity_2m"
)
)
rawToChar(response$content)
},
error = function(e) {
paste("Error fetching weather data:", e$message)
}
)
}
# Create chat instance
chat <- chat_openai(
model = "gpt-4.1",
system_prompt = "You are a helpful assistant that can check the weather. Report results in imperial units."
)
# Register the weather tool
#
# Created using `ellmer::create_tool_def(get_weather)`
chat$register_tool(tool(
get_weather,
"Fetches weather information for a specified location given by latitude and
longitude.",
latitude = type_number(
"The latitude of the location for which weather information is requested."
),
longitude = type_number(
"The longitude of the location for which weather information is requested."
)
))
# Test the chat
chat$chat("What is the weather in Seattle?")
Demo: Weather Python
"""Tool calling demo: LLM looks up weather via two registered tools.
Run: cd code/lecture06/demo02 && python chatlas-weather.py
"""
import requests
from chatlas import ChatGithub
# from chatlas import ChatAnthropic
from dotenv import load_dotenv
load_dotenv()
from get_coordinates import get_coordinates
from get_weather import get_weather
chat = ChatGithub(
model="gpt-4.1-mini", # or "gpt-4.1"
system_prompt=(
"You are a helpful assistant that can check the weather. "
"Report results in imperial units."
),
)
chat.register_tool(get_coordinates)
chat.register_tool(get_weather)
chat.chat("What is the weather in Seattle?")
Demo: Shiny Application
from chatlas import ChatAnthropic
from shiny.express import ui
from helper.get_coordinates import get_coordinates
from helper.get_weather import get_weather
chat_client = ChatAnthropic()
chat_client.register_tool(get_coordinates)
chat_client.register_tool(get_weather)
chat = ui.Chat(id="chat")
chat.ui(
messages=[
"Hello! I am a weather bot! Where would you like to get the weather form?"
]
)
@chat.on_user_submit
async def _(user_input: str):
response = await chat_client.stream_async(user_input, content="all")
await chat.append_message_stream(response)