Interfacing with large language models

Demo

Demo 1
Author

Emi Tanaka

Published

March 19, 2025

The image depicts a futuristic scene with a humanoid robot at the center.

This demo assumes that you have all the software requirements.

To access the LLM API via curl, we can use httr2 R package (as shown in Section 2), but the ellmer R package (Section 3) provides a more user-friendly interface so we use the latter as our primary method. Finally, for those interested in producing a Shiny web app, shinychat and Shiny Assistant will be briefly demonstrated in Section 5.

Warning

The ellmer R package is in very active development. There may be breaking changes introduced in the near future (hopefully not during the workshop).

I will demonstrate using two methods via: Open AI and Ollama. You can choose to code along with one or both, or none!

1 Setting up to use Open AI

First to use Open AI, you will need to get the Open AI API key. Register an account with Open AI and go here to generate a new API key.

You will need to store the API key as OPENAI_API_KEY in your .Renviron file. The easiest way to find this file is to use usethis::edit_r_environ() which should open this file. In this file you will need to enter your key like below.

.Renviron
OPENAI_API_KEY=<YOUR API KEY>

You will need to restart your R session for the above environmental variable to take effect.

To check if this worked, try running the command below to see if you see your API key.

Sys.getenv("OPENAI_API_KEY")

2 The “hard” way with httr2

The httr2 package gives a method to create, modify, and perform HTTP requests. This approach requires more work from the user so you will probably prefer to use ellmer package instead.

library(httr2)
Warning: package 'httr2' was built under R version 4.3.3
content <- "All models are wrong, but some models are"

2.1 Using Open AI

json <- request(base_url = "https://api.openai.com/v1/chat/completions") |> 
  req_headers('Authorization' = paste('Bearer', Sys.getenv("OPENAI_API_KEY")),
              'Content-Type' = 'application/json') |> 
  req_body_json(data = list(model = "gpt-4o-mini",
                            messages = list(list(role = "user",
                                                 content = content)))) |> 
  req_perform() |> 
  resp_body_json()

cat(json$choices[[1]]$message$content)
useful. This phrase, often attributed to the statistician George Box, emphasizes that while no model can perfectly capture the complexities of real-world phenomena, some models can still provide valuable insights and aid in decision-making. The key is to understand the limitations of the model and use it appropriately within its context.

To use other vendor API, you will need to use the API key (or some authentication method) and endpoint for the corresponding vendor.

2.2 Using Ollama

json <- request(base_url = "http://localhost:11434/api/chat") |> 
  req_body_json(data = list(model = "llama3.1:8b",
                            messages = list(list(role = "user",
                                                 content = content)),
                            stream = FALSE)) |> 
  req_perform() |> 
  resp_body_json()

cat(json$message$content)
A quote from George Box, a British statistician!

"All models are wrong, but some models are useful."

This quote is often shortened to "All models are wrong," which can be misleading. The full quote emphasizes that while no model can perfectly capture the complexity of reality, some models are still valuable for making predictions, guiding decisions, or understanding phenomena.

In essence, Box's statement acknowledges that:

1. **No model is perfect**: All models simplify complex systems and relationships, introducing some level of error.
2. **Some models are better than others**: Despite their limitations, certain models can provide useful insights, predict outcomes, or inform decision-making more accurately than others.

This quote has been widely used in various fields, including:

* Statistics and data science: To highlight the importance of model selection and evaluation.
* Economics: To acknowledge that economic theories and models are simplifications of reality.
* Computer science: To emphasize the need for model evaluation and validation.
* Philosophy: To discuss the nature of knowledge and the limitations of human understanding.

Box's quote serves as a reminder to critically evaluate models, consider their assumptions and limitations, and strive for continuous improvement.

3 The easier way with ellmer

library(ellmer)
Warning: package 'ellmer' was built under R version 4.3.3

3.1 Using AI provider’s API

ellmer currently supports the AI providers in Table 1. To use the API, you need to sign up for an account with the provider and obtain an API key (you may need to pay for some small credit to use this). We will only focus on Open AI where I have put a credit of US$15 (so far) to use their services. The API key can be set in a similar manner to Open AI API key (described in Section 1), but using the corresponding environmental variable name.

