Code
library(httr)
library(jsonlite)
Sys.setenv(ANTHROPIC_API_KEY = rstudioapi::askForSecret("Anthropic API Key"))Many of the interactive portions of this workshop depend on successfully being able to connect to a generative AI model via API (we will discuss API keys in more depth later). For this workshop you’ll need to obtain your own Anthropic API key through console.anthropic.com.
$5 in credit should be sufficient for all workshop purposes, and you’ll have enough left over to continue practicing sending LLM calls via API..
The following is a basic function for you to test that you can connect to Anthropic’s Claude Sonnet 4.6 model. We’ll cover some of the elements of the function later. To use this function you’ll need to have the API loaded for the R session, which can you do via the syntax below:
library(httr)
library(jsonlite)
Sys.setenv(ANTHROPIC_API_KEY = rstudioapi::askForSecret("Anthropic API Key"))When using the API key in your workflow, you can skip this step by creating an .Renviron file that is saved in your working directory. Each API key should have a line like: ANTHROPIC_API_KEY='paste_your_api_key_here'.
call_claude functionYou can download the following function by clicking the copy button (looks like a clipboard) in the code chunk displayed below. Alternatively, you can click the icon below:
library(httr)
library(jsonlite)
call_claude <- function(prompt,
model = "claude-sonnet-4-6",
system = NULL,
temperature = 0.5,
max_tokens = 4096,
effort = "low") {
# Get API key from environment
api_key <- Sys.getenv("ANTHROPIC_API_KEY")
# Convert text prompt to required message format
messages <- list(list(role = "user", content = prompt))
# Build request body
request_body <- list(
model = model,
messages = messages,
max_tokens = max_tokens,
temperature = temperature,
output_config = list(effort = effort)
)
# Add system prompt if provided
if (!is.null(system)) {
request_body$system <- system
}
# Set up headers
headers <- add_headers(
"x-api-key" = api_key,
"anthropic-version" = "2023-06-01",
"content-type" = "application/json"
)
# Make the API request
response <- POST(
url = "https://api.anthropic.com/v1/messages",
headers,
body = toJSON(request_body, auto_unbox = TRUE)
)
# Check if request was successful
if (http_status(response)$category != "Success") {
stop(paste("API request failed:", http_status(response)$message,
"\nDetails:", content(response, "text", encoding = "UTF-8")))
}
# Parse response and extract text content
result <- fromJSON(content(response, "text", encoding = "UTF-8"))
return(result$content$text)
}The only argument that needs to be supplied to the function is the prompt that you want to send to the model; all other arguments have default values, and we’ll discuss the other options in more detail in subsequent sections. The prompt should be a text string that is enclosed by parentheses: e.g., “Tell me a joke about educational measurement.”
test_joke <- call_claude("Tell me a joke about educational measurement.")
test_joke[1] "Here's one:\n\nA psychometrician walks into a bar and orders a drink. The bartender asks, \"How was your day?\"\n\nHe says, \"Well, I can't be certain, but I'm 95% confident it was somewhere between terrible and awful.\"\n\n---\n\nOr this classic:\n\nWhy did the test score go to therapy?\n\nIt had **reliability issues** — it kept giving different answers depending on the day.\n\n---\n\nThese are pretty niche, but if you work in assessment, you feel them deeply. 😄"
A few things to note:
Generally speaking, the process used by a generative AI model involves predicting the next token to be selected and sampling from a distribution of possible options. (We will discuss ways to better control this later.) A (sometimes, mostly) beautiful implication of this is that responses from generative AI models will often be different, even if the exact same prompt is used. This is especially the case in the simple function above, as we haven’t made an effort to tune the model generation parameters to achieve a deterministic or replicable result. Thus, when you call Anthropic and have it generate a joke for you using the same prompt, you will likely get different responses each time. In fact, each time that I compile this book on GitHub the result changes!
You may notice that escape sequences (\n, \t, etc.) may be present because the is returned from the model. One way to handle this is to wrap your response in cat(). We’ll revisit this again later in other sections.
cat(test_joke)Here's one:
A psychometrician walks into a bar and orders a drink. The bartender asks, "How was your day?"
He says, "Well, I can't be certain, but I'm 95% confident it was somewhere between terrible and awful."
---
Or this classic:
Why did the test score go to therapy?
It had **reliability issues** — it kept giving different answers depending on the day.
---
These are pretty niche, but if you work in assessment, you feel them deeply. 😄