Parallel processing and structured outputs

Exercise

Exercise 5
Author

Emi Tanaka

Published

June 4, 2025

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

library(ellmer)

1 response_format

The default response format is text, but you can have a more structured output with JSON output.

1.1 JSON format

The response_format is useful for getting a structured response. For data processing tasks, this is arguably the most useful feature. If you use JSON format, you need to make sure that you tell the system that you want the format as JSON somewhere, otherwise you may end up getting a lot of white spaces.

be_statistician <- "Respond like a statistician but keep it concise. No more than 80 words."
statistician <- chat_ollama(model = "llama3.1:8b",
                            seed = 1,
                            system_prompt = paste0(be_statistician, " Return the format as json."),
                            api_args = list(response_format = list(type = "json_object"),
                                            temperature = 0),
                            echo = TRUE)

The answer is in JSON format.

out <- statistician$chat("Could you tell me what the probability of getting a head four times if I toss the coin 4 times? Just return the probability.")
{
"probability": 0.0625
}

But it’s stored as a character.

class(out)
[1] "character"

Let’s convert this to a list object by converting the character to list using jsonlite::fromJSON().

jsonlite::fromJSON(out)
$probability
[1] 0.0625

The user prompt asks to specifically return the probability only, but sometimes the output format can have different JSON output.

1.2 Structured output

Suppose that we want the output with a structure shown in Figure 1.

flowchart LR
  response("response<br>(list)") --> steps("steps<br>(data.frame)")
  response --> final_answer("final_answer<br>(numeric)")
  steps --> explanation("explanation<br>(character)")
  steps --> output("output<br>(character)")
  final_answer
  linkStyle default stroke: black
Figure 1: The desired structured output.

The schema in Figure 1 is coded as a list below.

json_schema <- type_object(.description = "Provide the reasoning behind the answer.",
                           steps = type_array(description ="Provide the steps to the answer.",
                                              items = type_object(explanation = type_string("Explanation for the steps."),
                                                                  output = type_string("What the corresponding output is."))),
                           final_answer = type_number())

The schema stored in json_schema is parsed to response_format below.

be_statistician <- "Respond like a statistician but keep it concise. No more than 80 words."
tame_statistician <- chat_openai(
  system_prompt = be_statistician,
  model = "gpt-4o-mini",
  params = params(seed = 1,
                  temperature = 0)
)

The output is shown below.

out <- tame_statistician$chat_structured("Could you tell me what the probability of getting a head four times if I toss the coin 4 times?",
                                      type = json_schema) 
out
$steps
                                                                            explanation
1 Identify the number of trials (n) and the number of successes (k). Here, n=4 and k=4.
2               Calculate the probability of success (p) for a fair coin, which is 0.5.
3      Use the binomial probability formula: P(X=k) = (n choose k) * p^k * (1-p)^(n-k).
4                        Calculate (4 choose 4) = 1, (0.5)^4 = 0.0625, and (0.5)^0 = 1.
5                                                Thus, the final probability is 0.0625.
                                     output
1                                  n=4, k=4
2                                     p=0.5
3 P(X=4) = (4 choose 4) * (0.5)^4 * (0.5)^0
4                   P(X=4) = 1 * 0.0625 * 1
5                           P(X=4) = 0.0625

$final_answer
[1] 0.0625

Looking at the structure, you can see it matches with the desired output in Figure 1.

str(out)
List of 2
 $ steps       :'data.frame':   5 obs. of  2 variables:
  ..$ explanation: chr [1:5] "Identify the number of trials (n) and the number of successes (k). Here, n=4 and k=4." "Calculate the probability of success (p) for a fair coin, which is 0.5." "Use the binomial probability formula: P(X=k) = (n choose k) * p^k * (1-p)^(n-k)." "Calculate (4 choose 4) = 1, (0.5)^4 = 0.0625, and (0.5)^0 = 1." ...
  ..$ output     : chr [1:5] "n=4, k=4" "p=0.5" "P(X=4) = (4 choose 4) * (0.5)^4 * (0.5)^0" "P(X=4) = 1 * 0.0625 * 1" ...
 $ final_answer: num 0.0625

2 Parallel processing

If you have large data then you’ll find generally that the text processing with LLM will take too long. In this instance, you can speed it up by parallel processing with multiple simultaneous chats.

2.1 Parallel structured output

Suppose we have a series of prompts about a person and their age. We want to convert each of these prompts to extract the person’s name and their age.

prompts <- list(
  "They call me Ronan — I'm 28 and currently wandering across South America.",
  "At 45, I’ve finally learned how to skateboard. I’m Grace, by the way.",
  "I'm 34, and the name’s Layla. I restore vintage motorcycles for a living.",
  "Hi! The name’s Mira. I turned 22 last week and still don’t like coffee.",
  "I'm Jules. Just hit 39, and I still can’t figure out taxes.",
  "Name’s Andre, age 31. I collect rare vinyl and make terrible dad jokes.",
  "I’m 26, and people know me as Sam — the guy who bakes at midnight.",
  "My name? Camila. I’m 48 and recently became a certified yoga instructor.",
  "Hey, I’m Leo. Age 20. Professionally confused, but full of ambition.",
  "Just turned 30! I’m Nia, and I write greeting cards for a living.")

schema_person <- type_object(name = type_string(), 
                             age = type_number())

We can start multiple simultaneous chats by using parallel_chat_structured by parsing the chat object, prompts and the schema for structured output.

chat <- chat_openai(model = "gpt-4.1",
                    params = params(seed = 0))

parallel_chat_structured(chat, prompts, schema_person)
[working] (0 + 0) -> 5 -> 5 | ■■■■■■■■■■■■■■■■                  50%
[working] (0 + 0) -> 0 -> 10 | ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■  100%
     name age
1   Ronan  28
2   Grace  45
3   Layla  34
4    Mira  22
5   Jules  39
6   Andre  31
7     Sam  26
8  Camila  48
9     Leo  20
10    Nia  30

2.2 Batch chat

batch_chat() and batch_chat_structured() are similar to parallel_chat() and parallel_chat_structured(), but these are submitted as batch requests. This process can take longer to complete, but you generally pay less to the provider.




🎉 Huh! You’ve reached the end of this exercise! You are mind-blowing! Proceed to …

next slide

next exercise