Tool Calling

Programming with LLM APIs
A Beginner’s Guide in R and Python

posit::conf(2025)

2025-09-16

Recall: How do LLMs work?

  1. You write some words

  2. The LLM writes some more words

  3. You use those words

On their own, can LLMs… access the internet? send an email? interact with the world?

Let’s try it

library(ellmer)

chat <- chat("openai/gpt-4.1-nano")

chat$chat("What's the weather like in Atlanta, GA?")
chat$chat("Who are the keynote speakers at posit::conf(2025)?")
chat$chat("What day is it?")
import chatlas

chat = chatlas.Chat(model="openai/gpt-4.1-nano")
chat.chat("What's the weather like in Atlanta, GA?")
chat.chat("Who are the keynote speakers at posit::conf(2025)?")
chat.chat("What day is it?")

Tools

a.k.a. functions, tool calling or function calling

  • Bring real-time or up-to-date information to the model

  • Let the model interact with the world

Chatbot Systems

How do tool calls work?

What should I wear to posit::conf(2025) in Atlanta?

Human in the loop

👨‍💻 _demos/18_manual-tools/app.R

Wait… I can write code!

👨‍💻 _demos/19_tools/19_weather-tool.R

👨‍💻 _demos/19_tools/19_weather-tool.py

Recap: Tool definitions in R

tool_get_weather <- tool(
  tool_fn,
  description = "How and when to use the tool",
  arguments = list(
    .... = type_string(),
    .... = type_integer(),
    .... = type_enum(
      c("choice1", "choice2"),
      required = FALSE
    )
  )
)

Recap: Tool definitions in Python

def get_weather(lat, lon):
    return NWS.GetCurrentForecast(lat, lon)

Recap: Tool definitions in Python

def get_weather(lat: float, lon: float):
    return NWS.GetCurrentForecast(lat, lon)

Recap: Tool definitions in Python

def get_weather(lat: float, lon: float):
    """
    Get forecast data for a specific latitude and longitude.

    Parameters
    ----------
    lat : str
        Latitude of the location.
    lon : str
        Longitude of the location.
    """
    return NWS.GetCurrentForecast(lat, lon)

Your Turn 20_quiz-game-2

  1. I’ve given you a function that plays a sound when called.

  2. Your job: teach the model to play sounds in the Quiz Show game we made earlier today.

  3. You can use your prompt or switch _exercises_solutions to use ours.

06:00

Tool Annotations

  • Provide hints to clients about tool behavior

  • Help humans and AI systems make informed decisions about tool usage

  • All properties are hints only - not guaranteed to be accurate

  • MCP Tool Annotations Spec

Key Properties

Property Description
title Human-readable tool name
readOnlyHint Tool doesn’t modify environment
destructiveHint May perform destructive updates
idempotentHint Repeated calls with same arguments return same
openWorldHint Interacts with external entities vs. closed domain

Tool Annotations in R

tool_get_weather <- tool(
  get_weather,
  # description, arguments, ...
  annotations = tool_annotations(
    title = "Get Weather",
    readOnlyHint = TRUE,
    icon = fontawesome::fa_i("cloud-sun")
  )
)

Tool Annotations in Python

chat.register_tool(
    get_weather,
    annotations={
        "title": "Get Weather",
        "readOnlyHint": True,
        "extra": {
            "icon": faicons.fa_i("cloud-sun"),
        },
    },
)

Your Turn 21_quiz-game-3

  1. Add tool annotations to our play_sound tools.

  2. Pick an icon from FontAwesome free icons

08:00

What if we want to decide title and icon dynamically in the tool?

ellmer::ContentToolResult

get_weather <- function(lat, lon) {
  weathR::point_forecast(lat, lon)
}

ellmer::ContentToolResult

get_weather <- function(lat, lon) {
  forecast <- weathR::point_forecast(lat, lon)

  ContentToolResult(
    forecast
  )
}

ellmer::ContentToolResult

get_weather <- function(lat, lon) {
  forecast <- weathR::point_forecast(lat, lon)

  ContentToolResult(
    forecast,
    extra = list(
      display = list(
        title = paste("Weather for", lat, lon),
        icon = fontawesome::fa_i("cloud-sun")
      )
    )
  )
}

ellmer::ContentToolResult

get_weather <- function(lat, lon, location_name) {
  forecast <- weathR::point_forecast(lat, lon)

  ContentToolResult(
    forecast,
    extra = list(
      display = list(
        title = paste("Weather for", location_name),
        icon = fontawesome::fa_i("cloud-sun")
      )
    )
  )
}

chatlas.ContentToolResult

def get_weather(lat: float, lon: float, location_name: str):
    forecast = NWS.GetCurrentForecast(lat, lon)

    return ContentToolResult(
        forecast,
        extra={
            "display": {
                "title": f"Weather for {location_name}",
                "icon": faicons.fa_i("cloud-sun"),
            }
        },
    )

Your Turn 22_quiz-game-4

  1. Pick an icon and a title for each action in the Quiz show app.

  2. Return a ContentToolResult from each tool that sets the icon and title.

  3. Play the game again to see the icons and titles in action.

08:00

Tools in Shiny

  • Define the tool function inside the server

  • You can update reactive values in the tool function!

  • You can read reactive values, if you isolate() reads. (Be careful!)

Your Turn 23_quiz-game-5

I’ve updated the app to show the score in value boxes.
(And turned off the sounds.)

  1. In the system prompt, tell the model when to use update_score.

  2. Read update_score(). It should update a reactive value in the app when called. Finish implementing it.

  3. Complete the tool definition.

  4. Add tool annotations (optional, but fun!)

08:00