Table 1: AI Providers
Provider Name Link Environmental Variable
Azure OpenAI azure https://azure.microsoft.com/en-us/products/ai-services/openai-service AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET
Amazon bedrock https://aws.amazon.com/bedrock/
Anthropic claude https://www.anthropic.com/claude ANTHROPIC_API_KEY
Cortex Analyst cortex_analyst https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst
Databricks databricks https://www.databricks.com/
Deepseek deepseek https://platform.deepseek.com/ DEEPSEEK_API_KEY
Google gemini https://gemini.google.com/ GOOGLE_API_KEY
GitHub github https://github.com/marketplace/models GITHUB_PAT
Groq groq https://groq.com GROQ_API_KEY
Ollama ollama https://ollama.com/
Open AI openai https://platform.openai.com/ OPENAI_API_KEY
Openrouter openrouter https://openrouter.ai/ OPENROUTER_API_KEY
Perplexity AI perplexity https://www.perplexity.ai PERPLEXITY_API_KEY
Snowflake snowflake https://docs.snowflake.com/
vLLM vllm https://docs.vllm.ai VLLM_API_KEY

Once you have set up the API key, you can use the ellmer package to interact with the AI provider’s API.

3.1.1 Open AI via ellmer

chat_gpt <- chat_openai(model = "gpt-4o-mini", seed = 1, echo = TRUE)

chat_gpt$chat("Tell me a statistics joke!")
Why did the statistician bring a ladder to the bar?

Because he heard the drinks were on the house!

3.1.2 Gemini via ellmer

# seed doesn't work currently for ellmer 0.1.1
chat_flash <- chat_gemini(model = "gemini-2.0-flash", echo = TRUE)

chat_flash$chat("Tell me a statistics joke!")
Why did the statistician bring a ladder to the bar?

Because he heard the drinks were on the house!

3.1.3 Claude via ellmer

# seed doesn't work currently for ellmer 0.1.1
chat_anthropic <- chat_claude(echo = TRUE)

chat_anthropic$chat("Tell me a statistics joke!")
Here's a classic statistics joke:

What did the statistician say when he got into a hot bath?
"This is mean regression!"

Here's another one:
How do you tell the difference between an extroverted statistician and an 
introverted statistician?
The extroverted statistician looks at YOUR shoes when they're talking to you!

3.2 Using a local LLM

chat_llama <- chat_ollama(model = "llama3.1:8b", seed = 1, echo = TRUE)

chat_llama$chat("Tell me a statistics joke!")
A stats joke just for you!

Why did the mean go to therapy?

Because it was feeling a little "off"!

(Get it? Off-average... ah, nevermind!)

Which joke was funnier? 😄

3.3 Chat object

The created chat object can be used to continue the conversation.

chat_llama$chat("Tell me another statistics joke!")
You're a patient one!

Here's another one:

Why did the correlation coefficient break up with its lover?

Because it couldn't handle the negative relationship anymore!

(Hope that one plotted well for you!)

You can also use the chat object to get the chat history.

chat_llama
<Chat turns=4 tokens=86/79>
── user ────────────────────────────────────────────────────────────────────────
Tell me a statistics joke!
── assistant ───────────────────────────────────────────────────────────────────
A stats joke just for you!

Why did the mean go to therapy?

Because it was feeling a little "off"!

(Get it? Off-average... ah, nevermind!)
── user ────────────────────────────────────────────────────────────────────────
Tell me another statistics joke!
── assistant ───────────────────────────────────────────────────────────────────
You're a patient one!

Here's another one:

Why did the correlation coefficient break up with its lover?

Because it couldn't handle the negative relationship anymore!

(Hope that one plotted well for you!)

Or if you want to get it in a structured format, you can use the get_turns method.

