From 474f73fbe9d551ce0f87649f8ef3bda0db201ccb Mon Sep 17 00:00:00 2001 From: John Coene Date: Wed, 24 Apr 2024 02:54:40 +0200 Subject: [PATCH 1/2] refactor: use S3 classes to make framework extendable --- .Rbuildignore | 2 + DESCRIPTION | 7 +- NAMESPACE | 32 ++++++- R/add_message.R | 68 +++++++++++++-- R/add_model.R | 27 ++++-- R/add_params.R | 5 -- R/create_chat.R | 188 ++++++++++++++++++++++++++---------------- R/extract_chat.R | 28 +++---- R/perform_chat.R | 138 ++++++++++++++++++------------- R/utils.R | 52 ++++++++++++ man/add_message.Rd | 12 ++- man/add_model.Rd | 9 +- man/append_message.Rd | 36 ++++++++ man/create_chat.Rd | 36 ++++++-- man/extract_chat.Rd | 4 +- man/get_engine.Rd | 14 ++++ man/get_messages.Rd | 22 +++++ man/get_model.Rd | 22 +++++ man/get_params.Rd | 14 ++++ man/get_uses.Rd | 14 ++++ man/inc_uses.Rd | 14 ++++ man/perform_chat.Rd | 18 +++- man/perform_query.Rd | 26 ++++++ man/prepare_engine.Rd | 29 +++++++ man/set_uses.Rd | 16 ++++ 25 files changed, 653 insertions(+), 180 deletions(-) create mode 100644 R/utils.R create mode 100644 man/append_message.Rd create mode 100644 man/get_engine.Rd create mode 100644 man/get_messages.Rd create mode 100644 man/get_model.Rd create mode 100644 man/get_params.Rd create mode 100644 man/get_uses.Rd create mode 100644 man/inc_uses.Rd create mode 100644 man/perform_query.Rd create mode 100644 man/prepare_engine.Rd create mode 100644 man/set_uses.Rd diff --git a/.Rbuildignore b/.Rbuildignore index f0a5d34..e266d62 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,3 +6,5 @@ ^docs$ ^pkgdown$ ^\.github$ +^README\.qmd$ +^tidychatmodels\.png$ diff --git a/DESCRIPTION b/DESCRIPTION index d3c9467..81afab4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,8 +2,10 @@ Type: Package Package: tidychatmodels Title: Chat With All Kinds of AI Models Through a Common Interface Version: 0.1.0 -Authors@R: - person("Albert", "Rapp", , "info@albert-rapp.de", role = c("aut", "cre")) +Authors@R: c( + person("Albert", "Rapp", , "info@albert-rapp.de", role = c("aut", "cre")), + person("John", "Coene", , "jcoenep@gmail.com", role = c("ctb")) + ) Description: This packages lets you chat with models from openAI and mistral.ai really easily. This package is set up in a modular fashion (similar to {tidymodels}) so that it is easy to switch between using @@ -16,7 +18,6 @@ Depends: R (>= 4.1.0) Imports: cli, - glue, httr2, knitr, purrr, diff --git a/NAMESPACE b/NAMESPACE index 65dd9f6..f2ea3f3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,9 +1,39 @@ # Generated by roxygen2: do not edit by hand -S3method(print,chat) +S3method(add_message,tidychat) +S3method(append_message,antropic) +S3method(append_message,ollama) +S3method(append_message,tidychat) +S3method(get_engine,tidychat) +S3method(get_messages,tidychat) +S3method(get_model,tidychat) +S3method(get_params,tidychat) +S3method(get_uses,tidychat) +S3method(inc_uses,tidychat) +S3method(perform_chat,tidychat) +S3method(perform_query,tidychat) +S3method(prepare_engine,anthropic) +S3method(prepare_engine,tidychat) +S3method(print,tidychat) +S3method(set_uses,tidychat) export(add_message) export(add_model) export(add_params) +export(append_message) export(create_chat) export(extract_chat) +export(get_engine) +export(get_messages) +export(get_model) +export(get_params) +export(get_uses) +export(inc_uses) +export(new_chat) +export(new_chat_anthropic) +export(new_chat_mistral) +export(new_chat_ollama) +export(new_chat_openai) export(perform_chat) +export(perform_query) +export(prepare_engine) +export(set_uses) diff --git a/R/add_message.R b/R/add_message.R index 3a1a42a..cc9da39 100644 --- a/R/add_message.R +++ b/R/add_message.R @@ -1,11 +1,10 @@ #' Add messages to a chat object. #' -#' @param chat_obj A chat object created from `create_chat()` +#' @param chat A chat object of class `tidychat`. #' @param message A character vector with one element. The message to add to the chat. #' @param role A character vector with one element. The role of the message. Typically 'user' or 'system'. #' #' @return A chat object with the messages added -#' @export #' #' @examples #' \dontrun{dotenv::load_dot_env() @@ -31,13 +30,72 @@ #' ) |> #' add_message('2 + 2 is 4, minus 1 that\'s 3, ') #' } -add_message <- function(chat_obj, message, role = 'user') { +#' @export +#' @name add_message +add_message <- function(chat, message, role = "user") UseMethod("add_message") + +#' @describeIn add_message Add a message to a `tidychat` object. +#' @export +add_message.tidychat <- function(chat, message, role = "user") { + messages <- get_messages(chat) - chat_obj$messages[[length(chat_obj$messages) + 1]] <- list( + messages[[length(messages) + 1]] <- list( role = role, content = message ) - chat_obj + + attr(chat, "messages") <- messages + chat +} + +#' Get messages from a chat object. +#' @param chat An object of class `tidychat`. +#' @export +#' @name get_messages +get_messages <- function(chat) UseMethod("get_messages") + +#' @describeIn get_messages Get messages from a `tidychat` object. +#' @export +get_messages.tidychat <- function(chat) { + attr(chat, "messages") +} + +#' Append a message to a `tidychat` object. +#' @param chat An object of class `tidychat`. +#' @param response The response as returned by `perform_query`. +#' @param ... Ignored for future compatibility. +#' @export +#' @name append_message +append_message <- function(chat, response, ...) UseMethod("append_message") + +#' @describeIn append_message Appends a message to a `tidychat` object. +#' @export +append_message.tidychat <- function(chat, response, ...) { + messages <- get_messages(chat) + + messages[[length(messages) + 1]] <- response$choices[[1]]$message + + attr(chat, "messages") <- messages + invisible(chat) } +#' @describeIn append_message Appends a message to a `ollama` object. +#' @export +append_message.ollama <- function(chat, response, ...){ + messages <- get_messages(chat) + messages[[length(messages) + 1]] <- response$message + attr(chat, "messages") <- messages + invisible(chat) +} +#' @describeIn append_message Appends a message to a `anthropic` object. +#' @export +append_message.antropic <- function(chat, response, ...){ + messages <- get_messages(chat) + messages[[length(messages) + 1]] <- list( + role = "assistant", + content = response$content[[1]]$text + ) + attr(chat, "messages") <- messages + invisible(chat) +} diff --git a/R/add_model.R b/R/add_model.R index 46843b1..3ff6844 100644 --- a/R/add_model.R +++ b/R/add_model.R @@ -1,10 +1,12 @@ #' Add a model to a chat object. #' -#' @param chat_obj A chat object created from `create_chat()` -#' @param model A character vector with one element. The model to use for the chat. You can use any chat completion model from openAI and mistral.ai here. Refer to their API docs for specific names. +#' @param chat An object of class `tidychat`. +#' @param model A character vector with one element. +#' The model to use for the chat. +#' You can use any chat completion model from openAI and mistral.ai here. +#' Refer to their API docs for specific names. #' #' @return A chat object with the model added -#' @export #' #' @examples #' \dontrun{ @@ -15,8 +17,21 @@ #' chat_mistral <- create_chat('mistral', Sys.getenv('MISTRAL_DEV_KEY')) |> #' add_model('mistral-large-latest') #' } -add_model <- function(chat_obj, model) { - chat_obj$model <- model - chat_obj +#' @export +#' @name add_model +add_model <- function(chat, model) { + attr(chat, "model") <- model + chat } +#' Get model from a chat object. +#' @param chat An object of class `tidychat`. +#' @export +#' @name get_model +get_model <- function(chat) UseMethod("get_model") + +#' @describeIn get_model Gets a model from a `tidychat` object. +#' @export +get_model.tidychat <- function(chat) { + attr(chat, "model") +} diff --git a/R/add_params.R b/R/add_params.R index e3fdfce..f860d24 100644 --- a/R/add_params.R +++ b/R/add_params.R @@ -24,8 +24,3 @@ add_params <- function(chat_obj, ...) { } chat_obj } - - - - - diff --git a/R/create_chat.R b/R/create_chat.R index f54b3d4..f369335 100644 --- a/R/create_chat.R +++ b/R/create_chat.R @@ -1,91 +1,52 @@ #' Create a chat object. #' -#' @param vendor A character vector with one element. Currently, only 'openai', 'mistral', 'anthropic' and 'ollama' are supported. -#' @param api_key The API key for the vendor's chat engine. If the vendor is 'ollama', this parameter is not required. -#' @param port The port number for the ollama chat engine. Default to ollama's standard port. If the vendor is not 'ollama', this parameter is not required. -#' @param api_version Api version that is required for Anthropic +#' @param vendor,engine A character vector with one element. +#' Currently, only "openai", "mistral", "anthropic" and "ollama" are supported. +#' @param api_key,key The API key for the vendor"s chat engine. +#' If the vendor is "ollama", this parameter is not required. +#' @param port The port number for the ollama chat engine. +#' Default to ollama"s standard port. If the vendor is not "ollama", this parameter is not required. +#' @param api_version,version Api version that is required for Anthropic +#' @param ... Additional parameters to be passed to the chat engine query. +#' @param object New query object from [httr2::request]. #' #' @return A chat object -#' @export #' #' @examples #' \dontrun{ #' dotenv::load_dot_env() -#' chat_openai <- create_chat('openai', Sys.getenv('OAI_DEV_KEY')) -#' chat_mistral <- create_chat('mistral', Sys.getenv('MISTRAL_DEV_KEY')) +#' chat_openai <- create_chat("openai", Sys.getenv("OAI_DEV_KEY")) +#' chat_mistral <- create_chat("mistral", Sys.getenv("MISTRAL_DEV_KEY")) #' } -create_chat <- function(vendor, api_key = '', port = if (vendor == 'ollama') 11434 else NULL, api_version = '') { - if (vendor != 'openai' & vendor != 'mistral' & vendor != 'ollama' & vendor != 'anthropic') stop('Unsupported vendor') - - if (vendor == 'openai') { - # https://platform.openai.com/docs/api-reference/making-requests - engine <- httr2::request( - base_url ='https://api.openai.com/v1/chat/completions' - ) |> - httr2::req_headers( - 'Authorization' = paste('Bearer', api_key), - 'Content-Type' = 'application/json' - ) - } - - if (vendor == 'mistral') { - # https://docs.mistral.ai/ - engine <- httr2::request( - base_url ='https://api.mistral.ai/v1/chat/completions' - ) |> - httr2::req_headers( - 'Authorization' = paste('Bearer', api_key), - 'Content-Type' = 'application/json', - 'Accept' = 'application/json' - ) - } - - if (vendor == 'ollama') { - # https://docs.mistral.ai/ - engine <- httr2::request( - base_url = glue::glue( - 'http://localhost:{port}/api/chat' - ) - ) - } - - if (vendor == 'anthropic') { - # https://platform.openai.com/docs/api-reference/making-requests - if (api_version == '') stop('Anthropic requires API version') - - engine <- httr2::request( - base_url ='https://api.anthropic.com/v1/messages' - ) |> - httr2::req_headers( - 'x-api-key' = api_key, - 'Content-Type' = 'application/json', - 'anthropic-version' = api_version - ) - } - +#' @export +#' @name create_chat +create_chat <- function( + vendor = c("openai", "mistral", "ollama", "anthropic"), + api_key = "", + port = if (vendor == "ollama") 11434 else NULL, + api_version = "" +) { + vendor <- match.arg(vendor) - if (vendor == 'ollama') { - chat <- list( - vendor_name = vendor, - engine = engine, - messages = list(), - params = list(stream = FALSE) + .Deprecated( + call, + "tidychat", + msg = sprintf( + "Deprecated in favour of `new_chat_%s`", + vendor ) - } + ) - if (vendor != 'ollama') { - chat <- list( - vendor_name = vendor, - engine = engine, - messages = list() - ) - } - class(chat) <- 'chat' - return(chat) + switch( + openai = new_chat_openai(api_key), + mistral = new_chat_mistral(api_key), + ollama = new_chat_ollama(port), + anthropic = new_chat_anthropic(api_key, api_version) + ) } #' @export -print.chat <- function(x, ...) { +print.tidychat <- function(x, ...) { cli::cli_div( theme = list( span.param = list(color = "blue") @@ -110,3 +71,84 @@ print.chat <- function(x, ...) { knit_print.chat <- function(x, ...) { knitr::knit_print(x, ...) } + +#' @export +#' @rdname create_chat +new_chat <- function(engine, object, ...){ + stopifnot(!...length() == 0) + structure( + object = object, + engine = engine, + messages = list(), + params = list(...), + model = NULL, + uses = 0L, + class = c("tidychat", engine) + ) +} + +#' @export +#' @rdname create_chat +new_chat_openai <- function(key){ + stopifnot(!missing(key)) + # https://platform.openai.com/docs/api-reference/making-requests + req <- httr2::request( + base_url = "https://api.openai.com/v1/chat/completions" + ) |> + httr2::req_headers( + "Authorization" = paste("Bearer", key), + "Content-Type" = "application/json" + ) + + new_chat("openai", req) +} + +#' @export +#' @rdname create_chat +new_chat_mistral <- function(key){ + stopifnot(!missing(key)) + # https://docs.mistral.ai/ + req <- httr2::request( + base_url = "https://api.mistral.ai/v1/chat/completions" + ) |> + httr2::req_headers( + "Authorization" = paste("Bearer", key), + "Content-Type" = "application/json", + "Accept" = "application/json" + ) + + new_chat("mistral", req) +} + +#' @export +#' @rdname create_chat +new_chat_ollama <- function(port) { + stopifnot(!missing(port)) + # https://docs.mistral.ai/ + req <- httr2::request( + base_url = sprintf( + "http://localhost:%s/api/chat", + port + ) + ) + + new_chat("ollama", req) +} + +#' @export +#' @rdname create_chat +new_chat_anthropic <- function(key, version){ + stopifnot(!missing(key), !missing(version)) + # https://platform.openai.com/docs/api-reference/making-requests + + req <- httr2::request( + base_url = "https://api.anthropic.com/v1/messages" + ) |> + httr2::req_headers( + "x-api-key" = key, + "Content-Type" = "application/json", + "anthropic-version" = version + ) + + new_chat("anthropic", req) +} diff --git a/R/extract_chat.R b/R/extract_chat.R index 182d38d..73547b7 100644 --- a/R/extract_chat.R +++ b/R/extract_chat.R @@ -1,6 +1,6 @@ #' Prints all messages to the console and saves them invisibly #' -#' @param chat_obj A chat object created from `create_chat()` +#' @param chat A chat object created from `create_chat()` #' @param silent A logical vector with one element. If TRUE, the messages are not printed to the console. #' #' @return A chat object with the responses added @@ -36,7 +36,7 @@ #' perform_chat() #' msgs_mistral <- chat_mistral |> extract_chat() #' } -extract_chat <- function(chat_obj, silent = FALSE) { +extract_chat <- function(chat, silent = FALSE) { if (!silent) { cli::cli_div( theme = list( @@ -46,21 +46,22 @@ extract_chat <- function(chat_obj, silent = FALSE) { ) ) - for (i in seq_along(chat_obj$messages)) { - if (chat_obj$messages[[i]]$role == "system") { - cli::cli_text("{.system_msg System: {chat_obj$messages[[i]]$content}}") + for (i in seq_along(chat$messages)) { + if (chat$messages[[i]]$role == "system") { + cli::cli_text("{.system_msg System: {chat$messages[[i]]$content}}") } - if (chat_obj$messages[[i]]$role == "user") { - cli::cli_text("{.user_msg User: {chat_obj$messages[[i]]$content}}") + if (chat$messages[[i]]$role == "user") { + cli::cli_text("{.user_msg User: {chat$messages[[i]]$content}}") } - if (chat_obj$messages[[i]]$role == "assistant") { - cli::cli_text("{.assistant_msg Assistant: {chat_obj$messages[[i]]$content}}") + if (chat$messages[[i]]$role == "assistant") { + cli::cli_text("{.assistant_msg Assistant: {chat$messages[[i]]$content}}") } } } - transposed_and_flattened_chats <- chat_obj$messages |> + + transposed_and_flattened_chats <- chat$messages |> purrr::transpose() |> purrr::map(unlist) @@ -68,9 +69,8 @@ extract_chat <- function(chat_obj, silent = FALSE) { role = transposed_and_flattened_chats$role, message = transposed_and_flattened_chats$content ) - if (!silent) { + if (!silent) return(invisible(msg_tibble)) - } else { - return(msg_tibble) - } + + return(msg_tibble) } diff --git a/R/perform_chat.R b/R/perform_chat.R index 3bd15a9..efd46a3 100644 --- a/R/perform_chat.R +++ b/R/perform_chat.R @@ -1,10 +1,11 @@ #' Sends the chat to the engine and adds the response to the chat object #' -#' @param chat_obj A chat object created from `create_chat()` -#' @param dry_run A logical indicating whether to return the prepared `httr2` request without actually sending out the request to the vendor. Defaults to FALSE. +#' @param chat An object of class `tidychat`. +#' @param dry_run A logical indicating whether to return the prepared_engine +#' `httr2` request without actually sending out the request to the vendor. Defaults to FALSE. +#' @param ... Ignored for future compatibility. #' #' @return A chat object with the responses added -#' @export #' #' @examples #' \dontrun{dotenv::load_dot_env() @@ -34,73 +35,98 @@ #' chat_mistral <- chat_mistral |> #' perform_chat() #' } -perform_chat <- function(chat_obj, dry_run = FALSE) { +#' +#' @export +#' @name perform_chat +perform_chat <- function(chat, dry_run = FALSE, ...) UseMethod("perform_chat") - if (chat_obj$vendor_name == 'anthropic') { - msgs <- chat_obj$messages +#' @describeIn perform_chat Perform a chat on a `tidychat` object. +#' @export +perform_chat.tidychat <- function(chat, dry_run = FALSE, ...){ + # prepare the query + prepared <- prepare_engine(chat) - non_system_msgs <- msgs[purrr::map_lgl(msgs, \(x) x$role != 'system')] - system_msg <- msgs[purrr::map_lgl(msgs, \(x) x$role == 'system')] - if (length(system_msg) > 1) stop('There can only be one system message') + # return early if dry run + if (dry_run) return(prepared) - if (length(system_msg) == 0) { - prepared_engine <- chat_obj$engine |> - httr2::req_body_json( - data = rlang::list2( - messages = non_system_msgs, - model = chat_obj$model, - !!!chat_obj$params - ) - ) - } + # perform the query + response <- perform_query(chat, prepared) - if (length(system_msg) == 1) { - prepared_engine <- chat_obj$engine |> - httr2::req_body_json( - data = rlang::list2( - messages = non_system_msgs, - model = chat_obj$model, - system = system_msg[[1]]$content, - !!!chat_obj$params - ) - ) - } + # increment the uses by 1 + chat <- inc_uses(chat) - } + # post process the query + chat <- append_message(chat, response) + + invisible(chat) +} + +#' Prepare tidychat engine query +#' @param chat An object of class `tidychat`. +#' @param ... Ignored for future compatibility. +#' @export +#' @name prepare_engine +prepare_engine <- function(chat, ...) UseMethod("prepare_engine") + +#' @describeIn prepare_engine Prepare a tidychat engine query. +#' @export +prepare_engine.tidychat <- function(chat, ...) { + chat |> + attr("engine") |> + httr2::req_body_json( + data = rlang::list2( + messages = get_messages(chat), + model = get_model(chat), + !!!get_params(chat) + ) + ) +} + +#' @describeIn prepare_engine Prepare an anthropic engine query. +#' @export +prepare_engine.anthropic <- function(chat, ...){ + msgs <- get_messages(chat) + + non_system_msgs <- msgs[purrr::map_lgl(msgs, \(x) x$role != "system")] + system_msg <- msgs[purrr::map_lgl(msgs, \(x) x$role == "system")] - if (chat_obj$vendor_name != 'anthropic') { - prepared_engine <- chat_obj$engine |> + if (length(system_msg) > 1) stop("There can only be one system message") + + if (length(system_msg) == 0) { + prepared_engine <- get_engine(chat) |> httr2::req_body_json( data = rlang::list2( - messages = chat_obj$messages, - model = chat_obj$model, - !!!chat_obj$params + messages = non_system_msgs, + model = get_model(chat), + !!!get_params(chat) ) ) + return(prepared_engine) } + get_engine(chat) |> + httr2::req_body_json( + data = rlang::list2( + messages = non_system_msgs, + model = get_model(chat), + system = system_msg[[1]]$content, + !!!get_params(chat) + ) + ) +} - if (dry_run) return(prepared_engine) +#' Perform a tidychat engine query +#' @param chat An object of class `tidychat`. +#' @param prepared_query A prepared query as returned by `prepare_engine`. +#' @param ... Ignored for future compatibility. +#' @export +#' @name perform_query +perform_query <- function(chat, prepared_query, ...) UseMethod("perform_query") - response <- prepared_engine |> +#' @describeIn perform_query Perform a tidychat engine query. +#' @export +perform_query.tidychat <- function(chat, prepared_query, ...){ + prepared_query |> httr2::req_perform() |> httr2::resp_body_json() - - if (chat_obj$vendor_name == 'ollama') { - chat_obj$messages[[length(chat_obj$messages) + 1]] <- response$message - } - - if (chat_obj$vendor_name == 'anthropic') { - chat_obj$messages[[length(chat_obj$messages) + 1]] <- list( - role = 'assistant', - content = response$content[[1]]$text - ) - } - - if (chat_obj$vendor_name != 'ollama') { - chat_obj$messages[[length(chat_obj$messages) + 1]] <- response$choices[[1]]$message - } - chat_obj$usage[[length(chat_obj$usage) + 1]] <- response$usage - chat_obj } - diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..c0e5c4a --- /dev/null +++ b/R/utils.R @@ -0,0 +1,52 @@ +#' Get params from a chat object +#' @param chat An object of class `tidychat`. +#' @export +get_params <- function(chat) UseMethod("get_params") + +#' @export +get_params.tidychat <- function(chat) { + attr(chat, "params") +} + +#' Get uses from a chat object +#' @param chat An object of class `tidychat`. +#' @export +get_uses <- function(chat) UseMethod("get_uses") + +#' @export +get_uses.tidychat <- function(chat) { + attr(chat, "uses") +} + +#' Set uses from a chat object +#' @param chat An object of class `tidychat`. +#' @param value An integer to set the uses to. +#' @export +set_uses <- function(chat, value) UseMethod("set_uses") + +#' @export +set_uses.tidychat <- function(chat, value) { + attr(chat, "uses") <- value +} + +#' Increment uses from a chat object by 1 +#' @param chat An object of class `tidychat`. +#' @export +inc_uses <- function(chat) UseMethod("inc_uses") + +#' @export +inc_uses.tidychat <- function(chat) { + uses <- attr(chat, "uses") + attr(chat, "uses") <- uses + 1 + return(chat) +} + +#' Get engine from a `tidychat` object +#' @param chat An object of class `tidychat`. +#' @export +get_engine <- function(chat) UseMethod("get_engine") + +#' @export +get_engine.tidychat <- function(chat) { + attr(chat, "engine") +} diff --git a/man/add_message.Rd b/man/add_message.Rd index c821e93..0694c71 100644 --- a/man/add_message.Rd +++ b/man/add_message.Rd @@ -2,12 +2,15 @@ % Please edit documentation in R/add_message.R \name{add_message} \alias{add_message} +\alias{add_message.tidychat} \title{Add messages to a chat object.} \usage{ -add_message(chat_obj, message, role = "user") +add_message(chat, message, role = "user") + +\method{add_message}{tidychat}(chat, message, role = "user") } \arguments{ -\item{chat_obj}{A chat object created from `create_chat()`} +\item{chat}{A chat object of class `tidychat`.} \item{message}{A character vector with one element. The message to add to the chat.} @@ -19,6 +22,11 @@ A chat object with the messages added \description{ Add messages to a chat object. } +\section{Methods (by class)}{ +\itemize{ +\item \code{add_message(tidychat)}: Add a message to a `tidychat` object. + +}} \examples{ \dontrun{dotenv::load_dot_env() chat_openai <- create_chat('openai', Sys.getenv('OAI_DEV_KEY'))|> diff --git a/man/add_model.Rd b/man/add_model.Rd index 2d43243..541ed82 100644 --- a/man/add_model.Rd +++ b/man/add_model.Rd @@ -4,12 +4,15 @@ \alias{add_model} \title{Add a model to a chat object.} \usage{ -add_model(chat_obj, model) +add_model(chat, model) } \arguments{ -\item{chat_obj}{A chat object created from `create_chat()`} +\item{chat}{An object of class `tidychat`.} -\item{model}{A character vector with one element. The model to use for the chat. You can use any chat completion model from openAI and mistral.ai here. Refer to their API docs for specific names.} +\item{model}{A character vector with one element. +The model to use for the chat. +You can use any chat completion model from openAI and mistral.ai here. +Refer to their API docs for specific names.} } \value{ A chat object with the model added diff --git a/man/append_message.Rd b/man/append_message.Rd new file mode 100644 index 0000000..68c33bb --- /dev/null +++ b/man/append_message.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_message.R +\name{append_message} +\alias{append_message} +\alias{append_message.tidychat} +\alias{append_message.ollama} +\alias{append_message.antropic} +\title{Append a message to a `tidychat` object.} +\usage{ +append_message(chat, response, ...) + +\method{append_message}{tidychat}(chat, response, ...) + +\method{append_message}{ollama}(chat, response, ...) + +\method{append_message}{antropic}(chat, response, ...) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} + +\item{response}{The response as returned by `perform_query`.} + +\item{...}{Ignored for future compatibility.} +} +\description{ +Append a message to a `tidychat` object. +} +\section{Methods (by class)}{ +\itemize{ +\item \code{append_message(tidychat)}: Appends a message to a `tidychat` object. + +\item \code{append_message(ollama)}: Appends a message to a `ollama` object. + +\item \code{append_message(antropic)}: Appends a message to a `anthropic` object. + +}} diff --git a/man/create_chat.Rd b/man/create_chat.Rd index 9e0c594..e3d51c0 100644 --- a/man/create_chat.Rd +++ b/man/create_chat.Rd @@ -2,23 +2,45 @@ % Please edit documentation in R/create_chat.R \name{create_chat} \alias{create_chat} +\alias{new_chat} +\alias{new_chat_openai} +\alias{new_chat_mistral} +\alias{new_chat_ollama} +\alias{new_chat_anthropic} \title{Create a chat object.} \usage{ create_chat( - vendor, + vendor = c("openai", "mistral", "ollama", "anthropic"), api_key = "", port = if (vendor == "ollama") 11434 else NULL, api_version = "" ) + +new_chat(engine, object, ...) + +new_chat_openai(key) + +new_chat_mistral(key) + +new_chat_ollama(port) + +new_chat_anthropic(key, version) } \arguments{ -\item{vendor}{A character vector with one element. Currently, only 'openai', 'mistral', 'anthropic' and 'ollama' are supported.} +\item{vendor, engine}{A character vector with one element. +Currently, only "openai", "mistral", "anthropic" and "ollama" are supported.} + +\item{api_key, key}{The API key for the vendor"s chat engine. +If the vendor is "ollama", this parameter is not required.} + +\item{port}{The port number for the ollama chat engine. +Default to ollama"s standard port. If the vendor is not "ollama", this parameter is not required.} -\item{api_key}{The API key for the vendor's chat engine. If the vendor is 'ollama', this parameter is not required.} +\item{api_version, version}{Api version that is required for Anthropic} -\item{port}{The port number for the ollama chat engine. Default to ollama's standard port. If the vendor is not 'ollama', this parameter is not required.} +\item{object}{New query object from [httr2::request].} -\item{api_version}{Api version that is required for Anthropic} +\item{...}{Additional parameters to be passed to the chat engine query.} } \value{ A chat object @@ -29,7 +51,7 @@ Create a chat object. \examples{ \dontrun{ dotenv::load_dot_env() -chat_openai <- create_chat('openai', Sys.getenv('OAI_DEV_KEY')) -chat_mistral <- create_chat('mistral', Sys.getenv('MISTRAL_DEV_KEY')) +chat_openai <- create_chat("openai", Sys.getenv("OAI_DEV_KEY")) +chat_mistral <- create_chat("mistral", Sys.getenv("MISTRAL_DEV_KEY")) } } diff --git a/man/extract_chat.Rd b/man/extract_chat.Rd index 39c6bc2..c8b9ed4 100644 --- a/man/extract_chat.Rd +++ b/man/extract_chat.Rd @@ -4,10 +4,10 @@ \alias{extract_chat} \title{Prints all messages to the console and saves them invisibly} \usage{ -extract_chat(chat_obj, silent = FALSE) +extract_chat(chat, silent = FALSE) } \arguments{ -\item{chat_obj}{A chat object created from `create_chat()`} +\item{chat}{A chat object created from `create_chat()`} \item{silent}{A logical vector with one element. If TRUE, the messages are not printed to the console.} } diff --git a/man/get_engine.Rd b/man/get_engine.Rd new file mode 100644 index 0000000..149a121 --- /dev/null +++ b/man/get_engine.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{get_engine} +\alias{get_engine} +\title{Get engine from a `tidychat` object} +\usage{ +get_engine(chat) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} +} +\description{ +Get engine from a `tidychat` object +} diff --git a/man/get_messages.Rd b/man/get_messages.Rd new file mode 100644 index 0000000..46d18a5 --- /dev/null +++ b/man/get_messages.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_message.R +\name{get_messages} +\alias{get_messages} +\alias{get_messages.tidychat} +\title{Get messages from a chat object.} +\usage{ +get_messages(chat) + +\method{get_messages}{tidychat}(chat) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} +} +\description{ +Get messages from a chat object. +} +\section{Methods (by class)}{ +\itemize{ +\item \code{get_messages(tidychat)}: Get messages from a `tidychat` object. + +}} diff --git a/man/get_model.Rd b/man/get_model.Rd new file mode 100644 index 0000000..5b0f16b --- /dev/null +++ b/man/get_model.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_model.R +\name{get_model} +\alias{get_model} +\alias{get_model.tidychat} +\title{Get model from a chat object.} +\usage{ +get_model(chat) + +\method{get_model}{tidychat}(chat) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} +} +\description{ +Get model from a chat object. +} +\section{Methods (by class)}{ +\itemize{ +\item \code{get_model(tidychat)}: Gets a model from a `tidychat` object. + +}} diff --git a/man/get_params.Rd b/man/get_params.Rd new file mode 100644 index 0000000..6d5ca5f --- /dev/null +++ b/man/get_params.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{get_params} +\alias{get_params} +\title{Get params from a chat object} +\usage{ +get_params(chat) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} +} +\description{ +Get params from a chat object +} diff --git a/man/get_uses.Rd b/man/get_uses.Rd new file mode 100644 index 0000000..2cc09be --- /dev/null +++ b/man/get_uses.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{get_uses} +\alias{get_uses} +\title{Get uses from a chat object} +\usage{ +get_uses(chat) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} +} +\description{ +Get uses from a chat object +} diff --git a/man/inc_uses.Rd b/man/inc_uses.Rd new file mode 100644 index 0000000..8a611c8 --- /dev/null +++ b/man/inc_uses.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{inc_uses} +\alias{inc_uses} +\title{Increment uses from a chat object by 1} +\usage{ +inc_uses(chat) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} +} +\description{ +Increment uses from a chat object by 1 +} diff --git a/man/perform_chat.Rd b/man/perform_chat.Rd index 5bd0b17..ab13407 100644 --- a/man/perform_chat.Rd +++ b/man/perform_chat.Rd @@ -2,14 +2,20 @@ % Please edit documentation in R/perform_chat.R \name{perform_chat} \alias{perform_chat} +\alias{perform_chat.tidychat} \title{Sends the chat to the engine and adds the response to the chat object} \usage{ -perform_chat(chat_obj, dry_run = FALSE) +perform_chat(chat, dry_run = FALSE, ...) + +\method{perform_chat}{tidychat}(chat, dry_run = FALSE, ...) } \arguments{ -\item{chat_obj}{A chat object created from `create_chat()`} +\item{chat}{An object of class `tidychat`.} + +\item{dry_run}{A logical indicating whether to return the prepared_engine +`httr2` request without actually sending out the request to the vendor. Defaults to FALSE.} -\item{dry_run}{A logical indicating whether to return the prepared `httr2` request without actually sending out the request to the vendor. Defaults to FALSE.} +\item{...}{Ignored for future compatibility.} } \value{ A chat object with the responses added @@ -17,6 +23,11 @@ A chat object with the responses added \description{ Sends the chat to the engine and adds the response to the chat object } +\section{Methods (by class)}{ +\itemize{ +\item \code{perform_chat(tidychat)}: Perform a chat on a `tidychat` object. + +}} \examples{ \dontrun{dotenv::load_dot_env() chat_openai <- create_chat('openai', Sys.getenv('OAI_DEV_KEY'))|> @@ -45,4 +56,5 @@ chat_mistral <- create_chat('mistral', Sys.getenv('MISTRAL_DEV_KEY')) |> chat_mistral <- chat_mistral |> perform_chat() } + } diff --git a/man/perform_query.Rd b/man/perform_query.Rd new file mode 100644 index 0000000..56e1429 --- /dev/null +++ b/man/perform_query.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/perform_chat.R +\name{perform_query} +\alias{perform_query} +\alias{perform_query.tidychat} +\title{Perform a tidychat engine query} +\usage{ +perform_query(chat, prepared_query, ...) + +\method{perform_query}{tidychat}(chat, prepared_query, ...) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} + +\item{prepared_query}{A prepared query as returned by `prepare_engine`.} + +\item{...}{Ignored for future compatibility.} +} +\description{ +Perform a tidychat engine query +} +\section{Methods (by class)}{ +\itemize{ +\item \code{perform_query(tidychat)}: Perform a tidychat engine query. + +}} diff --git a/man/prepare_engine.Rd b/man/prepare_engine.Rd new file mode 100644 index 0000000..e87bb26 --- /dev/null +++ b/man/prepare_engine.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/perform_chat.R +\name{prepare_engine} +\alias{prepare_engine} +\alias{prepare_engine.tidychat} +\alias{prepare_engine.anthropic} +\title{Prepare tidychat engine query} +\usage{ +prepare_engine(chat, ...) + +\method{prepare_engine}{tidychat}(chat, ...) + +\method{prepare_engine}{anthropic}(chat, ...) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} + +\item{...}{Ignored for future compatibility.} +} +\description{ +Prepare tidychat engine query +} +\section{Methods (by class)}{ +\itemize{ +\item \code{prepare_engine(tidychat)}: Prepare a tidychat engine query. + +\item \code{prepare_engine(anthropic)}: Prepare an anthropic engine query. + +}} diff --git a/man/set_uses.Rd b/man/set_uses.Rd new file mode 100644 index 0000000..0c09ca5 --- /dev/null +++ b/man/set_uses.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{set_uses} +\alias{set_uses} +\title{Set uses from a chat object} +\usage{ +set_uses(chat, value) +} +\arguments{ +\item{chat}{An object of class `tidychat`.} + +\item{value}{An integer to set the uses to.} +} +\description{ +Set uses from a chat object +} From cb5031815fa9bbabc60b8f57f82716af9a59ffe2 Mon Sep 17 00:00:00 2001 From: John Coene Date: Wed, 24 Apr 2024 12:15:55 +0200 Subject: [PATCH 2/2] tests: update tests to s3 --- NAMESPACE | 2 + R/add_params.R | 30 ++++++-- R/create_chat.R | 16 ++-- R/extract_chat.R | 20 +++-- R/perform_chat.R | 1 - man/add_params.Rd | 14 +++- man/create_chat.Rd | 4 +- man/extract_chat.Rd | 8 ++ man/get_params.Rd | 16 +++- tests/testthat/test-add_message.R | 72 ++++++++--------- tests/testthat/test-add_model.R | 18 ++--- tests/testthat/test-add_params.R | 12 +-- tests/testthat/test-create_chat.R | 70 +++++++++-------- tests/testthat/test-extract_chat.R | 27 +++---- tests/testthat/test-perform_chat.R | 119 ++++++++++++++--------------- 15 files changed, 234 insertions(+), 195 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index f2ea3f3..a35cf5b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,9 +1,11 @@ # Generated by roxygen2: do not edit by hand S3method(add_message,tidychat) +S3method(add_params,tidychat) S3method(append_message,antropic) S3method(append_message,ollama) S3method(append_message,tidychat) +S3method(extract_chat,tidychat) S3method(get_engine,tidychat) S3method(get_messages,tidychat) S3method(get_model,tidychat) diff --git a/R/add_params.R b/R/add_params.R index f860d24..09fd850 100644 --- a/R/add_params.R +++ b/R/add_params.R @@ -4,7 +4,6 @@ #' @param ... A named list of parameters to add to the chat object. Must be valid parameters from the API documentation. #' #' @return A chat object with the parameters added -#' @export #' #' @examples #' \dontrun{dotenv::load_dot_env() @@ -16,11 +15,26 @@ #' add_model('mistral-large-latest') |> #' add_params('temperature' = 0.5, 'max_tokens' = 100) #' } -add_params <- function(chat_obj, ...) { - if (is.null(chat_obj$params)) { - chat_obj$params <- utils::modifyList(list(), list(...)) - } else { - chat_obj$params <- utils::modifyList(chat_obj$params, list(...)) - } - chat_obj +#' @export +#' @name add_params +add_params <- function(chat, ...) UseMethod("add_params") + +#' @describeIn add_params Add parameters to a `tidychat` object. +#' @export +add_params.tidychat <- function(chat, ...) { + params <- modifyList(get_params(chat), list(...)) + attr(chat, "params") <- params + chat +} + +#' Get parameters from a chat object. +#' @param chat An object of class `tidychat`. +#' @export +#' @name get_params +get_params <- function(chat) UseMethod("get_params") + +#' @describeIn get_params Add parameters to a `tidychat` object. +#' @export +get_params.tidychat <- function(chat) { + attr(chat, "params") } diff --git a/R/create_chat.R b/R/create_chat.R index f369335..adafda2 100644 --- a/R/create_chat.R +++ b/R/create_chat.R @@ -38,6 +38,7 @@ create_chat <- function( ) switch( + vendor, openai = new_chat_openai(api_key), mistral = new_chat_mistral(api_key), ollama = new_chat_ollama(port), @@ -75,22 +76,21 @@ knit_print.chat <- function(x, ...) { #' @export #' @rdname create_chat new_chat <- function(engine, object, ...){ - stopifnot(!...length() == 0) structure( - object = object, + object, engine = engine, messages = list(), params = list(...), model = NULL, uses = 0L, - class = c("tidychat", engine) + class = c("tidychat", engine, class(object)) ) } #' @export #' @rdname create_chat -new_chat_openai <- function(key){ - stopifnot(!missing(key)) +new_chat_openai <- function(key = Sys.getenv("OPENAI_API_KEY")){ + stopifnot(key != "") # https://platform.openai.com/docs/api-reference/making-requests req <- httr2::request( base_url = "https://api.openai.com/v1/chat/completions" @@ -105,8 +105,8 @@ new_chat_openai <- function(key){ #' @export #' @rdname create_chat -new_chat_mistral <- function(key){ - stopifnot(!missing(key)) +new_chat_mistral <- function(key = Sys.getenv("MISTRAL_API_KEY")){ + stopifnot(key != "") # https://docs.mistral.ai/ req <- httr2::request( base_url = "https://api.mistral.ai/v1/chat/completions" @@ -132,7 +132,7 @@ new_chat_ollama <- function(port) { ) ) - new_chat("ollama", req) + new_chat("ollama", req, stream = FALSE) } #' @export diff --git a/R/extract_chat.R b/R/extract_chat.R index 73547b7..cb6d329 100644 --- a/R/extract_chat.R +++ b/R/extract_chat.R @@ -4,7 +4,6 @@ #' @param silent A logical vector with one element. If TRUE, the messages are not printed to the console. #' #' @return A chat object with the responses added -#' @export #' #' @examples #' \dontrun{dotenv::load_dot_env() @@ -36,7 +35,13 @@ #' perform_chat() #' msgs_mistral <- chat_mistral |> extract_chat() #' } -extract_chat <- function(chat, silent = FALSE) { +#' @export +#' @name extract_chat +extract_chat <- function(chat, silent = FALSE) UseMethod("extract_chat") + +#' @describeIn extract_chat Extracts the chat messages from a `tidychat` object. +#' @export +extract_chat.tidychat <- function(chat, silent = FALSE) { if (!silent) { cli::cli_div( theme = list( @@ -46,22 +51,22 @@ extract_chat <- function(chat, silent = FALSE) { ) ) - for (i in seq_along(chat$messages)) { - if (chat$messages[[i]]$role == "system") { + for (msg in get_messages(chat)) { + if (msg$role == "system") { cli::cli_text("{.system_msg System: {chat$messages[[i]]$content}}") } - if (chat$messages[[i]]$role == "user") { + if (msg$role == "user") { cli::cli_text("{.user_msg User: {chat$messages[[i]]$content}}") } - if (chat$messages[[i]]$role == "assistant") { + if (msg[[i]]$role == "assistant") { cli::cli_text("{.assistant_msg Assistant: {chat$messages[[i]]$content}}") } } } - transposed_and_flattened_chats <- chat$messages |> + transposed_and_flattened_chats <- get_messages(chat) |> purrr::transpose() |> purrr::map(unlist) @@ -69,6 +74,7 @@ extract_chat <- function(chat, silent = FALSE) { role = transposed_and_flattened_chats$role, message = transposed_and_flattened_chats$content ) + if (!silent) return(invisible(msg_tibble)) diff --git a/R/perform_chat.R b/R/perform_chat.R index efd46a3..27af5b0 100644 --- a/R/perform_chat.R +++ b/R/perform_chat.R @@ -72,7 +72,6 @@ prepare_engine <- function(chat, ...) UseMethod("prepare_engine") #' @export prepare_engine.tidychat <- function(chat, ...) { chat |> - attr("engine") |> httr2::req_body_json( data = rlang::list2( messages = get_messages(chat), diff --git a/man/add_params.Rd b/man/add_params.Rd index 127ac47..62e1e08 100644 --- a/man/add_params.Rd +++ b/man/add_params.Rd @@ -2,14 +2,17 @@ % Please edit documentation in R/add_params.R \name{add_params} \alias{add_params} +\alias{add_params.tidychat} \title{Add a model to a chat object.} \usage{ -add_params(chat_obj, ...) +add_params(chat, ...) + +\method{add_params}{tidychat}(chat, ...) } \arguments{ -\item{chat_obj}{A chat object created from `create_chat()`} - \item{...}{A named list of parameters to add to the chat object. Must be valid parameters from the API documentation.} + +\item{chat_obj}{A chat object created from `create_chat()`} } \value{ A chat object with the parameters added @@ -17,6 +20,11 @@ A chat object with the parameters added \description{ Add a model to a chat object. } +\section{Methods (by class)}{ +\itemize{ +\item \code{add_params(tidychat)}: Add parameters to a `tidychat` object. + +}} \examples{ \dontrun{dotenv::load_dot_env() chat_openai <- create_chat('openai', Sys.getenv('OAI_DEV_KEY'))|> diff --git a/man/create_chat.Rd b/man/create_chat.Rd index e3d51c0..190b644 100644 --- a/man/create_chat.Rd +++ b/man/create_chat.Rd @@ -18,9 +18,9 @@ create_chat( new_chat(engine, object, ...) -new_chat_openai(key) +new_chat_openai(key = Sys.getenv("OPENAI_API_KEY")) -new_chat_mistral(key) +new_chat_mistral(key = Sys.getenv("MISTRAL_API_KEY")) new_chat_ollama(port) diff --git a/man/extract_chat.Rd b/man/extract_chat.Rd index c8b9ed4..2e0623c 100644 --- a/man/extract_chat.Rd +++ b/man/extract_chat.Rd @@ -2,9 +2,12 @@ % Please edit documentation in R/extract_chat.R \name{extract_chat} \alias{extract_chat} +\alias{extract_chat.tidychat} \title{Prints all messages to the console and saves them invisibly} \usage{ extract_chat(chat, silent = FALSE) + +\method{extract_chat}{tidychat}(chat, silent = FALSE) } \arguments{ \item{chat}{A chat object created from `create_chat()`} @@ -17,6 +20,11 @@ A chat object with the responses added \description{ Prints all messages to the console and saves them invisibly } +\section{Methods (by class)}{ +\itemize{ +\item \code{extract_chat(tidychat)}: Extracts the chat messages from a `tidychat` object. + +}} \examples{ \dontrun{dotenv::load_dot_env() chat_openai <- create_chat('openai', Sys.getenv('OAI_DEV_KEY'))|> diff --git a/man/get_params.Rd b/man/get_params.Rd index 6d5ca5f..dcac450 100644 --- a/man/get_params.Rd +++ b/man/get_params.Rd @@ -1,14 +1,26 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.R +% Please edit documentation in R/add_params.R, R/utils.R \name{get_params} \alias{get_params} -\title{Get params from a chat object} +\alias{get_params.tidychat} +\title{Get parameters from a chat object.} \usage{ +get_params(chat) + +\method{get_params}{tidychat}(chat) + get_params(chat) } \arguments{ \item{chat}{An object of class `tidychat`.} } \description{ +Get parameters from a chat object. + Get params from a chat object } +\section{Methods (by class)}{ +\itemize{ +\item \code{get_params(tidychat)}: Add parameters to a `tidychat` object. + +}} diff --git a/tests/testthat/test-add_message.R b/tests/testthat/test-add_message.R index 4f7ecbf..480572f 100644 --- a/tests/testthat/test-add_message.R +++ b/tests/testthat/test-add_message.R @@ -1,37 +1,37 @@ test_that("user messages can be added to chat", { #ollama - chat <- create_chat('ollama', port = 14252) |> - add_message('This is a message') |> - add_message('And this is another one') + chat <- new_chat_ollama(port = 14252) |> + add_message("This is a message") |> + add_message("And this is another one") expect_equal( - chat$messages, + get_messages(chat), list( - list(role = 'user', content = 'This is a message'), - list(role = 'user', content = 'And this is another one') + list(role = "user", content = "This is a message"), + list(role = "user", content = "And this is another one") ) ) #openai - chat <- create_chat('openai', api_key = 'secret') |> - add_message('This is a message') |> - add_message('And this is another one') + chat <- new_chat_openai(key = "secret") |> + add_message("This is a message") |> + add_message("And this is another one") expect_equal( - chat$messages, + get_messages(chat), list( - list(role = 'user', content = 'This is a message'), - list(role = 'user', content = 'And this is another one') + list(role = "user", content = "This is a message"), + list(role = "user", content = "And this is another one") ) ) #mistral - chat <- create_chat('mistral', api_key = 'secret') |> - add_message('This is a message') |> - add_message('And this is another one') + chat <- new_chat_mistral(key = "secret") |> + add_message("This is a message") |> + add_message("And this is another one") expect_equal( - chat$messages, + get_messages(chat), list( - list(role = 'user', content = 'This is a message'), - list(role = 'user', content = 'And this is another one') + list(role = "user", content = "This is a message"), + list(role = "user", content = "And this is another one") ) ) }) @@ -39,38 +39,38 @@ test_that("user messages can be added to chat", { test_that("system messages can be added to chat", { #ollama - chat <- create_chat('ollama', port = 14252) |> - add_message(role = 'system', 'This is a SYSTEM message') |> - add_message('And this is another one') + chat <- new_chat_ollama(port = 14252) |> + add_message(role = "system", "This is a SYSTEM message") |> + add_message("And this is another one") expect_equal( - chat$messages, + get_messages(chat), list( - list(role = 'system', content = 'This is a SYSTEM message'), - list(role = 'user', content = 'And this is another one') + list(role = "system", content = "This is a SYSTEM message"), + list(role = "user", content = "And this is another one") ) ) #openai - chat <- create_chat('openai', api_key = 'secret') |> - add_message(role = 'system', 'This is a SYSTEM message') |> - add_message('And this is another one') + chat <- new_chat_openai(key = "secret") |> + add_message(role = "system", "This is a SYSTEM message") |> + add_message("And this is another one") expect_equal( - chat$messages, + get_messages(chat), list( - list(role = 'system', content = 'This is a SYSTEM message'), - list(role = 'user', content = 'And this is another one') + list(role = "system", content = "This is a SYSTEM message"), + list(role = "user", content = "And this is another one") ) ) #mistral - chat <- create_chat('mistral', api_key = 'secret') |> - add_message(role = 'system', 'This is a SYSTEM message') |> - add_message('And this is another one') + chat <- new_chat_mistral(key = "secret") |> + add_message(role = "system", "This is a SYSTEM message") |> + add_message("And this is another one") expect_equal( - chat$messages, + get_messages(chat), list( - list(role = 'system', content = 'This is a SYSTEM message'), - list(role = 'user', content = 'And this is another one') + list(role = "system", content = "This is a SYSTEM message"), + list(role = "user", content = "And this is another one") ) ) }) diff --git a/tests/testthat/test-add_model.R b/tests/testthat/test-add_model.R index fea8a6d..c5345ae 100644 --- a/tests/testthat/test-add_model.R +++ b/tests/testthat/test-add_model.R @@ -1,16 +1,16 @@ test_that("model can be added to chat", { #ollama - chat <- create_chat('ollama', port = 14252) |> - add_model('COOL-MODEL-NAME') - expect_equal(chat$model,'COOL-MODEL-NAME') + chat <- new_chat_ollama(port = 14252) |> + add_model("COOL-MODEL-NAME") + expect_equal(get_model(chat), "COOL-MODEL-NAME") #openai - chat <- create_chat('openai', api_key = 'secret') |> - add_model('COOL-MODEL-NAME') - expect_equal(chat$model,'COOL-MODEL-NAME') + chat <- new_chat_openai(key = "secret") |> + add_model("COOL-MODEL-NAME") + expect_equal(get_model(chat), "COOL-MODEL-NAME") #mistral - chat <- create_chat('mistral', api_key = 'secret') |> - add_model('COOL-MODEL-NAME') - expect_equal(chat$model,'COOL-MODEL-NAME') + chat <- new_chat_mistral(key = "secret") |> + add_model("COOL-MODEL-NAME") + expect_equal(get_model(chat), "COOL-MODEL-NAME") }) diff --git a/tests/testthat/test-add_params.R b/tests/testthat/test-add_params.R index 82cbcb1..026f2c3 100644 --- a/tests/testthat/test-add_params.R +++ b/tests/testthat/test-add_params.R @@ -1,21 +1,21 @@ test_that("parameters can be added to chat", { #ollama - chat <- create_chat('ollama', port = 14252) |> + chat <- new_chat_ollama(port = 14252) |> add_params(parameterA = 2343, parameterB = 23421) expect_equal( - chat$params, + get_params(chat), list(stream = FALSE, parameterA = 2343, parameterB = 23421) # stream = FALSE is default for this vendor # this also checks that multile `add_params` calls are merged ) #openai - chat <- create_chat('openai', api_key = 'secret') |> + chat <- new_chat_openai(key = "secret") |> add_params(parameterA = 2343, parameterB = 23421) - expect_equal(chat$params, list(parameterA = 2343, parameterB = 23421)) + expect_equal(get_params(chat), list(parameterA = 2343, parameterB = 23421)) #mistral - chat <- create_chat('mistral', api_key = 'secret') |> + chat <- new_chat_mistral(key = "secret") |> add_params(parameterA = 2343, parameterB = 23421) - expect_equal(chat$params, list(parameterA = 2343, parameterB = 23421)) + expect_equal(get_params(chat), list(parameterA = 2343, parameterB = 23421)) }) diff --git a/tests/testthat/test-create_chat.R b/tests/testthat/test-create_chat.R index edb5e69..f9c5d19 100644 --- a/tests/testthat/test-create_chat.R +++ b/tests/testthat/test-create_chat.R @@ -1,82 +1,80 @@ test_that("ollama chat can be created", { - chat <- create_chat('ollama', port = 14252) + chat <- new_chat_ollama(port = 14252) expect_equal( - chat$engine$url, - 'http://localhost:14252/api/chat' + chat$url, + "http://localhost:14252/api/chat" ) - expect_equal(chat$params$stream,FALSE) - expect_equal(chat$messages, list()) - expect_equal(chat$engine$headers, list()) + expect_equal(get_params(chat)$stream, FALSE) + expect_equal(get_messages(chat), list()) + expect_equal(chat$headers, list()) }) test_that("openai chat can be created", { - chat <- create_chat('openai', api_key = 'secret') + chat <- new_chat_openai(key = "secret") expect_equal( - chat$engine$url, - 'https://api.openai.com/v1/chat/completions' + chat$url, + "https://api.openai.com/v1/chat/completions" ) - expect_equal(chat$params$stream, NULL) - expect_equal(chat$messages, list()) + expect_equal(get_params(chat)$stream, NULL) + expect_equal(get_messages(chat), list()) headers_list <- list( - 'Authorization' = 'Bearer secret', - 'Content-Type' = 'application/json' + "Authorization" = "Bearer secret", + "Content-Type" = "application/json" ) attributes(headers_list) <- list( - names = c('Authorization', 'Content-Type'), - 'redact' = character() + names = c("Authorization", "Content-Type"), + "redact" = character() ) expect_equal( - chat$engine$headers, + chat$headers, headers_list ) }) test_that("mistral chat can be created", { - chat <- create_chat('mistral', api_key = 'secret') + chat <- new_chat_mistral(key = "secret") expect_equal( - chat$engine$url, - 'https://api.mistral.ai/v1/chat/completions' + chat$url, + "https://api.mistral.ai/v1/chat/completions" ) - expect_equal(chat$params$stream, NULL) - expect_equal(chat$messages, list()) - headers_list <- list('Bearer secret','application/json', 'application/json') + expect_equal(get_params(chat)$stream, NULL) + expect_equal(get_messages(chat), list()) + headers_list <- list("Bearer secret", "application/json", "application/json") attributes(headers_list) <- list( - names = c('Authorization', 'Content-Type', 'Accept'), - 'redact' = character() + names = c("Authorization", "Content-Type", "Accept"), + "redact" = character() ) expect_equal( - chat$engine$headers, + chat$headers, headers_list ) }) test_that("anthropic chat can be created", { - chat <- create_chat('anthropic', api_key = 'secret', api_version = '2023-06-01') + chat <- new_chat_anthropic(key = "secret", version = "2023-06-01") expect_equal( - chat$engine$url, - 'https://api.anthropic.com/v1/messages' + chat$url, + "https://api.anthropic.com/v1/messages" ) - expect_equal(chat$params$stream, NULL) - expect_equal(chat$messages, list()) - headers_list <- list('secret','application/json', '2023-06-01') + expect_equal(get_params(chat)$stream, NULL) + expect_equal(get_messages(chat), list()) + headers_list <- list("secret", "application/json", "2023-06-01") attributes(headers_list) <- list( - names = c('x-api-key', 'Content-Type', 'anthropic-version'), - 'redact' = character() + names = c("x-api-key", "Content-Type", "anthropic-version"), + "redact" = character() ) expect_equal( - chat$engine$headers, + chat$headers, headers_list ) }) - - diff --git a/tests/testthat/test-extract_chat.R b/tests/testthat/test-extract_chat.R index 873a32a..f6ba31a 100644 --- a/tests/testthat/test-extract_chat.R +++ b/tests/testthat/test-extract_chat.R @@ -1,18 +1,18 @@ test_that("chats can be extracted", { - chat <- create_chat('ollama',) |> + chat <- new_chat_ollama(port = 3000) |> add_message( - role = 'system', - message = 'You are a chatbot that completes texts. + role = "system", + message = "You are a chatbot that completes texts. You do not return the full text. - Just what you think completes the text.' + Just what you think completes the text." ) |> add_message( - # default role = 'user' - '2 + 2 is 4, minus 1 that\'s 3, ' + # default role = "user" + "2 + 2 is 4, minus 1 that\"s 3, " ) |> add_message( - role = 'assistant', - 'This is a fake reply' + role = "assistant", + "This is a fake reply" ) msgs <- chat |> extract_chat(silent = TRUE) @@ -20,15 +20,12 @@ test_that("chats can be extracted", { expect_equal( msgs, tibble::tibble( - role = c('system', 'user', 'assistant'), + role = c("system", "user", "assistant"), message = c( - 'You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text.', - '2 + 2 is 4, minus 1 that\'s 3, ', - 'This is a fake reply' + "You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text.", + "2 + 2 is 4, minus 1 that\"s 3, ", + "This is a fake reply" ) ) ) }) - - - diff --git a/tests/testthat/test-perform_chat.R b/tests/testthat/test-perform_chat.R index c874bfc..835e436 100644 --- a/tests/testthat/test-perform_chat.R +++ b/tests/testthat/test-perform_chat.R @@ -1,83 +1,83 @@ test_that("Mistral request fulfills API structure", { - chat <- create_chat('mistral', api_key = 'secret') |> - add_model('mistral-large-latest') |> + chat <- new_chat_mistral(key = "secret") |> + add_model("mistral-large-latest") |> add_params(temperature = 0.5, max_tokens = 100) |> add_message( - role = 'system', - message = 'You are a chatbot that completes texts. + role = "system", + message = "You are a chatbot that completes texts. You do not return the full text. - Just what you think completes the text.' + Just what you think completes the text." ) |> add_message( - # default role = 'user' - '2 + 2 is 4, minus 1 that\'s 3, ' + # default role = "user" + "2 + 2 is 4, minus 1 that\"s 3, " ) |> perform_chat(dry_run = TRUE) expect_equal( chat$body$data$messages, list( - list(role = 'system', content = 'You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text.'), - list(role = 'user', content = '2 + 2 is 4, minus 1 that\'s 3, ') + list(role = "system", content = "You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text."), + list(role = "user", content = "2 + 2 is 4, minus 1 that\"s 3, ") ) ) - expect_equal(chat$url, 'https://api.mistral.ai/v1/chat/completions') - expect_equal(chat$body$data$model, 'mistral-large-latest') + expect_equal(chat$url, "https://api.mistral.ai/v1/chat/completions") + expect_equal(chat$body$data$model, "mistral-large-latest") expect_equal(chat$body$data$temperature, 0.5) expect_equal(chat$body$data$max_tokens, 100) - expect_equal(chat$headers$`Content-Type`, 'application/json') - expect_equal(chat$headers$Authorization, 'Bearer secret') - expect_equal(chat$headers$Accept, 'application/json') + expect_equal(chat$headers$`Content-Type`, "application/json") + expect_equal(chat$headers$Authorization, "Bearer secret") + expect_equal(chat$headers$Accept, "application/json") }) test_that("openAI request fulfills API structure", { - chat <- create_chat('openai', api_key = 'secret') |> - add_model('gpt-3.5-turbo') |> + chat <- new_chat_openai(key = "secret") |> + add_model("gpt-3.5-turbo") |> add_params(temperature = 0.5, max_tokens = 100) |> add_message( - role = 'system', - message = 'You are a chatbot that completes texts. + role = "system", + message = "You are a chatbot that completes texts. You do not return the full text. - Just what you think completes the text.' + Just what you think completes the text." ) |> add_message( - # default role = 'user' - '2 + 2 is 4, minus 1 that\'s 3, ' + # default role = "user" + "2 + 2 is 4, minus 1 that\"s 3, " ) |> perform_chat(dry_run = TRUE) expect_equal( chat$body$data$messages, list( - list(role = 'system', content = 'You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text.'), - list(role = 'user', content = '2 + 2 is 4, minus 1 that\'s 3, ') + list(role = "system", content = "You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text."), + list(role = "user", content = "2 + 2 is 4, minus 1 that\"s 3, ") ) ) - expect_equal(chat$url, 'https://api.openai.com/v1/chat/completions') - expect_equal(chat$body$data$model, 'gpt-3.5-turbo') + expect_equal(chat$url, "https://api.openai.com/v1/chat/completions") + expect_equal(chat$body$data$model, "gpt-3.5-turbo") expect_equal(chat$body$data$temperature, 0.5) expect_equal(chat$body$data$max_tokens, 100) - expect_equal(chat$headers$`Content-Type`, 'application/json') - expect_equal(chat$headers$Authorization, 'Bearer secret') + expect_equal(chat$headers$`Content-Type`, "application/json") + expect_equal(chat$headers$Authorization, "Bearer secret") }) test_that("ollama request fulfills API structure", { - chat <- create_chat('ollama', api_key = 'secret', port = 25223) |> - add_model('gemma:7b') |> - add_message('What is love? IN 10 WORDS.') |> + chat <- new_chat_ollama(port = 25223) |> + add_model("gemma:7b") |> + add_message("What is love? IN 10 WORDS.") |> perform_chat(dry_run = TRUE) expect_equal( chat$body$data$messages, list( - list(role = 'user', content = 'What is love? IN 10 WORDS.') + list(role = "user", content = "What is love? IN 10 WORDS.") ) ) - expect_equal(chat$url, 'http://localhost:25223/api/chat') - expect_equal(chat$body$data$model, 'gemma:7b') + expect_equal(chat$url, "http://localhost:25223/api/chat") + expect_equal(chat$body$data$model, "gemma:7b") expect_equal(chat$body$data$stream, FALSE) expect_equal(chat$headers, list()) @@ -86,69 +86,64 @@ test_that("ollama request fulfills API structure", { test_that("Anthropic request fulfills API structure (when system msg present)", { - chat <- create_chat('anthropic', api_key = 'secret', api_version = '2023-06-01') |> - add_model('claude-3-opus-20240229') |> + chat <- new_chat_anthropic(key = "secret", version = "2023-06-01") |> + add_model("claude-3-opus-20240229") |> add_params(temperature = 0.5, max_tokens = 100) |> add_message( - role = 'system', - message = 'You are a chatbot that completes texts. + role = "system", + message = "You are a chatbot that completes texts. You do not return the full text. - Just what you think completes the text.' + Just what you think completes the text." ) |> add_message( - # default role = 'user' - '2 + 2 is 4, minus 1 that\'s 3, ' + # default role = "user" + "2 + 2 is 4, minus 1 that\"s 3, " ) |> perform_chat(dry_run = TRUE) expect_equal( chat$body$data$messages, list( - list(role = 'user', content = '2 + 2 is 4, minus 1 that\'s 3, ') + list(role = "user", content = "2 + 2 is 4, minus 1 that\"s 3, ") ) ) expect_equal( chat$body$data$system, - 'You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text.' + "You are a chatbot that completes texts.\n You do not return the full text.\n Just what you think completes the text." ) - expect_equal(chat$url, 'https://api.anthropic.com/v1/messages') - expect_equal(chat$body$data$model, 'claude-3-opus-20240229') + expect_equal(chat$url, "https://api.anthropic.com/v1/messages") + expect_equal(chat$body$data$model, "claude-3-opus-20240229") expect_equal(chat$body$data$temperature, 0.5) expect_equal(chat$body$data$max_tokens, 100) - expect_equal(chat$headers$`Content-Type`, 'application/json') - expect_equal(chat$headers$`x-api-key`, 'secret') - expect_equal(chat$headers$`anthropic-version`, '2023-06-01') + expect_equal(chat$headers$`Content-Type`, "application/json") + expect_equal(chat$headers$`x-api-key`, "secret") + expect_equal(chat$headers$`anthropic-version`, "2023-06-01") }) test_that("Anthropic request fulfills API structure (without system msg)", { - chat <- create_chat('anthropic', api_key = 'secret', api_version = '2023-06-01') |> - add_model('claude-3-opus-20240229') |> + chat <- new_chat_anthropic(key = "secret", version = "2023-06-01") |> + add_model("claude-3-opus-20240229") |> add_params(temperature = 0.5, max_tokens = 100) |> add_message( - # default role = 'user' - '2 + 2 is 4, minus 1 that\'s 3, ' + # default role = "user" + "2 + 2 is 4, minus 1 that\"s 3, " ) |> perform_chat(dry_run = TRUE) expect_equal( chat$body$data$messages, list( - list(role = 'user', content = '2 + 2 is 4, minus 1 that\'s 3, ') + list(role = "user", content = "2 + 2 is 4, minus 1 that\"s 3, ") ) ) expect_equal(chat$body$data$system, NULL) - expect_equal(chat$url, 'https://api.anthropic.com/v1/messages') - expect_equal(chat$body$data$model, 'claude-3-opus-20240229') + expect_equal(chat$url, "https://api.anthropic.com/v1/messages") + expect_equal(chat$body$data$model, "claude-3-opus-20240229") expect_equal(chat$body$data$temperature, 0.5) expect_equal(chat$body$data$max_tokens, 100) - expect_equal(chat$headers$`Content-Type`, 'application/json') - expect_equal(chat$headers$`x-api-key`, 'secret') - expect_equal(chat$headers$`anthropic-version`, '2023-06-01') - + expect_equal(chat$headers$`Content-Type`, "application/json") + expect_equal(chat$headers$`x-api-key`, "secret") + expect_equal(chat$headers$`anthropic-version`, "2023-06-01") }) - - - -