Skip to main content

AE 19: Improving LLM outputs

Application exercise
Modified

November 10, 2026

12_plot-image-1

library(readr)
library(ellmer)
library(ggplot2)

# Step 1: Make a scatter plot of the penguins dataset
data("penguins")

ggplot(data = penguins, aes(x = flipper_len, y = bill_len)) +
  geom_point(aes(color = species, shape = species), size = 3, alpha = 0.8) +
  geom_smooth(method = "lm", se = FALSE, aes(color = species)) +
  theme_minimal() +
  scale_color_manual(values = c("darkorange", "purple", "cyan4")) +
  labs(
    title = "Flipper and bill length",
    subtitle = "Dimensions for Adelie, Chinstrap and Gentoo Penguins at Palmer Station LTER",
    x = "Flipper length (mm)",
    y = "Bill length (mm)",
    color = "Penguin species",
    shape = "Penguin species"
  )

# Step 2: Ask Claude 4.6 Sonnet to interpret the plot.
# (Hint: see `content_image_...`)
chat <- chat("____", echo = "output")
chat$chat(
  "Interpret this plot.",
  ____()
)

13_plot-image-2

library(readr)
library(ellmer)
library(ggplot2)

# Step 1: This time, we're going to replace our mtcars scatter plot with a plot
# of uniform random noise.
set.seed(523)

m <- 32
u <- (seq_len(floor(sqrt(m))) - 0.5) / floor(sqrt(m))
grid <- as.matrix(expand.grid(x = u, y = u))

eps <- 1 / (2 * sqrt(m))
jitter <- matrix(runif(length(grid), -eps, eps), ncol = 2)
grid_jitter <- pmin(pmax(grid + jitter, 0), 1)

ggplot() +
  aes(x = grid_jitter[, 1], y = grid_jitter[, 2]) +
  geom_point(color = "steelblue", size = 3, alpha = 0.8) +
  theme_minimal() +
  labs(
    title = "Flipper and bill length",
    subtitle = "Dimensions for Adelie, Chinstrap and Gentoo Penguins at Palmer Station LTER",
    x = "Flipper length (mm)",
    y = "Bill length (mm)"
  )

# Step 2: Ask Claude 4.6 Sonnet to interpret the plot. How does it do this time?
chat <- chat("anthropic/claude-sonnet-4-6", echo = "output")
chat$chat(
  "Interpret this plot.",
  content_image_plot()
)

# Step 3: Work with a partner to improve the prompt to get a better
# interpretation.

14_evals

library(ellmer)
library(vitals)

vitals::vitals_log_dir_set("./logs")

# Each case plants a subtle data-quality artifact in the prompt, and the
# eval grades whether each model flags the specific artifact unprompted.
cases <- read_rds(here::here("data/bluff-mini-cases.rds"))

# The eval grades responses from two models against the same criteria.
models <- c(
  luna = "openai/gpt-6-luna",
  haiku = "anthropic/claude-haiku-4-5"
)

# Run the eval once per model and collect pass rates by case
tasks <- purrr::imap(models, \(model, model_name) {
  tsk <- Task$new(
    dataset = cases,
    solver = generate(),
    scorer = model_graded_qa(
      scorer_chat = chat_anthropic(model = "claude-sonnet-5")
    ),
    name = model_name
  )

  tsk$eval(
    solver_chat = chat(name = model),
    epochs = 2,
    view = FALSE
  )

  tsk
})

results <- purrr::map(tasks, \(tsk) {
  tsk$get_samples() |>
    dplyr::summarise(
      flagged = sum(score == "C"),
      n = dplyr::n(),
      pass_rate = mean(score == "C"),
      .by = id
    )
}) |>
  purrr::list_rbind(names_to = "model")

results

# Explore the transcripts in the log viewer
vitals::vitals_view()

15_quiz-game-1

library(shiny)
library(bslib)
library(ellmer)
library(shinychat)

# UI ---------------------------------------------------------------------------

ui <- page_fillable(
  chat_mod_ui("chat")
)

# Server -----------------------------------------------------------------------

server <- function(input, output, session) {
  client <- chat(
    "anthropic/claude-sonnet-4-6",
    # Step 1: Edit `prompt.md` to get the model to play the quiz game.
    system_prompt = interpolate_file(
      here::here("_exercises/14_quiz-game-1/prompt.md")
    )
  )

  chat <- chat_mod_server("chat", client)

  observe({
    # Note: this starts the game when the app launches
    chat$update_user_input(
      value = "Let's play the quiz game!",
      submit = TRUE
    )
  })
}

shinyApp(ui, server)

prompt.md

## Quiz Game Host

<!-- Add your game instructions here -->

16_coding-assistant

# Task ------------------------------------------------------------------------
library(ellmer)

# **Step 1:** Run the code below as-is to try the task without any extra
# context. How does the model do? Can you run the function? Does it give you the
# weather? Does it know enough about the {weathR} package to complete the task?
#
# **Step 2:** Now, let's add some context. Head over to GitHub repo for {weathR}
# (link in `docs.R.md`). Copy the project description from the `README.md` and
# paste it into the `docs.r.md` file.
#
# **Step 3:** Uncomment the extra lines to include these docs in the prompt and
# try again.

chat <- chat("anthropic/claude-sonnet-4-6", echo = "output")

chat$chat(
  ## Extra context from package docs
  # brio::read_file(here::here("_exercises/15_coding-assistant/docs.R.md")),
  ## Task prompt
  paste(
    "Write a simple function that takes latitude and longitude as inputs",
    "and returns the weather forecast for that location using the {weathR}",
    "package. Keep the function concise and simple and don't include error",
    "handling or data re-formatting. Include documentation in roxygen2 format,",
    "including examples for NYC and Atlanta, GA."
  )
)

Acknowledgments