Skip to content

Commit

Permalink
More vignettes (#135)
Browse files Browse the repository at this point in the history
Fixes #48

Defaults to GPT-4o (not mini)
  • Loading branch information
hadley authored Nov 28, 2024
1 parent 4a017e2 commit 5edbd44
Show file tree
Hide file tree
Showing 108 changed files with 819 additions and 173 deletions.
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
^docs$
^pkgdown$
^vignettes/articles$
_cache/
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
.quarto
docs
inst/doc

/.quarto/
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# elmer (development version)

* The default `chat_openai()` model is now GPT-4o.

* New `Chat$set_turns()` to set turns. `Chat$turns()` is now `Chat$get_turns()`. `Chat$system_prompt()` is replaced with `Chat$set_system_prompt()` and `Chat$get_system_prompt()`.

* Async and streaming async chat are now event-driven and use `later::later_fd()` to wait efficiently on curl socket activity (#157).
Expand Down
2 changes: 1 addition & 1 deletion R/content.R
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ as_content <- function(x, error_call = caller_env()) {
if (is.null(x)) {
list()
} else if (is.character(x)) {
ContentText(paste0(x, collapse = "\n"))
ContentText(paste0(x, collapse = "\n\n"))
} else if (S7_inherits(x, Content)) {
x
} else {
Expand Down
12 changes: 7 additions & 5 deletions R/provider-claude.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ NULL
#' @description
#' [Anthropic](https://www.anthropic.com) provides a number of chat based
#' models under the [Claude](https://www.anthropic.com/claude) moniker.
#' Note that a Claude Pro membership does not give you the ability to call
#' models via the API; instead, you will need to sign up (and pay for) a
#' [developer account](https://console.anthropic.com/)
#'
#' Note that a Claude Prop membership does not give you the ability to call
#' models via the API. You will need to go to the
#' [developer console](https://console.anthropic.com/account/keys) to sign up
#' (and pay for) a developer account that will give you an API key that
#' you can use with this package.
#' To authenticate, we recommend saving your
#' [API key](https://console.anthropic.com/account/keys) to
#' the `ANTHROPIC_API_KEY` env var in your `.Renviron`
#' (which you can easily edit by calling `usethis::edit_r_environ()`).
#'
#' @inheritParams chat_openai
#' @inherit chat_openai return
Expand Down
5 changes: 5 additions & 0 deletions R/provider-gemini.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ NULL

#' Chat with a Google Gemini model
#'
#' To authenticate, we recommend saving your
#' [API key](https://aistudio.google.com/app/apikey) to
#' the `GOOGLE_API_KEY` env var in your `.Renviron`
#' (which you can easily edit by calling `usethis::edit_r_environ()`).
#'
#' @param api_key The API key to use for authentication. You generally should
#' not supply this directly, but instead set the `GOOGLE_API_KEY` environment
#' variable.
Expand Down
2 changes: 1 addition & 1 deletion R/provider-github.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ chat_github <- function(system_prompt = NULL,

check_installed("gitcreds")

model <- set_default(model, "gpt-4o-mini")
model <- set_default(model, "gpt-4o")

chat_openai(
system_prompt = system_prompt,
Expand Down
4 changes: 2 additions & 2 deletions R/provider-ollama.R
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#' Chat with a local ollama model
#' Chat with a local Ollama model
#'
#' @description
#' To use `chat_ollama()` first download and install
#' [ollama](https://ollama.com). Then install some models from the command line,
#' [Ollama](https://ollama.com). Then install some models from the command line,
#' e.g. with `ollama pull llama3.1` or `ollama pull gemma2`.
#'
#' This function is a lightweight wrapper around [chat_openai()] with
Expand Down
18 changes: 10 additions & 8 deletions R/provider-openai.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ NULL
#' Chat with an OpenAI model
#'
#' @description
#' [OpenAI](https://openai.com/o1/) provides a number of chat based models under
#' the [ChatGPT](https://chatgpt.com) moniker.
#' [OpenAI](https://openai.com/) provides a number of chat-based models,
#' mostly under the [ChatGPT](https://chat.openai.com/) brand.
#' Note that a ChatGPT Plus membership does not grant access to the API.
#' You will need to sign up for a developer account (and pay for it) at the
#' [developer platform](https://platform.openai.com).
#'
#' Note that a ChatGPT Plus membership does not give you the ability to call
#' models via the API. You will need to go to the
#' [developer platform](https://platform.openai.com) to sign up
#' (and pay for) a developer account that will give you an API key that
#' you can use with this package.
#' For authentication, we recommend saving your
#' [API key](https://platform.openai.com/account/api-keys) to
#' the `OPENAI_API_KEY` environment variable in your `.Renviron` file.
#' You can easily edit this file by calling `usethis::edit_r_environ()`.
#'
#' @param system_prompt A system prompt to set the behavior of the assistant.
#' @param turns A list of [Turn]s to start the chat with (i.e., continuing a
Expand Down Expand Up @@ -56,7 +58,7 @@ chat_openai <- function(system_prompt = NULL,
api_args = list(),
echo = c("none", "text", "all")) {
turns <- normalize_turns(turns, system_prompt)
model <- set_default(model, "gpt-4o-mini")
model <- set_default(model, "gpt-4o")
echo <- check_echo(echo)

if (is_testing() && is.null(seed)) {
Expand Down
2 changes: 1 addition & 1 deletion R/tools-def-auto.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#' @param topic A symbol or string literal naming the function to create
#' metadata for. Can also be an expression of the form `pkg::fun`.
#' @param model The OpenAI model to use for generating the metadata. Defaults to
#' "gpt-4o", which is highly recommended over "gpt-4o-mini".
#' "gpt-4o".
#' @param echo Emit the registration code to the console. Defaults to `TRUE` in
#' interactive sessions.
#' @param verbose If `TRUE`, print the input we send to the LLM, which may be
Expand Down
5 changes: 4 additions & 1 deletion R/turns.R
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ normalize_turns <- function(turns = NULL,
overwrite = FALSE,
error_call = caller_env()) {

check_string(system_prompt, allow_null = TRUE, call = error_call)
check_character(system_prompt, allow_null = TRUE, call = error_call)
if (length(system_prompt) > 1) {
system_prompt <- paste(system_prompt, collapse = "\n\n")
}

if (!is.null(turns)) {
if (!is.list(turns) || is_named(turns)) {
Expand Down
113 changes: 72 additions & 41 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -20,48 +20,68 @@ knitr::opts_chunk$set(
[![R-CMD-check](https://github.com/tidyverse/elmer/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/tidyverse/elmer/actions/workflows/R-CMD-check.yaml)
<!-- badges: end -->

The goal of elmer is to provide a user friendly wrapper over the most common llm providers. Major design goals include support for streaming and making it easy to register and call R functions.
elmer makes it easy to use large language models (LLM) from R. It supports a wider variety of LLM providers and implements a rich set of features including streaming outputs, tool/function calling, structured data extration, and more.

(Looking for something similar to elmer for python? Check out [chatlas](https://github.com/cpsievert/chatlas)!)

## Installation

You can install the development version of elmer from [GitHub](https://github.com/) with:

``` r
```{r}
#| eval: false
# install.packages("pak")
pak::pak("tidyverse/elmer")
```

## Prerequisites
## Providers

Depending on which backend you use, you'll need to set the appropriate environment variable in your `~/.Renviron` (an easy way to open that file is to call `usethis::edit_r_environ()`):
elmer supports a wide variety of model providers:

* For `chat_claude()`, set `ANTHROPIC_API_KEY` using the key from <https://console.anthropic.com/account/keys>.
* For `chat_gemini()`, set `GOOGLE_API_KEY` using the key from <https://aistudio.google.com/app/apikey>.
* For `chat_openai()` set `OPENAI_API_KEY` using the key from <https://platform.openai.com/account/api-keys>.
* Anthropic's Claude: `chat_claude()`.
* AWS Bedrock: `chat_bedrock()`.
* Azure OpenAI: `chat_azure()`.
* Databricks: `chat_databricks()`.
* GitHub model marketplace: `chat_github()`.
* Google Gemini: `chat_gemini()`.
* Groq: `chat_groq()`.
* Ollama: `chat_ollama()`.
* OpenAI: `chat_openai()`.
* perplexity.ai: `chat_perplexity()`.
* Snowflake Cortex: `chat_cortex()`.

## Model choice

If you're using elmer inside your organisation, you'll be limited to what your org allows, which is likely to be one provided by a big cloud provider, e.g. `chat_azure()`, `chat_bedrock()`, `chat_databricks()`, or `chat_snowflake()`. If you're using elmer for your own personal exploration, you have a lot more freedom so we have a few recommendations to help you get started:

- `chat_openai()` or `chat_claude()` are both good places to start. `chat_openai()` defaults to **GPT-4o**, but you can use `model = "gpt-4o-mini"` for a cheaper lower-quality model, or `model = "o1-mini"` for more complex reasoning. `chat_claude()` is similarly good; it defaults to **Claude 3.5 Sonnet** which we have found to be particularly code at writing code.

- `chat_gemini()` is great for large prompt, because it has a much larger context window than other models. It allows up to 1 million tokens, compared to Claude 3.5 Sonnet's 200k and GPT-4o's 128k.

- `chat_ollama()`, which uses [Ollama](https://ollama.com), allows you to run models on your own computer. The biggest models you can run locally aren't as good as the state of the art hosted models, but they also don't share your data and are effectively free.

## Using elmer

You chat with elmer in several different ways, depending on whether you are working interactively or programmatically. They all start with creating a new chat object:
You can work with elmer in several different ways, depending on whether you are working interactively or programmatically. They all start with creating a new chat object:

```r
library(elmer)

chat <- chat_openai(
model = "gpt-4o-mini",
system_prompt = "You are a friendly but terse assistant.",
echo = TRUE
)
```

Chat objects are stateful: they retain the context of the conversation, so each new query can build on the previous ones. This is true regardless of which of the various ways of chatting you use.
Chat objects are stateful [R6 objects](https://r6.r-lib.org): they retain the context of the conversation, so each new query can build on the previous ones, and you call their methods with `$`.

### Interactive chat console

The most interactive, least programmatic way of using elmer is to chat with it directly in your R console with `live_console(chat)` or in your browser with `live_browser()`.
The most interactive and least programmatic way of using elmer is to chat directly in your R console or browser with `live_console(chat)` or `live_browser()`:

```{r}
#| eval: false
```{r eval=FALSE}
live_console(chat)
#> ╔════════════════════════════════════════════════════════╗
#> ║ Entering chat console. Use """ for multi-line input. ║
Expand All @@ -76,59 +96,70 @@ live_console(chat)
#> in the early 1990s.
```

The chat console is useful for quickly exploring the capabilities of the model, especially when you've customized the chat object with tool integrations (see below).

Again, keep in mind that the chat object retains state, so when you enter the chat console, any previous interactions with that chat object are still part of the conversation, and any interactions you have in the chat console will persist even after you exit back to the R prompt.
Keep in mind that the chat object retains state, so when you enter the chat console, any previous interactions with that chat object are still part of the conversation, and any interactions you have in the chat console will persist after you exit back to the R prompt. This is true regardless of which of the various chat functions you use.

### Interactive method call

The second most interactive way to chat using elmer is to call the `chat()` method.
The second most interactive way to chat is to call the `chat()` method:

```{r}
#| eval: false
```{r eval=FALSE}
chat$chat("What preceding languages most influenced R?")
#> R was primarily influenced by the S programming language, particularly S-PLUS.
#> Other languages that had an impact include Scheme and various data analysis
#> languages.
```

If you initialize the chat object with `echo = TRUE`, as we did above, the `chat` method streams the response to the console as it arrives. When the entire response is received, it is returned as a character vector (invisibly, so it's not printed twice).
If you initialize the chat object in the global environment, the `chat` method will stream the response to the console as it arrives. When the entire response is received, it is also returned as a character vector (but invisibly, so it's not printed twice). This mode is useful when you want to see the response as it arrives, but you don't want to enter the chat console.

This mode is useful when you want to see the response as it arrives, but you don't want to enter the chat console.
If you want to ask a question about an image, you can pass one or more additional input arguments using `content_image_file()` and/or `content_image_url()`:

#### Vision (image input)
```{r}
#| eval: false
If you want to ask a question about an image, you can pass one or more additional input arguments using `content_image_file()` and/or `content_image_url()`.

```{r eval=FALSE}
chat$chat(
content_image_url("https://www.r-project.org/Rlogo.png"),
"Can you explain this logo?"
)
#> The logo of R features a stylized letter "R" in blue, enclosed in an oval shape that resembles the letter "O,"
#> signifying the programming language's name. The design conveys a modern and professional look, reflecting its use
#> in statistical computing and data analysis. The blue color often represents trust and reliability, which aligns
#> with R's role in data science.
#> The logo of R features a stylized letter "R" in blue, enclosed in an oval
#> shape that resembles the letter "O," signifying the programming language's
#> name. The design conveys a modern and professional look, reflecting its use
#> in statistical computing and data analysis. The blue color often represents
#> trust and reliability, which aligns with R's role in data science.
```

The `content_image_url` function takes a URL to an image file and sends that URL directly to the API. The `content_image_file` function takes a path to a local image file and encodes it as a base64 string to send to the API. Note that by default, `content_image_file` automatically resizes the image to fit within 512x512 pixels; set the `resize` parameter to `"high"` if higher resolution is needed.

### Programmatic chat

If you don't want to see the response as it arrives, you can turn off echoing by leaving off the `echo = TRUE` argument to `chat_openai()`.

```{r eval=FALSE}
chat <- chat_openai(
model = "gpt-4o-mini",
system_prompt = "You are a friendly but terse assistant."
)
chat$chat("Is R a functional programming language?")
#> [1] "Yes, R supports functional programming concepts. It allows functions to be first-class objects, supports higher-order functions, and encourages the use of functions as core components of code. However, it also supports procedural and object-oriented programming styles."
The most programmataic way to chat is to create the chat object inside a function, where live streaming is automatically suppressed and `$chat()` returns the result as a string:

```{r}
#| eval: false
my_function <- function() {
chat <- chat_openai(
model = "gpt-4o-mini",
system_prompt = "You are a friendly but terse assistant.",
)
chat$chat("Is R a functional programming language?")
}
my_function()
#> [1] "Yes, R supports functional programming concepts. It allows functions to
#> be first-class objects, supports higher-order functions, and encourages the
#> use of functions as core components of code. However, it also supports
#> procedural and object-oriented programming styles."
```

This mode is useful for programming using elmer, when the result is either not intended for human consumption or when you want to process the response before displaying it.
If needed, you can manually control this behaviour with the `echo` argument.

This mode is useful for programming with elmer, when the result is either not intended for human consumption or when you want to process the response before displaying it.

## Learning more

* Learn more about streaming and async APIs in `vignette("streaming-async")`.
* Learn more about tool calling (aka function calling) in `vignette("tool-calling")`.
elmer comes with a bunch of vignettes to help you learn more:

* Learn key vocabulary and see example use cases in `vignette("elmer")`.
* Learn how to design your prompt in `vignette("prompt-design")`.
* Learn about tool/function calling in `vignette("tool-calling")`.
* Learn how to extract structured data in `vignette("structured-data")`.
* Learn about streaming and async APIs in `vignette("streaming-async")`.
Loading

0 comments on commit 5edbd44

Please sign in to comment.