In this tutorial, you’ll make your first structured LLM call with dsprrr. By the end, you’ll be able to ask questions, get typed responses, and understand why signatures are powerful.
Time: 10-15 minutes
What You’ll Build
A working question-answering system that returns structured data—not just raw text.
Prerequisites
- R installed
- An OpenAI API key (set as
OPENAI_API_KEYenvironment variable) - Install the packages:
install.packages("pak")
pak::pak("JamesHWade/dsprrr")
pak::pak("tidyverse/ellmer")Step 1: Load the Packages
library(dsprrr)
#>
#> Attaching package: 'dsprrr'
#> The following object is masked from 'package:methods':
#>
#> signature
library(ellmer)You should see no errors. If you do, check that your API key is set correctly.
Step 2: Create a Chat Connection
Connect to OpenAI:
chat <- chat_openai()
#> Using model = "gpt-4.1".This creates a chat object you’ll use for all your LLM calls.
Step 3: Your First Structured Call
Let’s ask a simple question using dsp():
chat |> dsp("question -> answer", question = "What is the capital of France?")
#> [1] "The capital of France is Paris."You should see "Paris" returned. Let’s break down what
happened:
-
"question -> answer"is a signature—it declares one input (question) and one output (answer) - dsprrr handled all the prompt engineering for you
- The result came back as structured data, not raw text
Step 4: Try Different Questions
The same signature works for any question:
chat |> dsp("question -> answer", question = "What is 7 * 8?")
#> [1] "7 multiplied by 8 is 56."
chat |> dsp("question -> answer", question = "Who wrote Romeo and Juliet?")
#> [1] "Romeo and Juliet was written by William Shakespeare."Notice how you get clean, direct answers—no extra prose.
Step 5: Add Output Types
So far, answers have been strings. But what if you want a number or a specific choice?
Getting a Number
chat |> dsp(
"math_problem -> result: number",
math_problem = "What is 15% of 200?"
)
#> [1] 30The : number after result tells dsprrr you
want a numeric value, not a string.
Getting a Choice (Enum)
chat |> dsp(
"text -> sentiment: enum('positive', 'negative', 'neutral')",
text = "I absolutely loved this movie!"
)
#> [1] "positive"The LLM must pick from exactly those three options. Try changing the text to see different results:
Step 6: Multiple Inputs
Signatures can have multiple inputs. Separate them with commas:
chat |> dsp(
"context, question -> answer",
context = "R was created in 1993 by Ross Ihaka and Robert Gentleman at the University of Auckland.",
question = "When was R created?"
)
#> [1] "R was created in 1993."Now the LLM uses your context to answer the question:
chat |> dsp(
"context, question -> answer",
context = "The bakery opens at 7am and closes at 6pm. They sell croissants for $3 each.",
question = "How much do croissants cost?"
)
#> [1] "Croissants cost $3 each."Step 7: Adding Instructions
You can guide the LLM’s behavior with instructions:
chat |> dsp(
signature("question -> answer", instructions = "Answer in exactly one word."),
question = "What color is the sky on a clear day?"
)
#> [1] "Blue"
chat |> dsp(
signature("question -> answer", instructions = "Answer like a pirate."),
question = "What is the capital of France?"
)
#> [1] "Arrr, matey! The capital of France be Paris!"What You Learned
In this tutorial, you:
- Made your first structured LLM call with
dsp() - Used signatures to declare inputs and outputs
- Added output types:
string,number,bool,enum() - Combined multiple inputs
- Added instructions to guide behavior
What’s Different from Raw LLM Calls?
Without dsprrr, you’d write prompts like:
You are a helpful assistant. The user will ask a question.
Respond with just the answer, nothing else.
User: What is the capital of France?
With dsprrr, you just declare "question -> answer"
and the framework handles the rest. This becomes powerful when you need
to:
- Optimize prompts automatically
- Chain multiple LLM calls together
- Get consistent, typed outputs
Next Steps
Ready to build something reusable? Continue to:
- Tutorial 2: Building a Classifier — Create a module you can use repeatedly
- Quick Reference — Look up signature syntax
- The DSPy Philosophy — Understand why signatures work this way
