
Tutorial
Tutorial.RmdSet the API key
To use ChatGPT via its API, you need to provide an API key. You can get one by creating an account at https://platform.openai.com/. You’ll get 5 USD worth of credit that expires after 3 months. This is enough to process around two and a half million English words.
set_api_key() # This will open a prompt. Paste your OpenAI API key into the prompt.When using set_api_key(), your key is stored in plain
text in the global .Renviron file as GPTWORKR_KEY, so it will apply to
all your projects. My preferred way is to create a .Renviron file in the
project directory and manually add the key there,
GPTWORKR_KEY = your_key_here. This way, I can use different
keys for different projects. If you do this while using git, remember to
add .Renviron to your .gitignore, or else your key will be visible in
the git repo.
Your key is sent unencrypted via HTTP, so your key is inherently a little exposed when using the OpenAI API.
Request a chat completion
gpt_req() requests one chat completion from the OpenAI
API’s chat-completion endpoint. It’s most important input is a tibble
with role and content columns.
messages <- tribble(
~role, ~content,
"system", "You are a helpful assistant in the tutorial vignette of the R package gptworkr.",
"assistant", "Hi, welcome to this tutorial! How can I help you today?",
"user", "I want to know what OpenAI's chat completion endpoint is."
)
reply <- gpt_req(messages)
reply
#> # A tibble: 1 × 10
#> gpt_response gpt_request gpt_model gpt_finish_reason gpt_prompt_tokens
#> <chr> <named lis> <chr> <chr> <int>
#> 1 "The OpenAI's chat … <tibble> gpt-3.5-… stop 61
#> # ℹ 5 more variables: gpt_completion_tokens <int>, gpt_total_tokens <int>,
#> # gpt_id <chr>, gpt_created <int>, gpt_system_fingerprint <chr>The reply is a tibble with one row, and the columns
gpt_response, gpt_request, and
gpt_model, plus some more columns that you can read about
in OpenAI’s documentation of the chat completion response object. Let’s
look at the first variables. gpt_response contains the full
response:
reply$gpt_response[1]
#> [1] "The OpenAI's chat completion endpoint is a service provided by OpenAI's GPT-3 model that allows you to generate text based on a prompt you provide. This endpoint can be accessed using the OpenAI API by sending a POST request to the appropriate URL with your prompt and API key.\n\nIn the gptworkr package, you can use the `gpt_chat()` function to interact with the OpenAI's chat completion endpoint. This function allows you to provide a prompt and receive a completion from the GPT-3 model.\n\nWould you like to see an example of how to use the `gpt_chat()` function in the gptworkr package?"gpt_request contains the tibble that was used to form
the request.
reply$gpt_request[[1]]
#> # A tibble: 3 × 2
#> role content
#> <chr> <chr>
#> 1 system You are a helpful assistant in the tutorial vignette of the R packa…
#> 2 assistant Hi, welcome to this tutorial! How can I help you today?
#> 3 user I want to know what OpenAI's chat completion endpoint is.This might seem superfluous, but it’s important for reproducibility
when using the other functions in gptworkr.
gpt_model simply contains which GPT model was used to
calculate the reply. gptworkr defaults to
gpt-3.5-turbo.
If you know the OpenAI API, you know there are other parameters like
model, temperature, max_tokens,
logit_bias. You can set these in gpt_req() as
well.
Example data
gptworkr comes with the wines dataset,
which we’ll use in the examples. We’ll only use the description
column
wines |> select(description) |> head()
#> # A tibble: 6 × 1
#> description
#> <chr>
#> 1 "The Definition range brings the world's greatest wine styles to Majestic cus…
#> 2 "You’d think that it’s seriously tough to impress Jean Rijckaert, who was the…
#> 3 "Sauternes is Bordeaux’s most renowned dessert wine style – and this is a stu…
#> 4 "Amarone is one of Italy’s most celebrated wines. Hailing from the Valpolicel…
#> 5 "The Edouard Delaunay estate’s rich history dates back to 1893. Its wines wer…
#> 6 "Our Parcel Series wines are Majestic's best kept secret. They're for when to…Put GPT to work
The function gpt() sends a gpt request with the same
instructions to all entries in a column of strings. It works by
iterating gpt_req() over all entries in the column.
#> # A tibble: 5 × 10
#> gpt_response gpt_request gpt_model gpt_finish_reason gpt_prompt_tokens
#> <chr> <named lis> <chr> <chr> <int>
#> 1 light, silky, suppl… <tibble> gpt-3.5-… stop 174
#> 2 fresh, creamy, mine… <tibble> gpt-3.5-… stop 224
#> 3 stunning, coveted, … <tibble> gpt-3.5-… stop 196
#> 4 celebrated, concent… <tibble> gpt-3.5-… stop 156
#> 5 rich, choice, fines… <tibble> gpt-3.5-… stop 173
#> # ℹ 5 more variables: gpt_completion_tokens <int>, gpt_total_tokens <int>,
#> # gpt_id <chr>, gpt_created <int>, gpt_system_fingerprint <chr>
Now it’s more opaque exactly which requests were sent to GPT. So let’s inspect one of them:
adjectives$gpt_request[[1]] |> tinytable::tt()| role | content |
|---|---|
| system | You are a helpful assistant that will be provided with a description of a wine. Please answer with a comma separated list of all adjectives used to describe the wine. |
| user | The Definition range brings the world's greatest wine styles to Majestic customers. Fleurie is the sub-region of Beaujolais where you'll find Gamay at its light, silky and supple best. To capture this style at its finest, we went to Pardon et Fils, a seventh-generation winery with some of the most experience in all of Beaujolais. To preserve all of its floral aromas, they left it completely unoaked. It's fresh and fruity, with flavours of red fruits, roses and violets. This is one of the few red wines that's light enough to serve with fish. |
We see that the gpt() function makes a system message
from the instruction argument and a user message from the
data column.
Classification
You may try to instruct gpt() to classify with
predetermined classes. This works surprisingly well, something like
this:
food_pairings <- gpt(
wines$description[1:5],
"What food goes with this wine? Answer with words from the following list. No other words are allowed. {red meat, poultry, fish, other}"
)But when I finally thought I had found a prompt that worked, it would
reply venison to the 187th item or have very chatty
responses from time to time.
gpt_classify() avoids this by implementing the “logit
bias trick”:
country <- gpt_classify(
data = wines$description[1:5],
instruction = "You are a helpful assistant. Where does the wine come from?",
classes = c("Narnia", "Atlantis", "Middle Earth")
)
country
#> # A tibble: 5 × 10
#> gpt_response gpt_request gpt_model gpt_finish_reason gpt_prompt_tokens
#> <fct> <named list> <chr> <chr> <int>
#> 1 Middle Earth <tibble [3 × 2]> gpt-3.5-tur… length 188
#> 2 Middle Earth <tibble [3 × 2]> gpt-3.5-tur… length 238
#> 3 Middle Earth <tibble [3 × 2]> gpt-3.5-tur… length 210
#> 4 Middle Earth <tibble [3 × 2]> gpt-3.5-tur… length 170
#> 5 Middle Earth <tibble [3 × 2]> gpt-3.5-tur… length 187
#> # ℹ 5 more variables: gpt_completion_tokens <int>, gpt_total_tokens <int>,
#> # gpt_id <chr>, gpt_created <int>, gpt_system_fingerprint <chr>See, it really works!
The request sent to GPT is more advanced in this case:
country$gpt_request[[1]] |> tinytable::tt()| role | content |
|---|---|
| system | You are a helpful assistant. Where does the wine come from? 1. Narnia 2. Atlantis 3. Middle Earth |
| user | The Definition range brings the world's greatest wine styles to Majestic customers. Fleurie is the sub-region of Beaujolais where you'll find Gamay at its light, silky and supple best. To capture this style at its finest, we went to Pardon et Fils, a seventh-generation winery with some of the most experience in all of Beaujolais. To preserve all of its floral aromas, they left it completely unoaked. It's fresh and fruity, with flavours of red fruits, roses and violets. This is one of the few red wines that's light enough to serve with fish. |
| assistant | The most likely choice for the data and context provided above is choice number |
So gpt_classify() builds its request like this:
| role | content |
|---|---|
| system |
instruction + numbered list of the
classes
|
| user | string from the data
|
| assistant | A reminder. The default reminder is “The most likely
choice for the data and context provided above is choice number” |
You can customize this. If you set append_classes to
FALSE, the system message will only contain the
instruction, not the numbered list of the
classes. This is useful if you need to add definitions or
information about the classes:
gpt_classify(data = wines$description[1:5],
instruction = "Where is the wine from?\nCountry 1: Narnia. Narnian wines are acidic and bitter.\nCountry 2: Atlantis. Atlantean wines are sweet.",
classes = c("Narnia","Atlantis"),
append_classes = FALSE
)To change the third message, the assistant message, set
the reminder argument. The third message is called a
reminder because it comes after the data and reminds GPT that it should
reply with a number. This can be any sentence that encourages GPT to
reply with a number as the next token.
gpt_classify(data = wines$description[1:5],
instruction = "Where is the wine from?\nCountry 1: Narnia. Narnian wines are acidic and bitter.\nCountry 2: Atlantis. Atlantean wines are sweet.",
classes = c("Narnia","Atlantis"),
reminder = "Considering the above information, the wine's most likely origin is Country "
)Loose ends
You can calculate what an analysis costs by feeding the response
tibble into the costs function.
costs(adjectives)
#> [1] 0.00058This number is in US $.
A WARNING
gptworkr does not yet control how quickly requests are
sent. This is a problem because the OpenAI API has rate
limits. My experience is that you will never overstep the rate limit
per minute, because gptworkr sends requests sequentially
and not in parallel, but you may overstep the rate limit per day. For
example, if you haven’t used the API much, your rate limit is 10 000
requests per day with many models. If you overstep this limit, the API
will send an error, gptworkr won’t catch it, and your call
to gpt() or gpt_classify() will end without
giving you any usable data. If this occurs in the, say, 2000th entry
processed, this can cost you some $.