diff --git a/.Rbuildignore b/.Rbuildignore index 7ef77f8..c098ecd 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -5,3 +5,4 @@ ^CODE_OF_CONDUCT\.md$ ^\.github$ ^codecov\.yml$ +^dev$ diff --git a/.Renviron b/.Renviron deleted file mode 100644 index d8a3bcb..0000000 --- a/.Renviron +++ /dev/null @@ -1 +0,0 @@ -ATTACH_STARTUP_PKGS=TRUE diff --git a/.Rprofile b/.Rprofile new file mode 100644 index 0000000..44b0137 --- /dev/null +++ b/.Rprofile @@ -0,0 +1,15 @@ +if (interactive()) { + if (as.logical(Sys.getenv("ATTACH_STARTUP_PKGS", FALSE))) { + usethis::ui_todo("Attaching development supporting packages...") + suppressPackageStartupMessages(suppressWarnings({ + library(devtools) + ui_done("Library {ui_value('devtools')} attached.") + library(usethis) + ui_done("Library {ui_value('usethis')} attached.") + library(testthat) + ui_done("Library {ui_value('testthat')} attached.") + library(checkmate) + ui_done("Library {ui_value('checkmate')} attached.") + })) + } +} diff --git a/.gitignore b/.gitignore index 7c794aa..4613b50 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .httr-oauth .DS_Store .quarto +.Renviron diff --git a/DESCRIPTION b/DESCRIPTION index f676def..f406b18 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,21 +1,28 @@ Package: ubep.gpt Title: What the Package Does (One Line, Title Case) -Version: 0.0.0.9000 +Version: 0.0.0.9001 Authors@R: person("First", "Last", , "first.last@example.com", role = c("aut", "cre"), comment = c(ORCID = "YOUR-ORCID-ID")) Description: What the package does (one paragraph). License: MIT + file LICENSE +URL: https://github.com/UBESP-DCTV/ubep.gpt +BugReports: https://github.com/UBESP-DCTV/ubep.gpt/issues Imports: + glue, lifecycle, - tibble + openai, + purrr, + rlang, + tibble, + usethis Suggests: + checkmate, + lintr, spelling, testthat (>= 3.0.0) Config/testthat/edition: 3 Encoding: UTF-8 +Language: en-US Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.1 -Language: en-US -URL: https://github.com/UBESP-DCTV/ubep.gpt -BugReports: https://github.com/UBESP-DCTV/ubep.gpt/issues diff --git a/NAMESPACE b/NAMESPACE index 993f193..b40d9bd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,4 +1,14 @@ # Generated by roxygen2: do not edit by hand +export(compose_prompt) +export(compose_prompt_api) +export(compose_prompt_system) +export(compose_prompt_user) +export(get_completion_from_messages) +export(get_content) +export(get_tokens) +export(query_gpt) +export(query_gpt_on_column) importFrom(lifecycle,deprecated) +importFrom(rlang,":=") importFrom(tibble,tibble) diff --git a/NEWS.md b/NEWS.md index 116ac2d..acfe804 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,4 @@ # ubep.gpt (development version) +* Setup development environment. * Initial setup from CorradoLanera/gpt-template. diff --git a/R/compose_prompt.R b/R/compose_prompt.R new file mode 100644 index 0000000..e44d73c --- /dev/null +++ b/R/compose_prompt.R @@ -0,0 +1,128 @@ +#' Create a prompt to ChatGPT +#' +#' Questa funzione è un semplice wrapper per comporre un buon prompt per +#' ChatGPT. L'output non è altro che la giustapposizione su righe separate delle +#' varie componenti (con il testo addizionale racchiuso tra i delimitatori in +#' fondo al prompt). Dunque il suo utilizzo è più che altro focalizzato è utile +#' per ricordare e prendere l'abitudine di inserire le componenti utili per un +#' buon prompt. +#' +#' @param role (chr) The role that ChatGPT should play +#' @param context (chr) The context behind the task required +#' @param task (chr) The tasks ChatGPT should assess +#' @param instructions (chr) Description of steps ChatGPT should follow +#' @param output (chr) The type/kind of output required +#' @param style (chr) The style ChatGPT should use in the output +#' @param examples (chr) Some examples of correct output +#' @param text (chr) Additional text to embed in the prompt +#' @param delimiter (chr) delimiters for the `text` to embed, a sequence of +#' three identical symbols is suggested +#' +#' @return (chr) the glue of all the prompts components +#' @export +#' +#' @examples +#' if (FALSE) { +#' compose_prompt( +#' role = "Sei l'assistente di un docente universitario.", +#' context = " +#' Tu e lui state preparando un workshop sull'utilizzo di ChatGPT +#' per biostatisitci ed epidemiologi.", +#' task = " +#' Il tuo compito è trovare cosa dire per spiegare cosa sia una +#' chat di ChatGPT agli studenti, considerando che potrebbe +#' esserci qualcuno che non ne ha mai sentito parlare (e segue +#' il worksho incuriosito dal titolo o dagli amici).", +#' output = " +#' Riporta un potenziale dialogo tra il docente e gli studenti +#' che assolva ed esemplifichi lo scopo descritto.", +#' style = "Usa un tono amichevole, colloquiale, ma preciso." +#' ) +#' } +compose_prompt <- function( + role = "", context = "", task = "", instructions = "", output = "", + style = "", examples = "", text = "", + delimiter = if (text == "") "" else '""""' +) { + msg_sys <- compose_prompt_system(role, context) + msg_usr <- compose_prompt_user( + task, instructions, output, style, examples, text, delimiter + ) + glue::glue( + " + {msg_sys} + {msg_usr} + " + ) +} + +#' Compose the ChatGPT System prompt +#' +#' @describeIn compose_prompt +#' +#' @return (chr) The complete system prompt +#' @export +#' @examples +#' if (FALSE) { +#' msg_sys <- compose_prompt_system( +#' role = "Sei l'assistente di un docente universitario.", +#' context = " +#' Tu e lui state preparando un workshop sull'utilizzo di ChatGPT +#' per biostatisitci ed epidemiologi." +#' ) +#' } +compose_prompt_system <- function(role = "", context = "") { + glue::glue(" + {role} + {context} + ") +} + +#' Compose the ChatGPT User prompt +#' +#' @describeIn compose_prompt +#' +#' @return (chr) The complete user prompt +#' @export +#' @examples +#' if (FALSE) { +#' msg_usr <- compose_prompt_user( +#' task = " +#' Il tuo compito è trovare cosa dire per spiegare cosa sia una +#' chat di ChatGPT agli studenti, considerando che potrebbe +#' esserci qualcuno che non ne ha mai sentito parlare (e segue +#' il worksho incuriosito dal titolo o dagli amici).", +#' output = " +#' Riporta un potenziale dialogo tra il docente e gli studenti +#' che assolva ed esemplifichi lo scopo descritto.", +#' style = "Usa un tono amichevole, colloquiale, ma preciso." +#' ) +#' } +compose_prompt_user <- function( + task = "", instructions = "", output = "", style = "", examples = "", + text = "", delimiter = if (text == "") "" else '""""' +) { + glue::glue(" + {task} + {instructions} + {output} + {style} + {examples} + + {delimiter} + {text} + {delimiter} + ") +} + + +create_usr_data_prompter <- function( + task = "", instructions = "", output = "", style = "", examples = "" +) { + function(text) { + compose_prompt_user( + task = task, instructions = instructions, output = output, + style = style, examples = examples, text = text + ) + } +} diff --git a/R/compose_prompt_api.R b/R/compose_prompt_api.R new file mode 100644 index 0000000..15499dc --- /dev/null +++ b/R/compose_prompt_api.R @@ -0,0 +1,69 @@ +#' Create a prompt to OpenAI API +#' +#' Questa funzione è un semplice wrapper per comporre un prompt per le +#' API OpenAI a ChatGPT. Per la sua semplicità, per lo più didattica, +#' non considera alternanze successive di prompt nella chat ma solo +#' l'impostazione iniziale del sistema e il primo messaggio dell'utente. +#' +#' @details In genere, una conversazione è formattata con un messaggio +#' di sistema, seguito da messaggi alternati dell'utente e +#' dell'assistente. +#' +#' Il messaggio di sistema consente di impostare il comportamento +#' dell'assistente. Ad esempio, è possibile modificare la personalità +#' dell'assistente o fornire istruzioni specifiche sul comportamento da +#' tenere durante la conversazione. Tuttavia, il messaggio di sistema è +#' facoltativo e il comportamento del modello senza un messaggio di +#' sistema sarà probabilmente simile a quello di un messaggio generico +#' come "Sei un assistente utile". +#' +#' I messaggi dell'utente forniscono richieste o commenti a cui +#' l'assistente deve rispondere. I messaggi dell'assistente memorizzano +#' le risposte precedenti dell'assistente, ma possono anche essere +#' scritti dall'utente per fornire esempi del comportamento desiderato. +#' +#' +#' @param sys_msg (chr) messaggio da usare per impostare il sistema +#' @param usr_msg (chr) messaggio da usare come richiesta al sistema +#' passata dall'utente +#' +#' @return (chr) una lista di due lista, la prima con il messaggio da +#' usare per il prompt di impostazione del sistema di assistenza delle +#' API, la seconda con il prompt di richiesta dell'utente. +#' @export +#' +#' @examples +#' msg_sys <- compose_prompt_system( +#' role = "Sei l'assistente di un docente universitario.", +#' context = " +#' Tu e lui state preparando un workshop sull'utilizzo di ChatGPT +#' per biostatisitci ed epidemiologi." +#' ) +#' +#' msg_usr <- compose_prompt_user( +#' task = " +#' Il tuo compito è trovare cosa dire per spiegare cosa sia una +#' chat di ChatGPT agli studenti, considerando che potrebbe +#' esserci qualcuno che non ne ha mai sentito parlare (e segue +#' il worksho incuriosito dal titolo o dagli amici).", +#' output = " +#' Riporta un potenziale dialogo tra il docente e gli studenti +#' che assolva ed esemplifichi lo scopo descritto.", +#' style = "Usa un tono amichevole, colloquiale, ma preciso." +#' ) +#' +#' compose_prompt_api(msg_sys, msg_usr) +#' +#' +compose_prompt_api <- function(sys_msg, usr_msg) { + list( + list( + role = "system", + content = sys_msg + ), + list( + role = "user", + content = usr_msg + ) + ) +} diff --git a/R/get_completion_from_messages.R b/R/get_completion_from_messages.R new file mode 100644 index 0000000..504bd30 --- /dev/null +++ b/R/get_completion_from_messages.R @@ -0,0 +1,130 @@ +#' Get completion from chat messages +#' +#' @param messages (list) in the following format: `⁠list(list("role" = +#' "user", "content" = "Hey! How old are you?")` (see: +#' https://platform.openai.com/docs/api-reference/chat/create#chat/create-model) +#' @param model (chr, default = "gpt-3.5-turbo") a length one character +#' vector indicating the model to use (see: +#' ) +#' @param temperature (dbl, default = 0) a value between 0 (most +#' deterministic answer) and 2 (more random). (see: +#' https://platform.openai.com/docs/api-reference/chat/create#chat/create-temperature) +#' @param max_tokens (dbl, default = 500) a value greater than 0. The +#' maximum number of tokens to generate in the chat completion. (see: +#' https://platform.openai.com/docs/api-reference/chat/create#chat/create-max_tokens) +#' +#' @details For argument description, please refer to the [official +#' documentation](https://platform.openai.com/docs/api-reference/chat/create). +#' +#' Lower values for temperature result in more consistent outputs, +#' while higher values generate more diverse and creative results. +#' Select a temperature value based on the desired trade-off between +#' coherence and creativity for your specific application. Setting +#' temperature to 0 will make the outputs mostly deterministic, but a +#' small amount of variability will remain. +#' +#' @return (list) of two element: `content`, which contains the chr +#' vector of the response, and `tokens`, which is a list of number of +#' tokens used for the request (`prompt_tokens`), answer +#' (`completion_tokens`), and overall (`total_tokens`, the sum of the +#' other two) +#' @export +#' +#' @examples +#' if (FALSE) { +#' prompt <- list( +#' list( +#' role = "system", +#' content = "you are an assistant who responds succinctly" +#' ), +#' list( +#' role = "user", +#' content = "Return the text: 'Hello world'." +#' ) +#' ) +#' res <- get_completion_from_messages(prompt) +#' answer <- get_content(res) # "Hello world." +#' token_used <- get_tokens(res) # 30 +#' } +#' +#' if (FALSE) { +#' msg_sys <- compose_prompt_system( +#' role = "Sei l'assistente di un docente universitario.", +#' context = " +#' Tu e lui state preparando un workshop sull'utilizzo di ChatGPT +#' per biostatisitci ed epidemiologi.", +#' ) +#' +#' msg_usr <- compose_prompt_user( +#' task = " +#' Il tuo compito è trovare cosa dire per spiegare cosa sia una +#' chat di ChatGPT agli studenti, considerando che potrebbe +#' esserci qualcuno che non ne ha mai sentito parlare (e segue +#' il worksho incuriosito dal titolo o dagli amici).", +#' output = " +#' Riporta un potenziale dialogo tra il docente e gli studenti +#' che assolva ed esemplifichi lo scopo descritto.", +#' style = "Usa un tono amichevole, colloquiale, ma preciso." +#' ) +#' +#' prompt <- compose_prompt_api(msg_sys, msg_usr) +#' res <- get_completion_from_messages(prompt, "4-turbo") +#' answer <- get_content(res) +#' token_used <- get_tokens(res) # 957 +#' } +get_completion_from_messages <- function( + messages, + model = c("gpt-3.5-turbo", "gpt-4-turbo"), + temperature = 0, + max_tokens = 1000 +) { + + model <- match.arg(model) + model <- switch(model, + "gpt-3.5-turbo" = "gpt-3.5-turbo", + "gpt-4-turbo" = "gpt-4-1106-preview" + ) + + res <- openai::create_chat_completion( + model = model, + messages = messages, + temperature = temperature, + max_tokens = max_tokens, + ) + + list( + content = res[["choices"]][["message.content"]], + tokens = res[["usage"]] + ) +} + + +#' Get content of a chat completion +#' +#' @param completion the output of a `get_completion_from_messages` call +#' @describeIn get_completion_from_messages +#' +#' @return (chr) the output message returned by the assistant +#' @export +get_content <- function(completion) { + completion[["content"]] +} + +#' Get the number of token of a chat completion +#' +#' @param completion the number of tokens used for output of a +#' `get_completion_from_messages` call +#' @param what (chr) one of "total" (default), "prompt", or "completion" +#' @describeIn get_completion_from_messages +#' +#' @return (int) number of token used in completion for prompt or completion part, or overall (total) +#' @export +get_tokens <- function( + completion, + what = c("total", "prompt", "completion") +) { + what <- match.arg(what) + sel <- paste0(what, "_tokens") + + completion[["tokens"]][[sel]] +} diff --git a/R/query_gpt.R b/R/query_gpt.R new file mode 100644 index 0000000..2080dbc --- /dev/null +++ b/R/query_gpt.R @@ -0,0 +1,209 @@ +#' Query the GPT model +#' +#' @param prompt (chr) the prompt to use +#' @param model (chr) the model to use +#' @param quiet (lgl) whether to print information +#' @param max_try (int) the maximum number of tries +#' @param temperature (dbl) the temperature to use +#' @param max_tokens (dbl) the maximum number of tokens +#' +#' @return (list) the result of the query +#' @export +#' +#' @examples +#' if (FALSE) { +#' prompt <- compose_prompt_api( +#' sys_msg = compose_prompt_system( +#' role = "Sei l'assistente di un docente universitario.", +#' context = " +#' Tu e lui state preparando un workshop sull'utilizzo di ChatGPT +#' per biostatisitci ed epidemiologi." +#' ), +#' usr_msg = compose_prompt_user( +#' task = " +#' Il tuo compito è trovare cosa dire per spiegare cosa sia una +#' chat di ChatGPT agli studenti, considerando che potrebbe +#' esserci qualcuno che non ne ha mai sentito parlare (e segue +#' il worksho incuriosito dal titolo o dagli amici).", +#' output = " +#' Riporta un potenziale dialogo tra il docente e gli studenti +#' che assolva ed esemplifichi lo scopo descritto.", +#' style = "Usa un tono amichevole, colloquiale, ma preciso." +#' ) +#' ) +#' res <- query_gpt(prompt) +#' get_content(res) +#' get_tokens(res) +#' } +query_gpt <- function( + prompt, + model = c("gpt-3.5-turbo", "gpt-4-turbo"), + quiet = TRUE, + max_try = 10, + temperature = 0, + max_tokens = 1000 +) { + model <- match.arg(model) + done <- FALSE + tries <- 0L + while (!done && tries <= max_try) { + tries[[1]] <- tries[[1]] + 1L + if (tries > 1 && !quiet) { + usethis::ui_info("Error: {res}.") + usethis::ui_info("Try: {tries}...") + Sys.sleep(0.2 * 2^tries) + } + res <- tryCatch({ + aux <- prompt |> + get_completion_from_messages( + model = model, + temperature = temperature, + max_tokens = max_tokens + ) + done <- TRUE + aux + }, error = function(e) e) + } + + if (tries > max_try) { + usethis::ui_info("Max unsucessfully tries ({tries}) reached.") + usethis::ui_stop("Last error: {res}") + } + + if (!quiet) { + usethis::ui_info("Tries: {tries}.") + usethis::ui_info("Prompt token used: {get_tokens(res, 'prompt')}.") + usethis::ui_info("Response token used: {get_tokens(res, 'completion')}.") + usethis::ui_info("Total token used: {get_tokens(res)}.") + } + res +} + + + +#' Compose the ChatGPT System prompt +#' +#' @param db (data.frame) the data to use +#' @param text_column (chr) the name of the column containing the text +#' data +#' @param role (chr) the role of the assistant in the context +#' @param context (chr) the context of the assistant in the context +#' @param task (chr) the task to perform +#' @param instructions (chr) the instructions to follow +#' @param output (chr) the output required +#' @param style (chr) the style to use in the output +#' @param examples (chr) some examples of correct output +#' @param model (chr, default = "gpt-3.5-turbo") the model to use +#' @param quiet (lgl, default = TRUE) whether to print information +#' @param max_try (int, default = 10) the maximum number of tries +#' @param temperature (dbl, default = 0) the temperature to use +#' @param max_tokens (dbl, default = 1000) the maximum number of tokens +#' @param include_source_text (lgl, default = TRUE) whether to include +#' the source text +#' @param simplify (lgl, default = TRUE) whether to simplify the output +#' +#' @return (tibble) the result of the query +#' +#' @importFrom rlang := +#' +#' @export +#' +#' @examples +#' if (FALSE) { +#' +#' db <- tibble( +#' commenti = c( +#' "Che barba, che noia!", +#' "Un po' noioso, ma interessante", +#' "Che bello, mi è piaciuto molto!" +#' ) +#' ) +#' +#' role <- "Sei l'assistente di un docente universitario." +#' context <- "State analizzando i commenti degli studenti dell'ultimo corso." +#' task <- "Il tuo compito è capire se sono soddisfatti del corso." +#' instructions <- "Analizza i commenti e decidi se sono soddisfatti o meno." +#' output <- "Riporta 'soddisfatto' o 'insoddisfatto'." +#' style <- "Non aggiungere nessun commento, restituisci solo ed +#' esclusivamente la classificazione." +#' examples <- " +#' commento_1: 'Mi è piaciuto molto il corso; davvero interessante.' +#' classificazione_1: 'soddisfatto' +#' commento_2: 'Non mi è piaciuto per niente; una noia mortale' +#' classificazione_2: 'insoddisfatto' +#' " +#' res <- db |> +#' query_gpt_on_column( +#' "commenti", +#' role = role, +#' context = context, +#' task = task, +#' instructions = instructions, +#' output = output, +#' style = style, +#' examples = examples +#' ) +#' res +#' } +query_gpt_on_column <- function( + db, + text_column, + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples, + model = c("gpt-3.5-turbo", "gpt-4-turbo"), + quiet = TRUE, + max_try = 10, + temperature = 0, + max_tokens = 1000, + include_source_text = TRUE, + simplify = TRUE +) { + model <- match.arg(model) + + sys_prompt <- compose_prompt_system( + role = role, + context = context + ) + + usr_data_prompter <- create_usr_data_prompter( + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples + ) + + gpt_answers <- db[[text_column]] |> + purrr::map(\(txt) { + usr_prompt <- usr_data_prompter(txt) + prompt <- compose_prompt_api(sys_prompt, usr_prompt) + query_gpt( + prompt = prompt, + model = model, + quiet = quiet, + max_try = max_try, + temperature = temperature, + max_tokens = max_tokens + ) + }) + + answers <- if (simplify) { + purrr::map_chr(gpt_answers, get_content) + } else { + gpt_answers + } + + if (include_source_text) { + tibble::tibble( + {{text_column}} := db[[text_column]], + gpt_res = answers + ) + } else { + tibble::tibble(gpt_res = answers) + } +} diff --git a/R/zzz.R b/R/zzz.R new file mode 100644 index 0000000..be916ed --- /dev/null +++ b/R/zzz.R @@ -0,0 +1,3 @@ +.onAttach <- function(...) { + +} diff --git a/README.Rmd b/README.Rmd index 38b65b3..75bea9b 100644 --- a/README.Rmd +++ b/README.Rmd @@ -37,25 +37,44 @@ This is a basic example which shows you how to solve a common problem: ```{r example} library(ubep.gpt) -## basic example code -``` - -What is special about using `README.Rmd` instead of just `README.md`? You can include R chunks like so: - -```{r cars} -summary(cars) -``` -You'll still need to render `README.Rmd` regularly, to keep `README.md` up-to-date. `devtools::build_readme()` is handy for this. - -You can also embed plots, for example: +db <- data.frame( + commenti = c( + "Che barba, che noia!", + "Un po' noioso, ma interessante", + "Che bello, mi è piaciuto molto!" + ) +) -```{r pressure, echo = FALSE} -plot(pressure) +role <- "Sei l'assistente di un docente universitario." +context <- "State analizzando i commenti degli studenti dell'ultimo corso." + +task <- "Il tuo compito è capire se sono soddisfatti del corso." +instructions <- "Analizza i commenti e decidi se sono soddisfatti o meno." +output <- "Riporta 'soddisfatto' o 'insoddisfatto', in caso di dubbio o impossibilità riporta 'NA'." +style <- "Non aggiungere nessun commento, restituisci solo ed esclusivamente una delle classificazioni possibile." + +examples <- " +commento_1: 'Mi è piaciuto molto il corso; davvero interessante.' +classificazione_1: 'soddisfatto' +commento_2: 'Non mi è piaciuto per niente; una noia mortale' +classificazione_2: 'insoddisfatto' +" + +res <- db |> + query_gpt_on_column( + "commenti", + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples + ) +res ``` -In that case, don't forget to commit and push the resulting figure files, so they display on GitHub and CRAN. - ## Code of Conduct Please note that the ubep.gpt project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b63f670 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ + + + +# ubep.gpt + + + +[![Lifecycle: +experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) +[![Codecov test +coverage](https://codecov.io/gh/UBESP-DCTV/ubep.gpt/branch/main/graph/badge.svg)](https://app.codecov.io/gh/UBESP-DCTV/ubep.gpt?branch=main) +[![R-CMD-check](https://github.com/UBESP-DCTV/ubep.gpt/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/UBESP-DCTV/ubep.gpt/actions/workflows/R-CMD-check.yaml) + + +The goal of ubep.gpt is to … + +## Installation + +You can install the development version of ubep.gpt like so: + +``` r +# FILL THIS IN! HOW CAN PEOPLE INSTALL YOUR DEV PACKAGE? +``` + +## Example + +This is a basic example which shows you how to solve a common problem: + +``` r +library(ubep.gpt) + +db <- data.frame( + commenti = c( + "Che barba, che noia!", + "Un po' noioso, ma interessante", + "Che bello, mi è piaciuto molto!" + ) +) + +role <- "Sei l'assistente di un docente universitario." +context <- "State analizzando i commenti degli studenti dell'ultimo corso." + +task <- "Il tuo compito è capire se sono soddisfatti del corso." +instructions <- "Analizza i commenti e decidi se sono soddisfatti o meno." +output <- "Riporta 'soddisfatto' o 'insoddisfatto', in caso di dubbio o impossibilità riporta 'NA'." +style <- "Non aggiungere nessun commento, restituisci solo ed esclusivamente una delle classificazioni possibile." + +examples <- " +commento_1: 'Mi è piaciuto molto il corso; davvero interessante.' +classificazione_1: 'soddisfatto' +commento_2: 'Non mi è piaciuto per niente; una noia mortale' +classificazione_2: 'insoddisfatto' +" + +res <- db |> + query_gpt_on_column( + "commenti", + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples + ) +#> • POST the query +#> ✔ POST the query +#> • Parse the response +#> ✔ Parse the response +#> • Check whether request failed and return parsed +#> ✔ Check whether request failed and return parsed +#> • POST the query +#> ✔ POST the query +#> • Parse the response +#> ✔ Parse the response +#> • Check whether request failed and return parsed +#> ✔ Check whether request failed and return parsed +#> • POST the query +#> ✔ POST the query +#> • Parse the response +#> ✔ Parse the response +#> • Check whether request failed and return parsed +#> ✔ Check whether request failed and return parsed +res +#> # A tibble: 3 × 2 +#> commenti gpt_res +#> +#> 1 Che barba, che noia! insoddisfatto +#> 2 Un po' noioso, ma interessante insoddisfatto +#> 3 Che bello, mi è piaciuto molto! soddisfatto +``` + +## Code of Conduct + +Please note that the ubep.gpt project is released with a [Contributor +Code of +Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). +By contributing to this project, you agree to abide by its terms. diff --git a/dev/00-setup.R b/dev/00-setup.R index 0ba9e30..0d1160d 100644 --- a/dev/00-setup.R +++ b/dev/00-setup.R @@ -7,7 +7,6 @@ usethis::use_lifecycle() usethis::use_lifecycle_badge("experimental") usethis::use_mit_license("CorradoLanera") usethis::use_code_of_conduct("Corrado.Lanera@ubep.unipd.it") -usethis::use_tidy_description() usethis::use_spell_check() usethis::use_git() usethis::git_vaccinate() @@ -15,3 +14,8 @@ usethis::use_github(organisation = "UBESP-DCTV", protocol = "ssh") usethis::use_coverage() usethis::use_github_action("test-coverage", badge = TRUE) usethis::use_github_action("check-standard", badge = TRUE) + + +usethis::use_package("checkmate", type = "Suggests") +usethis::use_package("lintr", type = "Suggests") +usethis::use_tidy_description() diff --git a/dev/01-check.R b/dev/01-check.R new file mode 100644 index 0000000..a70232d --- /dev/null +++ b/dev/01-check.R @@ -0,0 +1,10 @@ +spelling::spell_check_package() +## spelling::update_wordlist() +lintr::lint_package() + +## CTRL + SHIFT + D: update project documentation +## CTRL + SHIFT + T: run all project's tests +## CTRL + SHIFT + E: run all CRAN tests + +devtools::build_readme() +usethis::use_version() diff --git a/dev/01-dev.R b/dev/01-dev.R new file mode 100644 index 0000000..747b24f --- /dev/null +++ b/dev/01-dev.R @@ -0,0 +1,18 @@ +# packages -------------------------------------------------------- +usethis::use_package("glue") +usethis::use_package("openai") +usethis::use_package("usethis") +usethis::use_package("purrr") +usethis::use_package("tibble") +usethis::use_package("rlang") + +usethis::use_tidy_description() + + +# functions ------------------------------------------------------- + +usethis::use_r("zzz") +usethis::use_r("query_gpt") + +# tests ----------------------------------------------------------- +usethis::use_test("compose_prompt") diff --git a/inst/WORDLIST b/inst/WORDLIST index 8ea2b8a..249cf60 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,4 +1,123 @@ +CMD +ChatGPT +Codecov CorradoLanera +Dunque +Il +L'output Lifecycle +ORCID +OpenAI +Questa +Sei +Tuttavia +addizionale +al +alternanze +alternati +altro +anche +api +assistente +assistenza +buon +che +chr +commenti +componenti +comporre +comportamento +consente +considera +conversazione +cui +da +dall'utente +del +delimitatori +dell'assistente +dell'utente +delle +desiderato +deve +di +didattica +durante +esempi +esempio +essere +facoltativo +focalizzato +fondo +formattata +fornire +forniscono +funzione +genere +generico +giustapposizione gpt +https +il +impostare +impostazione +iniziale +inserire +istruzioni +l'abitudine +l'assistente +l'impostazione +le +lgl +lista +memorizzano +messaggi +messaggio +modello +modificare +nella +openai +passata +personalità +più +possibile +possono +precedenti +prendere +prima +primo +probabilmente +quello +racchiuso +richiesta +richieste +ricordare +righe +rispondere +risposte +sarà +scritti +seconda +seguito +semplice +semplicità +senza +sistema +specifiche +su +sua +sul +suo +tenere +testo +tibble +tra ubep +un +una +usare +utile +utili +utilizzo +varie +è diff --git a/man/compose_prompt.Rd b/man/compose_prompt.Rd new file mode 100644 index 0000000..bec71e9 --- /dev/null +++ b/man/compose_prompt.Rd @@ -0,0 +1,114 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/compose_prompt.R +\name{compose_prompt} +\alias{compose_prompt} +\alias{compose_prompt_system} +\alias{compose_prompt_user} +\title{Create a prompt to ChatGPT} +\usage{ +compose_prompt( + role = "", + context = "", + task = "", + instructions = "", + output = "", + style = "", + examples = "", + text = "", + delimiter = if (text == "") "" else "\\"\\"\\"\\"" +) + +compose_prompt_system(role = "", context = "") + +compose_prompt_user( + task = "", + instructions = "", + output = "", + style = "", + examples = "", + text = "", + delimiter = if (text == "") "" else "\\"\\"\\"\\"" +) +} +\arguments{ +\item{role}{(chr) The role that ChatGPT should play} + +\item{context}{(chr) The context behind the task required} + +\item{task}{(chr) The tasks ChatGPT should assess} + +\item{instructions}{(chr) Description of steps ChatGPT should follow} + +\item{output}{(chr) The type/kind of output required} + +\item{style}{(chr) The style ChatGPT should use in the output} + +\item{examples}{(chr) Some examples of correct output} + +\item{text}{(chr) Additional text to embed in the prompt} + +\item{delimiter}{(chr) delimiters for the \code{text} to embed, a sequence of +three identical symbols is suggested} +} +\value{ +(chr) the glue of all the prompts components + +(chr) The complete system prompt + +(chr) The complete user prompt +} +\description{ +Questa funzione è un semplice wrapper per comporre un buon prompt per +ChatGPT. L'output non è altro che la giustapposizione su righe separate delle +varie componenti (con il testo addizionale racchiuso tra i delimitatori in +fondo al prompt). Dunque il suo utilizzo è più che altro focalizzato è utile +per ricordare e prendere l'abitudine di inserire le componenti utili per un +buon prompt. +} +\section{Functions}{ +\itemize{ +\item \code{compose_prompt_system()}: + +\item \code{compose_prompt_user()}: + +}} +\examples{ +if (FALSE) { + compose_prompt( + role = "Sei l'assistente di un docente universitario.", + context = " + Tu e lui state preparando un workshop sull'utilizzo di ChatGPT + per biostatisitci ed epidemiologi.", + task = " + Il tuo compito è trovare cosa dire per spiegare cosa sia una + chat di ChatGPT agli studenti, considerando che potrebbe + esserci qualcuno che non ne ha mai sentito parlare (e segue + il worksho incuriosito dal titolo o dagli amici).", + output = " + Riporta un potenziale dialogo tra il docente e gli studenti + che assolva ed esemplifichi lo scopo descritto.", + style = "Usa un tono amichevole, colloquiale, ma preciso." + ) +} +if (FALSE) { + msg_sys <- compose_prompt_system( + role = "Sei l'assistente di un docente universitario.", + context = " + Tu e lui state preparando un workshop sull'utilizzo di ChatGPT + per biostatisitci ed epidemiologi." + ) +} +if (FALSE) { + msg_usr <- compose_prompt_user( + task = " + Il tuo compito è trovare cosa dire per spiegare cosa sia una + chat di ChatGPT agli studenti, considerando che potrebbe + esserci qualcuno che non ne ha mai sentito parlare (e segue + il worksho incuriosito dal titolo o dagli amici).", + output = " + Riporta un potenziale dialogo tra il docente e gli studenti + che assolva ed esemplifichi lo scopo descritto.", + style = "Usa un tono amichevole, colloquiale, ma preciso." + ) +} +} diff --git a/man/compose_prompt_api.Rd b/man/compose_prompt_api.Rd new file mode 100644 index 0000000..73f443d --- /dev/null +++ b/man/compose_prompt_api.Rd @@ -0,0 +1,67 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/compose_prompt_api.R +\name{compose_prompt_api} +\alias{compose_prompt_api} +\title{Create a prompt to OpenAI API} +\usage{ +compose_prompt_api(sys_msg, usr_msg) +} +\arguments{ +\item{sys_msg}{(chr) messaggio da usare per impostare il sistema} + +\item{usr_msg}{(chr) messaggio da usare come richiesta al sistema +passata dall'utente} +} +\value{ +(chr) una lista di due lista, la prima con il messaggio da +usare per il prompt di impostazione del sistema di assistenza delle +API, la seconda con il prompt di richiesta dell'utente. +} +\description{ +Questa funzione è un semplice wrapper per comporre un prompt per le +API OpenAI a ChatGPT. Per la sua semplicità, per lo più didattica, +non considera alternanze successive di prompt nella chat ma solo +l'impostazione iniziale del sistema e il primo messaggio dell'utente. +} +\details{ +In genere, una conversazione è formattata con un messaggio +di sistema, seguito da messaggi alternati dell'utente e +dell'assistente. + +Il messaggio di sistema consente di impostare il comportamento +dell'assistente. Ad esempio, è possibile modificare la personalità +dell'assistente o fornire istruzioni specifiche sul comportamento da +tenere durante la conversazione. Tuttavia, il messaggio di sistema è +facoltativo e il comportamento del modello senza un messaggio di +sistema sarà probabilmente simile a quello di un messaggio generico +come "Sei un assistente utile". + +I messaggi dell'utente forniscono richieste o commenti a cui +l'assistente deve rispondere. I messaggi dell'assistente memorizzano +le risposte precedenti dell'assistente, ma possono anche essere +scritti dall'utente per fornire esempi del comportamento desiderato. +} +\examples{ +msg_sys <- compose_prompt_system( + role = "Sei l'assistente di un docente universitario.", + context = " + Tu e lui state preparando un workshop sull'utilizzo di ChatGPT + per biostatisitci ed epidemiologi." + ) + +msg_usr <- compose_prompt_user( + task = " + Il tuo compito è trovare cosa dire per spiegare cosa sia una + chat di ChatGPT agli studenti, considerando che potrebbe + esserci qualcuno che non ne ha mai sentito parlare (e segue + il worksho incuriosito dal titolo o dagli amici).", + output = " + Riporta un potenziale dialogo tra il docente e gli studenti + che assolva ed esemplifichi lo scopo descritto.", + style = "Usa un tono amichevole, colloquiale, ma preciso." +) + +compose_prompt_api(msg_sys, msg_usr) + + +} diff --git a/man/get_completion_from_messages.Rd b/man/get_completion_from_messages.Rd new file mode 100644 index 0000000..2ea8603 --- /dev/null +++ b/man/get_completion_from_messages.Rd @@ -0,0 +1,118 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_completion_from_messages.R +\name{get_completion_from_messages} +\alias{get_completion_from_messages} +\alias{get_content} +\alias{get_tokens} +\title{Get completion from chat messages} +\usage{ +get_completion_from_messages( + messages, + model = c("gpt-3.5-turbo", "gpt-4-turbo"), + temperature = 0, + max_tokens = 1000 +) + +get_content(completion) + +get_tokens(completion, what = c("total", "prompt", "completion")) +} +\arguments{ +\item{messages}{(list) in the following format: \verb{⁠list(list("role" = "user", "content" = "Hey! How old are you?")} (see: +https://platform.openai.com/docs/api-reference/chat/create#chat/create-model)} + +\item{model}{(chr, default = "gpt-3.5-turbo") a length one character +vector indicating the model to use (see: +\url{https://platform.openai.com/docs/models/continuous-model-upgrades})} + +\item{temperature}{(dbl, default = 0) a value between 0 (most +deterministic answer) and 2 (more random). (see: +https://platform.openai.com/docs/api-reference/chat/create#chat/create-temperature)} + +\item{max_tokens}{(dbl, default = 500) a value greater than 0. The +maximum number of tokens to generate in the chat completion. (see: +https://platform.openai.com/docs/api-reference/chat/create#chat/create-max_tokens)} + +\item{completion}{the number of tokens used for output of a +\code{get_completion_from_messages} call} + +\item{what}{(chr) one of "total" (default), "prompt", or "completion"} +} +\value{ +(list) of two element: \code{content}, which contains the chr +vector of the response, and \code{tokens}, which is a list of number of +tokens used for the request (\code{prompt_tokens}), answer +(\code{completion_tokens}), and overall (\code{total_tokens}, the sum of the +other two) + +(chr) the output message returned by the assistant + +(int) number of token used in completion for prompt or completion part, or overall (total) +} +\description{ +Get completion from chat messages + +Get content of a chat completion + +Get the number of token of a chat completion +} +\details{ +For argument description, please refer to the \href{https://platform.openai.com/docs/api-reference/chat/create}{official documentation}. + +Lower values for temperature result in more consistent outputs, +while higher values generate more diverse and creative results. +Select a temperature value based on the desired trade-off between +coherence and creativity for your specific application. Setting +temperature to 0 will make the outputs mostly deterministic, but a +small amount of variability will remain. +} +\section{Functions}{ +\itemize{ +\item \code{get_content()}: + +\item \code{get_tokens()}: + +}} +\examples{ +if (FALSE) { + prompt <- list( + list( + role = "system", + content = "you are an assistant who responds succinctly" + ), + list( + role = "user", + content = "Return the text: 'Hello world'." + ) + ) + res <- get_completion_from_messages(prompt) + answer <- get_content(res) # "Hello world." + token_used <- get_tokens(res) # 30 +} + +if (FALSE) { + msg_sys <- compose_prompt_system( + role = "Sei l'assistente di un docente universitario.", + context = " + Tu e lui state preparando un workshop sull'utilizzo di ChatGPT + per biostatisitci ed epidemiologi.", + ) + + msg_usr <- compose_prompt_user( + task = " + Il tuo compito è trovare cosa dire per spiegare cosa sia una + chat di ChatGPT agli studenti, considerando che potrebbe + esserci qualcuno che non ne ha mai sentito parlare (e segue + il worksho incuriosito dal titolo o dagli amici).", + output = " + Riporta un potenziale dialogo tra il docente e gli studenti + che assolva ed esemplifichi lo scopo descritto.", + style = "Usa un tono amichevole, colloquiale, ma preciso." + ) + + prompt <- compose_prompt_api(msg_sys, msg_usr) + res <- get_completion_from_messages(prompt, "4-turbo") + answer <- get_content(res) + token_used <- get_tokens(res) # 957 +} +} diff --git a/man/query_gpt.Rd b/man/query_gpt.Rd new file mode 100644 index 0000000..5c8a699 --- /dev/null +++ b/man/query_gpt.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/query_gpt.R +\name{query_gpt} +\alias{query_gpt} +\title{Query the GPT model} +\usage{ +query_gpt( + prompt, + model = c("gpt-3.5-turbo", "gpt-4-turbo"), + quiet = TRUE, + max_try = 10, + temperature = 0, + max_tokens = 1000 +) +} +\arguments{ +\item{prompt}{(chr) the prompt to use} + +\item{model}{(chr) the model to use} + +\item{quiet}{(lgl) whether to print information} + +\item{max_try}{(int) the maximum number of tries} + +\item{temperature}{(dbl) the temperature to use} + +\item{max_tokens}{(dbl) the maximum number of tokens} +} +\value{ +(list) the result of the query +} +\description{ +Query the GPT model +} +\examples{ +if (FALSE) { + prompt <- compose_prompt_api( + sys_msg = compose_prompt_system( + role = "Sei l'assistente di un docente universitario.", + context = " + Tu e lui state preparando un workshop sull'utilizzo di ChatGPT + per biostatisitci ed epidemiologi." + ), + usr_msg = compose_prompt_user( + task = " + Il tuo compito è trovare cosa dire per spiegare cosa sia una + chat di ChatGPT agli studenti, considerando che potrebbe + esserci qualcuno che non ne ha mai sentito parlare (e segue + il worksho incuriosito dal titolo o dagli amici).", + output = " + Riporta un potenziale dialogo tra il docente e gli studenti + che assolva ed esemplifichi lo scopo descritto.", + style = "Usa un tono amichevole, colloquiale, ma preciso." + ) + ) + res <- query_gpt(prompt) + get_content(res) + get_tokens(res) +} +} diff --git a/man/query_gpt_on_column.Rd b/man/query_gpt_on_column.Rd new file mode 100644 index 0000000..9a67f98 --- /dev/null +++ b/man/query_gpt_on_column.Rd @@ -0,0 +1,104 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/query_gpt.R +\name{query_gpt_on_column} +\alias{query_gpt_on_column} +\title{Compose the ChatGPT System prompt} +\usage{ +query_gpt_on_column( + db, + text_column, + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples, + model = c("gpt-3.5-turbo", "gpt-4-turbo"), + quiet = TRUE, + max_try = 10, + temperature = 0, + max_tokens = 1000, + include_source_text = TRUE, + simplify = TRUE +) +} +\arguments{ +\item{db}{(data.frame) the data to use} + +\item{text_column}{(chr) the name of the column containing the text +data} + +\item{role}{(chr) the role of the assistant in the context} + +\item{context}{(chr) the context of the assistant in the context} + +\item{task}{(chr) the task to perform} + +\item{instructions}{(chr) the instructions to follow} + +\item{output}{(chr) the output required} + +\item{style}{(chr) the style to use in the output} + +\item{examples}{(chr) some examples of correct output} + +\item{model}{(chr, default = "gpt-3.5-turbo") the model to use} + +\item{quiet}{(lgl, default = TRUE) whether to print information} + +\item{max_try}{(int, default = 10) the maximum number of tries} + +\item{temperature}{(dbl, default = 0) the temperature to use} + +\item{max_tokens}{(dbl, default = 1000) the maximum number of tokens} + +\item{include_source_text}{(lgl, default = TRUE) whether to include +the source text} + +\item{simplify}{(lgl, default = TRUE) whether to simplify the output} +} +\value{ +(tibble) the result of the query +} +\description{ +Compose the ChatGPT System prompt +} +\examples{ +if (FALSE) { + + db <- tibble( + commenti = c( + "Che barba, che noia!", + "Un po' noioso, ma interessante", + "Che bello, mi è piaciuto molto!" + ) + ) + + role <- "Sei l'assistente di un docente universitario." + context <- "State analizzando i commenti degli studenti dell'ultimo corso." + task <- "Il tuo compito è capire se sono soddisfatti del corso." + instructions <- "Analizza i commenti e decidi se sono soddisfatti o meno." + output <- "Riporta 'soddisfatto' o 'insoddisfatto'." + style <- "Non aggiungere nessun commento, restituisci solo ed + esclusivamente la classificazione." + examples <- " + commento_1: 'Mi è piaciuto molto il corso; davvero interessante.' + classificazione_1: 'soddisfatto' + commento_2: 'Non mi è piaciuto per niente; una noia mortale' + classificazione_2: 'insoddisfatto' + " + res <- db |> + query_gpt_on_column( + "commenti", + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples + ) + res +} +} diff --git a/man/ubep.gpt-package.Rd b/man/ubep.gpt-package.Rd new file mode 100644 index 0000000..13fb1f2 --- /dev/null +++ b/man/ubep.gpt-package.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ubep.gpt-package.R +\docType{package} +\name{ubep.gpt-package} +\alias{ubep.gpt} +\alias{ubep.gpt-package} +\title{ubep.gpt: What the Package Does (One Line, Title Case)} +\description{ +What the package does (one paragraph). +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/UBESP-DCTV/ubep.gpt} + \item Report bugs at \url{https://github.com/UBESP-DCTV/ubep.gpt/issues} +} + +} +\author{ +\strong{Maintainer}: First Last \email{first.last@example.com} (\href{https://orcid.org/YOUR-ORCID-ID}{ORCID}) + +} +\keyword{internal} diff --git a/tests/spelling.R b/tests/spelling.R index 6713838..d60e024 100644 --- a/tests/spelling.R +++ b/tests/spelling.R @@ -1,3 +1,7 @@ -if(requireNamespace('spelling', quietly = TRUE)) - spelling::spell_check_test(vignettes = TRUE, error = FALSE, - skip_on_cran = TRUE) +if (requireNamespace("spelling", quietly = TRUE)) { + spelling::spell_check_test( + vignettes = TRUE, + error = FALSE, + skip_on_cran = TRUE + ) +} diff --git a/tests/testthat/test-compose_prompt.R b/tests/testthat/test-compose_prompt.R new file mode 100644 index 0000000..d34edb4 --- /dev/null +++ b/tests/testthat/test-compose_prompt.R @@ -0,0 +1,138 @@ +test_that("compose_prompt_system works", { + # setup + role <- "role" + context <- "context" + + # execution + res <- compose_prompt_system(role = role, context = context) + + # expectation + expect_string(res) +}) + +test_that("compose_prompt_user works", { + # setup + task <- "task" + instructions <- "instructions" + output <- "output" + style <- "style" + examples <- "examples" + text <- "text" + + # execution + res <- compose_prompt_user( + task = task, instructions = instructions, output = output, + style = style, examples = examples, text = text + ) + + # expectation + expect_string(res) +}) + +test_that("create_usr_data_prompter works", { + # setup + task <- "task" + instructions <- "instructions" + output <- "output" + style <- "style" + examples <- "examples" + + # execution + res <- create_usr_data_prompter( + task = task, instructions = instructions, output = output, + style = style, examples = examples + ) + + # expectation + expect_function(res) +}) + +test_that("query_gpt_on_column works", { + skip_on_ci() + skip_on_cran() + + # setup + db <- tibble::tibble(commenti = c("commento1", "commento2")) + text_column <- "commenti" + role <- "role" + context <- "context" + task <- "task" + instructions <- "instructions" + output <- "output" + style <- "style" + examples <- "examples" + model <- "gpt-3.5-turbo" + quiet <- TRUE + max_try <- 10 + temperature <- 0 + max_tokens <- 1000 + include_source_text <- TRUE + simplify <- TRUE + + # execution + res <- query_gpt_on_column( + db = db, + text_column = text_column, + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples, + model = model, + quiet = quiet, + max_try = max_try, + temperature = temperature, + max_tokens = max_tokens, + include_source_text = include_source_text, + simplify = simplify + ) |> + suppressMessages() + + res_not_simplified <- query_gpt_on_column( + db = db, + text_column = text_column, + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples, + model = model, + quiet = quiet, + max_try = max_try, + temperature = temperature, + max_tokens = max_tokens, + include_source_text = include_source_text, + simplify = FALSE + ) |> + suppressMessages() + + res_without_source_text <- query_gpt_on_column( + db = db, + text_column = text_column, + role = role, + context = context, + task = task, + instructions = instructions, + output = output, + style = style, + examples = examples, + model = model, + quiet = quiet, + max_try = max_try, + temperature = temperature, + max_tokens = max_tokens, + include_source_text = FALSE, + simplify = simplify + ) |> + suppressMessages() + + # expectation + expect_tibble(res, ncols = 2, nrows = 2) + expect_tibble(res_not_simplified, ncols = 2, nrows = 2) + expect_tibble(res_without_source_text, ncols = 1, nrows = 2) +}) +