chat_llama$get_turns()
[[1]]
<ellmer::Turn>
 @ role    : chr "user"
 @ contents:List of 1
 .. $ : <ellmer::ContentText>
 ..  ..@ text: chr "Tell me a statistics joke!"
 @ json    : list()
 @ tokens  : num [1:2] 0 0
 @ text    : chr "Tell me a statistics joke!"

[[2]]
<ellmer::Turn>
 @ role    : chr "assistant"
 @ contents:List of 1
 .. $ : <ellmer::ContentText>
 ..  ..@ text: chr "A stats joke just for you!\n\nWhy did the mean go to therapy?\n\nBecause it was feeling a little \"off\"!\n\n(G"| __truncated__
 @ json    :List of 7
 .. $ id                : chr "chatcmpl-483"
 .. $ object            : chr "chat.completion.chunk"
 .. $ created           : int 1742336090
 .. $ model             : chr "llama3.1:8b"
 .. $ system_fingerprint: chr "fp_ollama"
 .. $ choices           :List of 1
 ..  ..$ :List of 3
 ..  .. ..$ index        : int 0
 ..  .. ..$ delta        :List of 2
 ..  .. .. ..$ role   : chr "assistant"
 ..  .. .. ..$ content: chr "A stats joke just for you!\n\nWhy did the mean go to therapy?\n\nBecause it was feeling a little \"off\"!\n\n(G"| __truncated__
 ..  .. ..$ finish_reason: chr "stop"
 .. $ usage             :List of 3
 ..  ..$ prompt_tokens    : int 17
 ..  ..$ completion_tokens: int 37
 ..  ..$ total_tokens     : int 54
 @ tokens  : int [1:2] 17 37
 @ text    : chr "A stats joke just for you!\n\nWhy did the mean go to therapy?\n\nBecause it was feeling a little \"off\"!\n\n(G"| __truncated__

[[3]]
<ellmer::Turn>
 @ role    : chr "user"
 @ contents:List of 1
 .. $ : <ellmer::ContentText>
 ..  ..@ text: chr "Tell me another statistics joke!"
 @ json    : list()
 @ tokens  : num [1:2] 0 0
 @ text    : chr "Tell me another statistics joke!"

[[4]]
<ellmer::Turn>
 @ role    : chr "assistant"
 @ contents:List of 1
 .. $ : <ellmer::ContentText>
 ..  ..@ text: chr "You're a patient one!\n\nHere's another one:\n\nWhy did the correlation coefficient break up with its lover?\n\"| __truncated__
 @ json    :List of 7
 .. $ id                : chr "chatcmpl-143"
 .. $ object            : chr "chat.completion.chunk"
 .. $ created           : int 1742336091
 .. $ model             : chr "llama3.1:8b"
 .. $ system_fingerprint: chr "fp_ollama"
 .. $ choices           :List of 1
 ..  ..$ :List of 3
 ..  .. ..$ index        : int 0
 ..  .. ..$ delta        :List of 2
 ..  .. .. ..$ role   : chr "assistant"
 ..  .. .. ..$ content: chr "You're a patient one!\n\nHere's another one:\n\nWhy did the correlation coefficient break up with its lover?\n\"| __truncated__
 ..  .. ..$ finish_reason: chr "stop"
 .. $ usage             :List of 3
 ..  ..$ prompt_tokens    : int 69
 ..  ..$ completion_tokens: int 42
 ..  ..$ total_tokens     : int 111
 @ tokens  : int [1:2] 69 42
 @ text    : chr "You're a patient one!\n\nHere's another one:\n\nWhy did the correlation coefficient break up with its lover?\n\"| __truncated__

3.4 Interactive usage

You can use a chatbot via console:

live_console(chat_llama)

Or alternatively, via the browser:

live_browser(chat_llama) 

4 Advanced usage

Note that these advanced features are not necessary supported by all LLMs.

4.1 Reading images

Suppose you have an image below.

The LLM can “read” the image like below.

chat <- chat_openai(model = "gpt-4o-mini")
chat$chat("Describe the image below.",
          content_image_url("https://emitanaka.org/workshop-LLM-2025/images/abs-logo.jpg"))
The image features the emblem of Australia, which includes a kangaroo and an 
emu on each side of a shield. Above the shield is a star, and the word 
"AUSTRALIA" is displayed prominently. Below the emblem, in bold letters, is the
text "Australian Bureau of Statistics." The overall design is clean and 
straightforward, with a black-and-white color scheme.

4.2 Reading PDF documents

Now next, suppose you have some PDF document. Some LLMs can read the content of the PDF document. This is currently not supported on the Open AI via ellmer but is supported on Gemini.

chat <- chat_gemini(model = "gemini-2.0-flash")
chat$chat("Summarise the PDF document below.",
          content_pdf_url("https://www.abs.gov.au/about/our-organisation/corporate-reporting/abs-annual-reports/2023-24%20ABS%20Annual%20Report_final.pdf"))
Here is a summary of the PDF document you sent.

The document is the Australian Bureau of Statistics (ABS) Annual Report for 
2023-24.

Key aspects highlighted in the document are:
*   The ABS presents its achievements in a busy and productive year, responding
to the changing needs of the Australian community and providing greater value 
by measuring emerging economic and social trends.
*   It discusses modernising data acquisition, including adopting a 
digital-first approach and introducing the ABS Business Reporting web 
application.
*   It explains how the ABS is harnessing artificial intelligence, with a case 
study on using Al to support the review of the Australian and New Zealand 
Standard Classification of Occupations (ANZSCO).
*   It details preparations for the 2026 Census, including reviews of topics, 
stakeholder engagement, and testing.
*   It discusses key initiatives for 2024-25 and beyond, including the 
Australian Government's investment in designing an annual General Social 
Survey, the Big Data Timely Insights (BDTI) program, the comprehensive review 
of the ANZSCO, the Time Use Survey, and the Life Course Data Initiative (LCDI).
*   It covers a second special article that focuses on a Safety Iceberg Model, 
a strategic initiative to promote work health and safety (WHS) reporting and 
develop a culture of learning and continuous improvement.
*   The ABS performance in 2023-24 is assessed against strategic priorities, 
with a summary of performance results provided.
*   It provides an overview of financial matters, risks, and employee 
statistics.
*   There are also notes included on errors and omissions from the ABS 2022-23 
Annual report.

4.3 Tool or function calling

A LLM is often restricted to the information it has been trained on, so may not be aware of the current context

chat <- chat_openai(model = "gpt-4o-mini")
chat$chat("How long ago was the Australian Federal election?")
The most recent Australian federal election was held on May 21, 2022. As of 
today, October 4, 2023, that was approximately 16 months ago.

You can register a tool or function to be used by the LLM.

get_current_date <- function() as.character(Sys.Date())
chat$register_tool(tool(
  get_current_date,
  "Gets the current date."
))

And now when you ask the same question, the LLM can use the tool to get the answer.

chat$chat("How long ago was the Australian Federal election?")
The most recent Australian federal election was held on May 21, 2022. As of 
today, March 19, 2025, that was approximately 2 years and 10 months ago.

5 R Shiny apps

Shiny helps you to write web apps easily with R. This has proved to be massively useful for fast deployment of and proof of concept web apps for data science. You can integrate a chatbot interface using shinychat or use Shiny Assistant to help you write Shiny apps.

5.1 Chat assistant with shinychat

You can easily make a Shiny web app that incorporates a nice chat user interface using shinychat.

library(shiny)
library(shinychat)

ui <- fluidPage(
  chat_ui("chat")
)

server <- function(input, output, session) {
  chat <- ellmer::chat_openai(system_prompt = "You're a helpful statistics tutor.")
  
  observeEvent(input$chat_user_input, {
    stream <- chat$stream_async(input$chat_user_input)
    chat_append("chat", stream)
  })
}

shinyApp(ui, server)

5.2 Shiny Assistant

Shiny Assistant is an AI assistant to help you build Shiny apps! See the blog post about it here and to use it, go to https://gallery.shinyapps.io/assistant.




🎉 Ah! You’ve reached the end of this demo! You are astounding! Proceed to …

next slide

next exercise