├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── vignettes ├── .gitignore ├── figs │ ├── key.png │ └── key-rs.png └── Usage.Rmd ├── .lintr ├── data └── token_limits.rda ├── man ├── figures │ └── logo.png ├── new_conversation.Rd ├── prompt_history.Rd ├── response_history.Rd ├── parse_response.Rd ├── token_limits.Rd ├── explain_code.Rd ├── test_function.Rd ├── log_init.Rd ├── tutorialise_addin.Rd ├── annotate_code.Rd ├── improve_addin.Rd ├── document_code.Rd ├── list_models.Rd ├── login.Rd ├── estimate_token.Rd ├── askgpt.Rd ├── chat_api.Rd └── completions_api.Rd ├── tests ├── testthat │ ├── responses │ │ ├── gpt-3.5-turbo_isthisatest.rds │ │ ├── text-davinci-003_isthisatest.rds │ │ ├── gpt-3.5-turbo_whatdoesthisdo1+1.rds │ │ ├── gpt-3.5-turbo_ExplainthefollowingRcodet.rds │ │ ├── gpt-3.5-turbo_Writeatestthatunittestfor.rds │ │ ├── text-davinci-003_AddinlinecommentstothisRc.rds │ │ └── text-davinci-003_DocumentthisRfunctionusin.rds │ ├── _snaps │ │ └── askgpt.md │ ├── test-tutorial-addin.R │ ├── test-utils.R │ ├── test-askgpt.R │ ├── test-code.R │ ├── test-auth.R │ └── setup.R ├── spelling.R └── testthat.R ├── .gitignore ├── R ├── misc.r ├── zzz.r ├── code_prompts.r ├── auth.r ├── askgpt.r ├── utils.r ├── shiny_addins.r └── api.r ├── .Rbuildignore ├── codecov.yml ├── data-raw └── token_limits.R ├── cran-comments.md ├── inst ├── WORDLIST └── rstudio │ └── addins.dcf ├── askgpt.Rproj ├── NAMESPACE ├── submit2cran.r ├── DESCRIPTION ├── NEWS.md ├── README.Rmd ├── README.md └── LICENSE.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | examples.mp4 4 | -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults(line_length_linter(120)) 2 | -------------------------------------------------------------------------------- /data/token_limits.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/data/token_limits.rda -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /vignettes/figs/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/vignettes/figs/key.png -------------------------------------------------------------------------------- /vignettes/figs/key-rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/vignettes/figs/key-rs.png -------------------------------------------------------------------------------- /tests/testthat/responses/gpt-3.5-turbo_isthisatest.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/tests/testthat/responses/gpt-3.5-turbo_isthisatest.rds -------------------------------------------------------------------------------- /tests/testthat/responses/text-davinci-003_isthisatest.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/tests/testthat/responses/text-davinci-003_isthisatest.rds -------------------------------------------------------------------------------- /tests/testthat/responses/gpt-3.5-turbo_whatdoesthisdo1+1.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/tests/testthat/responses/gpt-3.5-turbo_whatdoesthisdo1+1.rds -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .Renviron 6 | README_cache 7 | askgpt.Rproj 8 | inst/doc 9 | /doc/ 10 | /Meta/ 11 | asgpt_log.jsonl 12 | -------------------------------------------------------------------------------- /tests/testthat/responses/gpt-3.5-turbo_ExplainthefollowingRcodet.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/tests/testthat/responses/gpt-3.5-turbo_ExplainthefollowingRcodet.rds -------------------------------------------------------------------------------- /tests/testthat/responses/gpt-3.5-turbo_Writeatestthatunittestfor.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/tests/testthat/responses/gpt-3.5-turbo_Writeatestthatunittestfor.rds -------------------------------------------------------------------------------- /tests/spelling.R: -------------------------------------------------------------------------------- 1 | if (requireNamespace("spelling", quietly = TRUE)) 2 | spelling::spell_check_test(vignettes = TRUE, error = FALSE, 3 | skip_on_cran = TRUE) 4 | -------------------------------------------------------------------------------- /tests/testthat/responses/text-davinci-003_AddinlinecommentstothisRc.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/tests/testthat/responses/text-davinci-003_AddinlinecommentstothisRc.rds -------------------------------------------------------------------------------- /tests/testthat/responses/text-davinci-003_DocumentthisRfunctionusin.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JBGruber/askgpt/HEAD/tests/testthat/responses/text-davinci-003_DocumentthisRfunctionusin.rds -------------------------------------------------------------------------------- /R/misc.r: -------------------------------------------------------------------------------- 1 | #' Max tokens limits of the different models 2 | #' 3 | #' OpenAI's token limits for different models. 4 | #' 5 | #' @source 6 | "token_limits" 7 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^LICENSE\.md$ 5 | README_cache 6 | ^\.github$ 7 | ^\.lintr$ 8 | ^codecov\.yml$ 9 | ^doc$ 10 | ^Meta$ 11 | ^cran-comments\.md$ 12 | ^CRAN-SUBMISSION$ 13 | ^submit2cran\.r$ 14 | ^asgpt_log\.jsonl$ 15 | ^data-raw$ 16 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/askgpt.md: -------------------------------------------------------------------------------- 1 | # Ask questions 2 | 3 | Code 4 | askgpt("", progress = FALSE, callfun = mockcall) 5 | Message 6 | 7 | -- Answer ---------------------------------------------------------------------- 8 | test 9 | 10 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /tests/testthat/test-tutorial-addin.R: -------------------------------------------------------------------------------- 1 | test_that("tutorialise utils", { 2 | skip_on_cran() 3 | expect_length(httr2::with_mock(cache_response, make_request("what does this do", "1 + 1")), 1L) 4 | }) 5 | 6 | test_that("split prompt", { 7 | expect_length(split_prompt("This\nis\na\ntest", tok_max = 2), 4L) 8 | }) 9 | -------------------------------------------------------------------------------- /data-raw/token_limits.R: -------------------------------------------------------------------------------- 1 | ## code to prepare `token_limits` dataset 2 | token_limits <- data.frame( 3 | model = c("gpt-3.5-turbo", "gpt-3.5-turbo-0301", "gpt-4", "gpt-4-0314", "gpt-4-32k", "gpt-4-32k-0314"), 4 | limit = c(4096L, 4096L, 8192L, 8192L, 32768L, 32768L) 5 | ) 6 | usethis::use_data(token_limits, overwrite = TRUE) 7 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Submission 2 | 3 | This release fixes a bug made the package ignore user options in some cases and adds a new RStudio addin. 4 | 5 | ## Test environments 6 | 7 | - locally using `devtools::check(cran = TRUE)` 8 | - rhub::check_for_cran() 9 | 10 | ## R CMD check results 11 | 12 | 0 errors | 0 warnings | 0 note 13 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | Addin 2 | CMD 3 | ChatGPT 4 | Codecov 5 | GPT 6 | Lifecycle 7 | OPENAI 8 | OpenAI 9 | OpenAI's 10 | OpenAI’s 11 | RStudio 12 | addin 13 | api 14 | com 15 | davinci 16 | dir 17 | entrace 18 | env 19 | github 20 | httr 21 | io 22 | ist 23 | openai 24 | openai's 25 | rlang 26 | rstudio 27 | rstudioaddins 28 | tibble 29 | tutorialise 30 | -------------------------------------------------------------------------------- /man/new_conversation.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.r 3 | \name{new_conversation} 4 | \alias{new_conversation} 5 | \title{Start a new conversation} 6 | \usage{ 7 | new_conversation() 8 | } 9 | \value{ 10 | Does not return a value 11 | } 12 | \description{ 13 | Deletes the local prompt and response history to start a new conversation. 14 | } 15 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/tests.html 7 | # * https://testthat.r-lib.org/reference/test_package.html#special-files 8 | 9 | library(testthat) 10 | library(askgpt) 11 | 12 | test_check("askgpt") 13 | -------------------------------------------------------------------------------- /man/prompt_history.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.r 3 | \name{prompt_history} 4 | \alias{prompt_history} 5 | \title{Return the prompt/response history} 6 | \usage{ 7 | prompt_history(n = Inf) 8 | } 9 | \arguments{ 10 | \item{n}{number of prompts/responses to return.} 11 | } 12 | \value{ 13 | a character vector 14 | } 15 | \description{ 16 | Return the prompt/response history 17 | } 18 | -------------------------------------------------------------------------------- /askgpt.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /man/response_history.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.r 3 | \name{response_history} 4 | \alias{response_history} 5 | \title{Return the prompt/response history} 6 | \usage{ 7 | response_history(n = Inf) 8 | } 9 | \arguments{ 10 | \item{n}{number of prompts/responses to return.} 11 | } 12 | \value{ 13 | a character vector 14 | } 15 | \description{ 16 | Return the prompt/response history 17 | } 18 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("rstudio_available", { 2 | expect_equal(class(rstudio_available()), "logical") 3 | }) 4 | 5 | test_that("history as file", { 6 | expect_s3_class({ 7 | tmp <- tempfile() 8 | log_("test", "test2", tmp) 9 | jsonlite::stream_in(file(tmp), verbose = FALSE) 10 | }, "data.frame") 11 | }) 12 | 13 | test_that("estimate number of tokens", { 14 | expect_equal(estimate_token("This is a test", mult = 1.6), 7L) 15 | }) 16 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(annotate_code) 4 | export(askgpt) 5 | export(chat_api) 6 | export(completions_api) 7 | export(document_code) 8 | export(estimate_token) 9 | export(explain_code) 10 | export(list_models) 11 | export(log_init) 12 | export(login) 13 | export(new_conversation) 14 | export(parse_response) 15 | export(prompt_history) 16 | export(response_history) 17 | export(test_function) 18 | import(rlang) 19 | importFrom(rlang,`%||%`) 20 | -------------------------------------------------------------------------------- /man/parse_response.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.r 3 | \name{parse_response} 4 | \alias{parse_response} 5 | \title{Parse response from API functions} 6 | \usage{ 7 | parse_response(response) 8 | } 9 | \arguments{ 10 | \item{response}{a response object from \code{\link{chat_api}} or 11 | \code{\link{completions_api}}} 12 | } 13 | \value{ 14 | a character vector 15 | } 16 | \description{ 17 | Parse response from API functions 18 | } 19 | -------------------------------------------------------------------------------- /man/token_limits.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.r 3 | \docType{data} 4 | \name{token_limits} 5 | \alias{token_limits} 6 | \title{Max tokens limits of the different models} 7 | \format{ 8 | An object of class \code{data.frame} with 6 rows and 2 columns. 9 | } 10 | \source{ 11 | 12 | } 13 | \usage{ 14 | token_limits 15 | } 16 | \description{ 17 | OpenAI's token limits for different models. 18 | } 19 | \keyword{datasets} 20 | -------------------------------------------------------------------------------- /man/explain_code.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/code_prompts.r 3 | \name{explain_code} 4 | \alias{explain_code} 5 | \title{Explain R code} 6 | \usage{ 7 | explain_code(code, ...) 8 | } 9 | \arguments{ 10 | \item{code}{A character vector of R code. If missing the code currently 11 | selected in RStudio is documented (If RStudio is used).} 12 | 13 | \item{...}{passed on to \code{\link{askgpt}}.} 14 | } 15 | \value{ 16 | A character vector. 17 | } 18 | \description{ 19 | Explain R code 20 | } 21 | -------------------------------------------------------------------------------- /man/test_function.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/code_prompts.r 3 | \name{test_function} 4 | \alias{test_function} 5 | \title{Test R code} 6 | \usage{ 7 | test_function(code, ...) 8 | } 9 | \arguments{ 10 | \item{code}{A character vector of R code. If missing the code currently 11 | selected in RStudio is documented (If RStudio is used).} 12 | 13 | \item{...}{passed on to \code{\link{askgpt}}.} 14 | } 15 | \value{ 16 | A character vector. 17 | } 18 | \description{ 19 | Test R code 20 | } 21 | -------------------------------------------------------------------------------- /man/log_init.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.r 3 | \name{log_init} 4 | \alias{log_init} 5 | \title{Initiate error logging} 6 | \usage{ 7 | log_init(...) 8 | } 9 | \arguments{ 10 | \item{...}{forwarded to \code{\link[rlang]{global_entrace}}} 11 | } 12 | \value{ 13 | No return value, called to enable rlang error logging 14 | } 15 | \description{ 16 | Initiate error logging 17 | } 18 | \details{ 19 | Just an alias for rlang::global_entrace() with a more fitting name (for the 20 | purpose here). 21 | } 22 | -------------------------------------------------------------------------------- /man/tutorialise_addin.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shiny_addins.r 3 | \name{tutorialise_addin} 4 | \alias{tutorialise_addin} 5 | \title{Turn R code into a tutorial} 6 | \usage{ 7 | tutorialise_addin() 8 | } 9 | \value{ 10 | No return value, opens a new file in RStudio 11 | } 12 | \description{ 13 | `tutorialise_addin()` opens an [RStudio 14 | gadget](https://shiny.rstudio.com/articles/gadgets.html) and 15 | [addin](http://rstudio.github.io/rstudioaddins/) that turns selected code 16 | into an R Markdown/Quarto Tutorial. 17 | } 18 | -------------------------------------------------------------------------------- /man/annotate_code.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/code_prompts.r 3 | \name{annotate_code} 4 | \alias{annotate_code} 5 | \title{Annotate R code with inline comments} 6 | \usage{ 7 | annotate_code(code, ...) 8 | } 9 | \arguments{ 10 | \item{code}{A character vector of R code. If missing the code currently 11 | selected in RStudio is documented (If RStudio is used).} 12 | 13 | \item{...}{passed on to \code{\link{askgpt}}.} 14 | } 15 | \value{ 16 | A character vector. 17 | } 18 | \description{ 19 | Annotate R code with inline comments 20 | } 21 | -------------------------------------------------------------------------------- /man/improve_addin.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shiny_addins.r 3 | \name{improve_addin} 4 | \alias{improve_addin} 5 | \title{Improve code/documentation/writing using a prompt} 6 | \usage{ 7 | improve_addin() 8 | } 9 | \value{ 10 | No return value, opens a new file in RStudio 11 | } 12 | \description{ 13 | `tutorialise_addin()` opens an [RStudio 14 | gadget](https://shiny.rstudio.com/articles/gadgets.html) and 15 | [addin](http://rstudio.github.io/rstudioaddins/) that can be used to 16 | improve existing code, documentation, or writing. 17 | } 18 | -------------------------------------------------------------------------------- /man/document_code.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/code_prompts.r 3 | \name{document_code} 4 | \alias{document_code} 5 | \title{Document R Code} 6 | \usage{ 7 | document_code(code, ...) 8 | } 9 | \arguments{ 10 | \item{code}{A character vector of R code. If missing the code currently 11 | selected in RStudio is documented (If RStudio is used).} 12 | 13 | \item{...}{passed on to \code{\link{askgpt}}.} 14 | } 15 | \value{ 16 | A character vector. 17 | } 18 | \description{ 19 | Document R Code 20 | } 21 | \examples{ 22 | \dontrun{ 23 | document_code() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/testthat/test-askgpt.R: -------------------------------------------------------------------------------- 1 | test_that("Ask questions", { 2 | skip_on_cran() 3 | expect_length(httr2::with_mock(cache_response, completions_api("is this a test?")), 8L) 4 | expect_length(httr2::with_mock(cache_response, chat_api("is this a test?")), 8L) 5 | expect_snapshot(askgpt("", progress = FALSE, callfun = mockcall)) 6 | }) 7 | 8 | test_that("Error messages", { 9 | skip_on_cran() 10 | skip("Skipping since with_mock is not working right") 11 | expect_error( 12 | httr2::with_mock(mock_error, chat_api("is this a test?", api_key = "")), 13 | "This error can also mean that you ran out of credit." 14 | ) 15 | }) 16 | -------------------------------------------------------------------------------- /tests/testthat/test-code.R: -------------------------------------------------------------------------------- 1 | test_that("document", { 2 | skip_on_cran() 3 | expect_length(httr2::with_mock(cache_response, document_code("1 + 1", progress = FALSE)), 1L) 4 | }) 5 | 6 | test_that("annotate", { 7 | skip_on_cran() 8 | expect_length(httr2::with_mock(cache_response, annotate_code("1 + 1", progress = FALSE)), 1L) 9 | }) 10 | 11 | test_that("explain", { 12 | skip_on_cran() 13 | expect_length(httr2::with_mock(cache_response, explain_code("1 + 1", progress = FALSE)), 8L) 14 | }) 15 | 16 | test_that("test", { 17 | skip_on_cran() 18 | expect_length(httr2::with_mock(cache_response, test_function("1 + 1", progress = FALSE)), 8L) 19 | }) 20 | -------------------------------------------------------------------------------- /R/zzz.r: -------------------------------------------------------------------------------- 1 | .onAttach <- function(...) { 2 | greet_startup() 3 | } 4 | 5 | #' @keywords internal 6 | greet_startup <- function() { 7 | msg <- paste0( 8 | c( 9 | "Hi, this is {.emph askgpt} {cli::symbol$smiley}.", 10 | "{cli::symbol$bullet} To start error logging, run {.run [{.fun log_init}](askgpt::log_init())} now.", 11 | "{cli::symbol$bullet} To see what you can do use {.help [{.fun ?askgpt}](askgpt::askgpt)}.", 12 | "{cli::symbol$bullet} Or just run {.run [{.fun askgpt}](askgpt::askgpt())} with any question you want!" 13 | ), 14 | collapse = "\n" 15 | ) 16 | rlang::inform(cli::format_inline(msg), class = "packageStartupMessage") 17 | } 18 | -------------------------------------------------------------------------------- /man/list_models.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.r 3 | \name{list_models} 4 | \alias{list_models} 5 | \title{List Models} 6 | \usage{ 7 | list_models(api_key = NULL) 8 | } 9 | \arguments{ 10 | \item{api_key}{set the API key. If NULL, looks for the env OPENAI_API_KEY.} 11 | } 12 | \value{ 13 | A tibble with available models 14 | } 15 | \description{ 16 | List the models available in the API. You can refer to the [Models 17 | documentation](https://platform.openai.com/docs/models) to understand what 18 | models are available and the differences between them. 19 | } 20 | \examples{ 21 | \dontrun{ 22 | completions_api("The quick brown fox") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/testthat/test-auth.R: -------------------------------------------------------------------------------- 1 | test_that("retrieve key", { 2 | skip_on_cran() 3 | expect_no_match(login(no_cache = TRUE), "^$") 4 | }) 5 | 6 | test_that("key cache", { 7 | skip_on_cran() 8 | temp_cache <- paste0(tempfile("keys"), "/") 9 | dir.create(temp_cache) 10 | expect_length( 11 | { 12 | login(cache_dir = temp_cache, no_cache = TRUE) 13 | list.files(temp_cache) 14 | }, 15 | 0L 16 | ) 17 | expect_error(withr::with_envvar(new = c("OPENAI_API_KEY" = ""), 18 | login(cache_dir = temp_cache)), 19 | "No API key available") 20 | expect_length( 21 | { 22 | login(cache_dir = temp_cache) 23 | list.files(temp_cache) 24 | }, 25 | 1L 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /man/login.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/auth.r 3 | \name{login} 4 | \alias{login} 5 | \title{Log in to OpenAI} 6 | \usage{ 7 | login(api_key, force_refresh = FALSE, cache_dir = NULL, no_cache = FALSE) 8 | } 9 | \arguments{ 10 | \item{api_key}{API key to use for authentication. If not provided, the 11 | function look for a cached key or guide the user to obtain one.} 12 | 13 | \item{force_refresh}{Log in again even if an API key is already cached.} 14 | 15 | \item{cache_dir}{dir location to save keys on disk. The default is to use 16 | \code{rappdirs::user_cache_dir("askgpt")}.} 17 | 18 | \item{no_cache}{Don't cache the API key, only load it into the environment.} 19 | } 20 | \value{ 21 | a character vector with an API key 22 | } 23 | \description{ 24 | Log in to OpenAI 25 | } 26 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Document Code 2 | Description: Use GPT to add roxygen2 style code to selected code. 3 | Binding: document_code 4 | Interactive: true 5 | 6 | Name: Annotate Code 7 | Description: Use GPT to add inline comments explaining R code. 8 | Binding: annotate_code 9 | Interactive: true 10 | 11 | Name: Explain Code 12 | Description: Use GPT explain selected code. 13 | Binding: explain_code 14 | Interactive: true 15 | 16 | Name: Write Test 17 | Description: Use GPT to write unit test. 18 | Binding: test_function 19 | Interactive: true 20 | 21 | Name: Tutorialise Code 22 | Description: Use GPT to turn code into a tutorial. 23 | Binding: tutorialise_addin 24 | Interactive: true 25 | 26 | Name: Improve 27 | Description: Use GPT to improve existing code, documentation, or writing. 28 | Binding: improve_addin 29 | Interactive: true 30 | -------------------------------------------------------------------------------- /man/estimate_token.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.r 3 | \name{estimate_token} 4 | \alias{estimate_token} 5 | \title{Estimate token count} 6 | \usage{ 7 | estimate_token(x, mult = 1.6) 8 | } 9 | \arguments{ 10 | \item{x}{character vector} 11 | 12 | \item{mult}{the multiplier used} 13 | } 14 | \value{ 15 | a integer vector of token counts 16 | } 17 | \description{ 18 | Estimate token count 19 | } 20 | \details{ 21 | This function estimates how many tokens the API will make of the 22 | input words. For the models 1 word is more than one token. The default 23 | multiplier value resulted from testing the API. See 24 | 25 | for more information. 26 | } 27 | \examples{ 28 | estimate_token("this is a test") 29 | } 30 | -------------------------------------------------------------------------------- /submit2cran.r: -------------------------------------------------------------------------------- 1 | ## Update roxygen and check 2 | roxygen2::roxygenise(clean = TRUE) 3 | devtools::check() 4 | 5 | ## Check code quality 6 | lintr::lint_package() 7 | goodpractice::gp() 8 | 9 | ## Check spelling 10 | spelling::spell_check_package() 11 | spelling::update_wordlist() 12 | spelling::spell_check_files("README.Rmd", ignore = readLines("./inst/WORDLIST"), lang = "en-GB") 13 | 14 | ## build manual 15 | devtools::build_manual() 16 | 17 | ## build vignette 18 | devtools::build_readme() 19 | devtools::build_vignettes() 20 | 21 | ## test covr 22 | devtools::test_coverage() 23 | 24 | 25 | # For release on CRAN 26 | ## test on winbuilder 27 | devtools::check_win_devel() 28 | devtools::check_win_oldrelease() 29 | devtools::check_win_release() 30 | 31 | ## check r_hub 32 | ch <- rhub::check_for_cran(show_status = FALSE) 33 | ch$livelog() # check status 34 | 35 | ## release 36 | revdepcheck::revdep_check() 37 | devtools::release() 38 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: askgpt 2 | Type: Package 3 | Title: Asking GPT About R Stuff 4 | Version: 0.1.3.9000 5 | Authors@R: person("Johannes", "Gruber", email = "johannesb.gruber@gmail.com", 6 | role = c("aut", "cre")) 7 | Description: A chat package connecting to API endpoints by 'OpenAI' 8 | () to answer questions (about R). 9 | Depends: 10 | R (>= 4.1.0) 11 | Imports: 12 | cli, 13 | callr, 14 | dplyr, 15 | glue, 16 | methods, 17 | rlang, 18 | httr2, 19 | rappdirs, 20 | jsonlite 21 | Suggests: 22 | covr, 23 | knitr, 24 | miniUI, 25 | rmarkdown, 26 | rstudioapi, 27 | shiny, 28 | shinycssloaders, 29 | spelling, 30 | testthat (>= 3.0.0), 31 | withr 32 | URL: https://github.com/JBGruber/askgpt 33 | BugReports: https://github.com/JBGruber/askgpt/issues 34 | License: GPL (>= 3) 35 | Encoding: UTF-8 36 | RoxygenNote: 7.3.1 37 | VignetteBuilder: knitr 38 | Config/testthat/edition: 3 39 | Language: en-GB 40 | LazyData: true 41 | -------------------------------------------------------------------------------- /tests/testthat/setup.R: -------------------------------------------------------------------------------- 1 | cache_response <- function(req) { 2 | 3 | prompt <- req[["body"]][["data"]][["prompt"]] 4 | if (is.null(prompt)) prompt <- tail(req[["body"]][["data"]][["messages"]][, 2], 1) 5 | 6 | id <- paste0(req[["body"]][["data"]][["model"]], "_", substr(gsub("[ ?:\n]", "", prompt), 1, 25), ".rds") 7 | path <- file.path("./responses", id) 8 | 9 | if (file.exists(path)) { 10 | resp <- readRDS(path) 11 | } else { 12 | message("mock cache does not exist, saving to ", path) 13 | resp <- withr::with_options(list(httr2_mock = NULL), httr2::req_perform(req)) 14 | dir.create("responses", showWarnings = FALSE) 15 | saveRDS(resp, path) 16 | } 17 | return(resp) 18 | } 19 | 20 | 21 | mockcall <- function(...) { 22 | list( 23 | api = "chat", 24 | choices = list(list(message = list(content = "test"))) 25 | ) 26 | } 27 | 28 | mock_error <- function(req) { 29 | httr2::response( 30 | 429, 31 | method = "POST", 32 | headers = list("content-type" = "application/json; charset=utf-8"), 33 | body = charToRaw(jsonlite::toJSON(list(error = list(message = "test")))) 34 | ) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # askgpt 0.1.3 2 | 3 | - fixes bug that ignored some options when askgpt() was run with spinner 4 | - adds an addin that can improve code, documentation, or writing given a prompt 5 | 6 | # askgpt 0.1.2 7 | 8 | - fixed bug that would keep the package from printing answers to screen 9 | 10 | # askgpt 0.1.1 11 | 12 | - better error handling 13 | - answers look better on screen and can handle Markdown code without breaking 14 | - new_conversation() function introduces the ability to make GPT forget previous questions 15 | - removes the streaming option, since it made little sense (see https://github.com/JBGruber/askgpt/issues/4) 16 | - improved the tutorialise adding to handle long documents and gave it a nicer interface 17 | - various bug fixes 18 | 19 | # askgpt 0.0.2 20 | 21 | First release to CRAN! This version introduces the core functionality of 22 | askgpt, which goal it is to make R more approachable by employing the power of 23 | the ChatGPT language model through the OpenAI APIs. The package provides the 24 | main function askgpt(), which can be used as to ask questions to OpenAI's GPT 25 | models, and some tools to document code via an RStudio Addin. 26 | -------------------------------------------------------------------------------- /man/askgpt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/askgpt.r 3 | \name{askgpt} 4 | \alias{askgpt} 5 | \title{Ask openai's GPT models a question} 6 | \usage{ 7 | askgpt(prompt, chat = TRUE, progress = TRUE, return_answer = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{prompt}{What you want to ask} 11 | 12 | \item{chat}{whether to use the chat API (i.e., the same model as ChatGPT) or 13 | the completions API.} 14 | 15 | \item{progress}{Show a progress spinner while the request to the API has not 16 | been fulfilled.} 17 | 18 | \item{return_answer}{Should the answer be returned as an object instead of 19 | printing it to the screen?} 20 | 21 | \item{...}{additional options forwarded to \code{\link{chat_api}} or 22 | \code{\link{completions_api}} respectively.} 23 | } 24 | \value{ 25 | either an httr2 response from one of the APIs or a character vector 26 | (if return_answer). 27 | } 28 | \description{ 29 | Ask openai's GPT models a question 30 | } 31 | \examples{ 32 | \dontrun{ 33 | askgpt("What is an R function?") 34 | askgpt("What is wrong with my last command?") 35 | askgpt("Can you help me with the function aes() from ggplot2?") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /man/chat_api.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.r 3 | \name{chat_api} 4 | \alias{chat_api} 5 | \title{Request answer from openai's chat API} 6 | \usage{ 7 | chat_api( 8 | prompt, 9 | model = NULL, 10 | config = NULL, 11 | max_tokens = NULL, 12 | api_key = NULL, 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{prompt}{character string of the prompt to be completed.} 18 | 19 | \item{model}{character string of the model to be used (defaults to 20 | "gpt-3.5-turbo-instruct").} 21 | 22 | \item{config}{a configuration prompt to tell the model how it should behave.} 23 | 24 | \item{max_tokens}{The maximum number of tokens to generate in the completion. 25 | 2048L is the maximum the models accept.} 26 | 27 | \item{api_key}{set the API key. If NULL, looks for the env OPENAI_API_KEY.} 28 | 29 | \item{...}{additional parameters to be passed to the API (see [the API 30 | documentation](https://platform.openai.com/docs/api-reference/completions)} 31 | } 32 | \value{ 33 | A tibble with available models 34 | 35 | a httr2 response object 36 | } 37 | \description{ 38 | Request answer from openai's chat API 39 | } 40 | \examples{ 41 | \dontrun{ 42 | chat_api("Hi, how are you?", config = "answer as a friendly chat bot") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /man/completions_api.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.r 3 | \name{completions_api} 4 | \alias{completions_api} 5 | \title{Request answer from openai's completions API} 6 | \usage{ 7 | completions_api( 8 | prompt, 9 | model = NULL, 10 | temperature = NULL, 11 | max_tokens = NULL, 12 | api_key = NULL, 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{prompt}{character string of the prompt to be completed.} 18 | 19 | \item{model}{character string of the model to be used (defaults to 20 | "gpt-3.5-turbo-instruct").} 21 | 22 | \item{temperature}{numeric value between 0 and 1 to control the randomness of 23 | the output (defaults to 0.2; lower values like 0.2 will make answers more 24 | focused and deterministic).} 25 | 26 | \item{max_tokens}{The maximum number of tokens to generate in the completion. 27 | 2048L is the maximum the models accept.} 28 | 29 | \item{api_key}{set the API key. If NULL, looks for the env OPENAI_API_KEY.} 30 | 31 | \item{...}{additional parameters to be passed to the API (see [the API 32 | documentation](https://platform.openai.com/docs/api-reference/completions)} 33 | } 34 | \value{ 35 | a httr2 response object 36 | } 37 | \description{ 38 | Mostly used under the hood for \code{\link{askgpt}}. 39 | } 40 | \details{ 41 | Only a few parameters are implemented by name. Most can be sent 42 | through the \code{...}. For example, you could use the \code{n} parameter 43 | just like this \code{completions_api("The quick brown fox", n = 2)}. 44 | 45 | A couple of defaults are used by the package: 46 | \itemize{ 47 | \item{the model used by default is "gpt-3.5-turbo-instruct"} 48 | \item{the default temperature is 0.2} 49 | \item{the default for max_tokens is 2048L} 50 | } 51 | 52 | You can configure how \code{\link{askgpt}} makes requests by setting 53 | options that start with \code{askgpt_*}. For example, to use a different 54 | model use \code{options(askgpt_model = "text-curie-001")}. It does not 55 | matter if the API parameter ist listed in the function or not. All are 56 | used. 57 | } 58 | \examples{ 59 | \dontrun{ 60 | completions_api("The quick brown fox") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # askgpt 17 | 18 | 19 | [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-green.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 20 | [![R-CMD-check](https://github.com/JBGruber/askgpt/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/JBGruber/askgpt/actions/workflows/R-CMD-check.yaml) 21 | [![Codecov test coverage](https://codecov.io/gh/JBGruber/askgpt/branch/main/graph/badge.svg)](https://app.codecov.io/gh/JBGruber/askgpt?branch=main) 22 | [![CRAN status](https://www.r-pkg.org/badges/version/askgpt)](https://CRAN.R-project.org/package=askgpt) 23 | [![CRAN_Download_Badge](https://cranlogs.r-pkg.org/badges/grand-total/askgpt)](https://cran.r-project.org/package=askgpt) 24 | 25 | 26 | You're new to R? 27 | You don't quite understand the code you copied from that tutorial? 28 | You get error messages that make no sense to you? 29 | Don't worry, just *askgpt*! 30 | 31 | `askgpt` is basically ChatGPT but from R (technically, it sends prompts to [OpenAI's API](https://openai.com/api/) directly from R). 32 | It also has some additional functionality: 33 | 34 | - Prompt *"What is wrong with my last command?"* (or *"help!"*) to get help on the last error R emitted 35 | - Use the RStudio addin to comment, annotate or explain highlighted code 36 | 37 | See more under [Usage](#usage). 38 | 39 | ## Installation 40 | 41 | You can install release version of askgpt like so: 42 | 43 | ``` r 44 | install.packages("askgpt") 45 | ``` 46 | 47 | You can install the development version of askgpt like so: 48 | 49 | ``` r 50 | remotes::install_github("JBGruber/askgpt") 51 | ``` 52 | 53 | ```{r echo=FALSE, results='asis'} 54 | fig_path = "vignettes/figs" 55 | res <- knitr::knit_child("vignettes/Usage.Rmd", quiet = TRUE) 56 | cat(res, sep = '\n') 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, tests] 6 | pull_request: 7 | branches: [main, tests] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 23 | - {os: ubuntu-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'oldrel-1'} 25 | 26 | env: 27 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 28 | R_KEEP_PKG_SOURCE: yes 29 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: | 45 | any::rcmdcheck 46 | any::covr 47 | needs: | 48 | coverage 49 | check 50 | 51 | - uses: r-lib/actions/check-r-package@v2 52 | with: 53 | upload-snapshots: true 54 | 55 | - name: Test coverage 56 | run: | 57 | covr::codecov( 58 | quiet = FALSE, 59 | clean = FALSE, 60 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") 61 | ) 62 | shell: Rscript {0} 63 | 64 | - name: Show testthat output 65 | if: always() 66 | run: | 67 | ## -------------------------------------------------------------------- 68 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 69 | shell: bash 70 | 71 | - name: Upload test results 72 | if: failure() 73 | uses: actions/upload-artifact@v3 74 | with: 75 | name: coverage-test-failures 76 | path: ${{ runner.temp }}/package 77 | -------------------------------------------------------------------------------- /R/code_prompts.r: -------------------------------------------------------------------------------- 1 | #' Document R Code 2 | #' 3 | #' @param code A character vector of R code. If missing the code currently 4 | #' selected in RStudio is documented (If RStudio is used). 5 | #' @param ... passed on to \code{\link{askgpt}}. 6 | #' 7 | #' @return A character vector. 8 | #' @export 9 | #' 10 | #' @examples 11 | #' \dontrun{ 12 | #' document_code() 13 | #' } 14 | document_code <- function(code, ...) { 15 | context <- NULL 16 | if (missing(code)) { 17 | selection <- get_selection() 18 | code <- selection$code 19 | context <- selection$context 20 | } 21 | 22 | prompt <- glue::glue("Document this R function using roxygen2 syntax:", 23 | "\n{code}") 24 | out <- askgpt(prompt, chat = FALSE, return_answer = TRUE, ...) 25 | 26 | if (rstudio_available()) { 27 | if (!is.null(context)) { 28 | rstudioapi::modifyRange( 29 | context$selection[[1L]]$range, 30 | paste0(out, collapse = "\n"), 31 | id = context$id 32 | ) 33 | invisible(rstudioapi::documentSave(context$id)) 34 | } 35 | } 36 | invisible(out) 37 | } 38 | 39 | 40 | #' Annotate R code with inline comments 41 | #' 42 | #' @inheritParams document_code 43 | #' @return A character vector. 44 | #' @export 45 | annotate_code <- function(code, ...) { 46 | context <- NULL 47 | if (missing(code)) { 48 | selection <- get_selection() 49 | code <- selection$code 50 | context <- selection$context 51 | } 52 | 53 | prompt <- glue::glue("Add inline comments to this R code:", 54 | "\n{code}") 55 | out <- askgpt(prompt, chat = FALSE, return_answer = TRUE, ...) 56 | 57 | if (rstudio_available()) { 58 | if (!is.null(context)) { 59 | rstudioapi::modifyRange( 60 | context$selection[[1L]]$range, 61 | paste0(out, collapse = "\n"), 62 | id = context$id 63 | ) 64 | invisible(rstudioapi::documentSave(context$id)) 65 | } 66 | } 67 | invisible(out) 68 | } 69 | 70 | 71 | #' Explain R code 72 | #' 73 | #' @inheritParams document_code 74 | #' @return A character vector. 75 | #' @export 76 | explain_code <- function(code, ...) { 77 | if (missing(code)) { 78 | code <- get_selection()$code 79 | } 80 | prompt <- glue::glue("Explain the following R code to me:", 81 | "\n{code}") 82 | askgpt(prompt, chat = TRUE, ...) 83 | } 84 | 85 | #' Test R code 86 | #' 87 | #' @inheritParams document_code 88 | #' @return A character vector. 89 | #' @export 90 | test_function <- function(code, ...) { 91 | if (missing(code)) { 92 | code <- get_selection()$code 93 | } 94 | prompt <- glue::glue("Write a testthat unit test for this R function:", 95 | "\n{code}") 96 | askgpt(prompt, chat = TRUE, ...) 97 | } 98 | -------------------------------------------------------------------------------- /R/auth.r: -------------------------------------------------------------------------------- 1 | #' Log in to OpenAI 2 | #' 3 | #' @param api_key API key to use for authentication. If not provided, the 4 | #' function look for a cached key or guide the user to obtain one. 5 | #' @param force_refresh Log in again even if an API key is already cached. 6 | #' @param cache_dir dir location to save keys on disk. The default is to use 7 | #' \code{rappdirs::user_cache_dir("askgpt")}. 8 | #' @param no_cache Don't cache the API key, only load it into the environment. 9 | #' 10 | #' @return a character vector with an API key 11 | #' @export 12 | login <- function(api_key, 13 | force_refresh = FALSE, 14 | cache_dir = NULL, 15 | no_cache = FALSE) { 16 | 17 | if (is.null(cache_dir)) cache_dir <- rappdirs::user_cache_dir("askgpt") 18 | 19 | # try get key from env 20 | if (missing(api_key)) api_key <- Sys.getenv("OPENAI_API_KEY") 21 | 22 | # try to load cached key 23 | if (api_key == "") api_key <- load_key(cache_dir) 24 | 25 | # prompt user to retrieve key 26 | if ((api_key == "" || force_refresh) && interactive()) api_key <- key_menu() 27 | 28 | if (api_key == "") cli::cli_abort("No API key available") 29 | 30 | # cache secret 31 | if ((length(list.files(cache_dir, full.names = TRUE)) == 0L || force_refresh) && 32 | !no_cache) cache_secret(api_key, cache_dir) 33 | 34 | Sys.setenv(OPENAI_API_KEY = api_key) 35 | invisible(api_key) 36 | } 37 | 38 | 39 | key_menu <- function() { 40 | utils::browseURL("https://platform.openai.com/account/api-keys") 41 | 42 | cli::cli_div(theme = list(span.button = list("background-color" = "#ECECF1", 43 | color = "#5C5D65"))) 44 | cli::cli_alert_info("It looks like you have not provided an API key yet.", 45 | "Let me guide you through the process:") 46 | cli::cli_ol(c( 47 | "Go to {.url https://platform.openai.com/account/api-keys}", 48 | "(Log into your account if you haven't done so yet)", 49 | "On the site, click the button {.button + Create new secret key} to create an API key", 50 | "Copy this key into R/RStudio" 51 | )) 52 | if (rstudio_available()) { 53 | api_key <- rstudioapi::askForSecret("api_key", message = "Enter OpenAI secret API key: ") 54 | } else { 55 | api_key <- readline(prompt = "Enter OpenAI secret API key: ") 56 | } 57 | return(api_key) 58 | } 59 | 60 | 61 | load_key <- function(cache_dir) { 62 | api_key <- "" 63 | cache <- list.files(cache_dir, 64 | full.names = TRUE) 65 | if (length(cache) > 0L) api_key <- httr2::secret_read_rds(cache, I(rlang::hash(Sys.info()[["user"]]))) 66 | return(api_key) 67 | } 68 | 69 | 70 | cache_secret <- function(api_key, cache_dir) { 71 | dir.create(cache_dir, showWarnings = FALSE, recursive = TRUE) 72 | httr2::secret_write_rds( 73 | api_key, 74 | path = file.path(cache_dir, 75 | "api_key.rds.enc"), 76 | key = I(rlang::hash(Sys.info()[["user"]])) 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /R/askgpt.r: -------------------------------------------------------------------------------- 1 | #' Ask openai's GPT models a question 2 | #' 3 | #' @param prompt What you want to ask 4 | #' @param chat whether to use the chat API (i.e., the same model as ChatGPT) or 5 | #' the completions API. 6 | #' @param progress Show a progress spinner while the request to the API has not 7 | #' been fulfilled. 8 | #' @param return_answer Should the answer be returned as an object instead of 9 | #' printing it to the screen? 10 | #' @param ... additional options forwarded to \code{\link{chat_api}} or 11 | #' \code{\link{completions_api}} respectively. 12 | #' 13 | #' @return either an httr2 response from one of the APIs or a character vector 14 | #' (if return_answer). 15 | #' @export 16 | #' 17 | #' @examples 18 | #' \dontrun{ 19 | #' askgpt("What is an R function?") 20 | #' askgpt("What is wrong with my last command?") 21 | #' askgpt("Can you help me with the function aes() from ggplot2?") 22 | #' } 23 | askgpt <- function(prompt, 24 | chat = TRUE, 25 | progress = TRUE, 26 | return_answer = FALSE, 27 | ...) { 28 | 29 | traceback_trigger <- c( 30 | "What is wrong with my last command?", 31 | "help!" 32 | ) 33 | 34 | if (prompt %in% traceback_trigger) { 35 | rrr <- rlang::last_error() 36 | prompt <- glue::glue("explain why this R code does not work:", 37 | "\n{rlang::expr_deparse(rrr[['call']])}", 38 | "\n{rlang::expr_deparse(rrr[['message']])}") 39 | } 40 | 41 | callfun <- ifelse(chat, chat_api, completions_api) 42 | # api function can be replaced with random function (for testing) 43 | if ("callfun" %in% names(list(...))) callfun <- list(...)$callfun 44 | 45 | if (progress) { 46 | 47 | if (interactive()) cli::cli_progress_step("GPT is thinking {cli::pb_spin}") 48 | key <- login() 49 | 50 | args = list(prompt = prompt, 51 | api_key = key, 52 | config = getOption("askgpt_config"), 53 | max_tokens = getOption("askgpt_max_tokens"), 54 | hist = c(rbind(prompt_history(), response_history())), 55 | ...) 56 | 57 | args$model <- switch(chat, getOption("askgpt_chat_model"), 58 | getOption("askgpt_completions_model")) 59 | 60 | # collect additional options 61 | opts <- grep("^askgpt_", names(.Options), value = TRUE) |> 62 | setdiff(c("askgpt_chat_model", "askgpt_completions_model", "askgpt_key", 63 | "askgpt_config", "askgpt_max_tokens", "askgpt_stream")) 64 | askopts <- lapply(opts, getOption) 65 | names(askopts) <- opts 66 | 67 | if (length(askopts) > 0) { 68 | args <- c(args, askopts) 69 | } 70 | 71 | rp <- callr::r_bg(callfun, 72 | args = args, 73 | package = TRUE) 74 | 75 | if (interactive()) while (rp$is_alive()) { 76 | cli::cli_progress_update() 77 | Sys.sleep(2 / 100) 78 | } 79 | 80 | response <- rp$get_result() 81 | 82 | } else { 83 | response <- callfun( 84 | prompt = prompt, 85 | ... 86 | ) 87 | } 88 | 89 | out <- parse_response(response) 90 | 91 | log_(prompt, out) 92 | if (interactive()) cli::cli_progress_done() 93 | 94 | if (return_answer) { 95 | return(out) 96 | } else { 97 | screen_answer(out) 98 | invisible(response) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /R/utils.r: -------------------------------------------------------------------------------- 1 | ## import rlang to use operators 2 | #' @import rlang 3 | 4 | # package environment 5 | the <- new.env(parent = emptyenv()) 6 | 7 | 8 | #' Initiate error logging 9 | #' 10 | #' @param ... forwarded to \code{\link[rlang]{global_entrace}} 11 | #' 12 | #' @details Just an alias for rlang::global_entrace() with a more fitting name (for the 13 | #' purpose here). 14 | #' 15 | #' @return No return value, called to enable rlang error logging 16 | #' @export 17 | log_init <- function(...) { 18 | if (!isTRUE(the$log_init)) global_entrace(...) 19 | the$log_init <- TRUE 20 | } 21 | 22 | 23 | #' Return the prompt/response history 24 | #' 25 | #' @param n number of prompts/responses to return. 26 | #' 27 | #' @return a character vector 28 | #' @export 29 | prompt_history <- function(n = Inf) { 30 | return(utils::tail(the$prompts, n)) 31 | } 32 | 33 | 34 | #' @inherit prompt_history 35 | #' @return a character vector 36 | #' @export 37 | response_history <- function(n = Inf) { 38 | return(utils::tail(the$responses, n)) 39 | } 40 | 41 | #' Start a new conversation 42 | #' 43 | #' Deletes the local prompt and response history to start a new conversation. 44 | #' 45 | #' @return Does not return a value 46 | #' @export 47 | new_conversation <- function() { 48 | the$responses <- NULL 49 | the$prompts <- NULL 50 | } 51 | 52 | 53 | #' Parse response from API functions 54 | #' 55 | #' @param response a response object from \code{\link{chat_api}} or 56 | #' \code{\link{completions_api}} 57 | #' 58 | #' @return a character vector 59 | #' @export 60 | parse_response <- function(response) { 61 | # if several answers are requested, collapse into one 62 | if (isTRUE(response$api == "chat")) { 63 | return( 64 | paste(sapply(response[["choices"]], function(x) x[["message"]][["content"]]), 65 | collapse = "\n\n") 66 | ) 67 | } else { 68 | return(paste(sapply(response[["choices"]], `[[`, "text"), collapse = "\n\n")) 69 | } 70 | } 71 | 72 | 73 | #' Estimate token count 74 | #' 75 | #' @details This function estimates how many tokens the API will make of the 76 | #' input words. For the models 1 word is more than one token. The default 77 | #' multiplier value resulted from testing the API. See 78 | #' 79 | #' for more information. 80 | #' 81 | #' 82 | #' @param x character vector 83 | #' @param mult the multiplier used 84 | #' 85 | #' @return a integer vector of token counts 86 | #' @export 87 | #' 88 | #' @examples 89 | #' estimate_token("this is a test") 90 | estimate_token <- function(x, mult = 1.6) { 91 | ceiling(vapply(gregexpr("\\S+", x), length, FUN.VALUE = integer(1L)) * mult) 92 | } 93 | 94 | 95 | # internal function to format answers 96 | screen_answer <- function(x) { 97 | pars <- unlist(strsplit(x, "\n", fixed = TRUE)) 98 | cli::cli_h1("Answer") 99 | # "{i}" instead of i stops glue from evaluating code inside the answer 100 | for (i in pars) cli::cli_text("{i}") 101 | } 102 | 103 | 104 | # safely check if rstudioapi is available 105 | rstudio_available <- function() { 106 | out <- FALSE 107 | if (rlang::is_installed("rstudioapi")) out <- rstudioapi::isAvailable() 108 | return(out) 109 | } 110 | 111 | 112 | # get selected text from RStudio 113 | get_selection <- function(variables) { 114 | if (rstudio_available()) { 115 | context <- rstudioapi::getActiveDocumentContext() 116 | code <- context$selection[[1L]]$text 117 | } else { 118 | cli::cli_abort("{.code code} is missing with no default") 119 | } 120 | return(list(context = context, code = code)) 121 | } 122 | 123 | 124 | # log prompts and responses 125 | log_ <- function(prompt, response, loc = Sys.getenv("askgpt_log_location")) { 126 | the$prompts <- c(the$prompts, prompt) 127 | the$responses <- c(the$responses, response) 128 | if (loc != "") { 129 | con <- file(loc, "ab") 130 | jsonlite::stream_out(data.frame(prompt = prompt, response = response), 131 | con, verbose = FALSE) 132 | close(con) 133 | } 134 | } 135 | 136 | 137 | # get all askgpt options 138 | get_askopts <- function(diff = NULL) { 139 | grep("^askgpt_", names(.Options), value = TRUE) |> 140 | # exclude options dealt with elsewhere 141 | setdiff(c( 142 | "askgpt_chat_model", 143 | "askgpt_completions_model", 144 | "askgpt_config", 145 | "askgpt_key", 146 | "askgpt_max_tokens", 147 | "askgpt_stream", 148 | "askgpt_temperature" 149 | )) 150 | } 151 | -------------------------------------------------------------------------------- /vignettes/Usage.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Usage" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Usage} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>", 14 | eval = FALSE 15 | ) 16 | if (!knitr:::child_mode()) fig_path <- "figs" 17 | ``` 18 | 19 | ## Log In 20 | 21 | You can log into OpenAI's API running the dedicated `login()` function or just ask something with `askgpt()`: 22 | 23 | 24 | ```{r} 25 | library(askgpt) 26 | login() 27 | #> ℹ It looks like you have not provided an API key yet. Let me guide you through the process: 28 | #> 1. Go to 29 | #> 2. (Log into your account if you haven't done so yet) 30 | #> 3. On the site, click the button + Create new secret key 31 | #> to create an API key 32 | #> 4. Copy this key into R/RStudio 33 | ``` 34 | 35 | ```{r eval=TRUE, echo=FALSE, fig.cap="Copy the API key from OpenAI's website"} 36 | knitr::include_graphics(file.path(fig_path, "key.png")) 37 | ``` 38 | 39 | ```{r eval=TRUE, echo=FALSE, fig.cap="And paste it into RStudio"} 40 | knitr::include_graphics(file.path(fig_path, "key-rs.png")) 41 | ``` 42 | 43 | 44 | You will not need to do this again after the first time. 45 | (Technical detail: This will store an encrypted version of your key in the directory returned by `rappdirs::user_cache_dir("askgpt")`.) 46 | If your old API key does not work any more, you can store a new one with: `login(force_refresh = TRUE)`. 47 | 48 | ## Usage 49 | 50 | To enable error logging (which you need if you want askgpt to explain errors to you) first run: 51 | 52 | ```{r} 53 | log_init() 54 | ``` 55 | 56 | After this, the key phrase **"What is wrong with my last command?"** (or just "help!") will make `askgpt` look up your last command and error message and return some help for you. 57 | The other important key phrase is **"Can you elaborate on that?"** (or just "What?"), which will elaborate on the previous answer. 58 | You can ask basically any question you want though: 59 | 60 | ```{r echo=FALSE} 61 | library(askgpt) 62 | log_init() 63 | 64 | # Ask any question you like and get an answer in your Console 65 | askgpt("What is an R function?") 66 | askgpt("Can you help me with the function aes() from ggplot2?") 67 | 68 | # this is a special trigger prompt that sends your last command to GPT 69 | mean[1] 70 | askgpt("What is wrong with my last command?") 71 | 72 | askgpt("Can you elaborate on that?") 73 | ``` 74 | 75 | ![some examples](https://user-images.githubusercontent.com/23524101/220632749-da59fe90-d6a2-4ebd-bbfe-d1164e8a7944.mp4) 76 | 77 | ## Configure 78 | 79 | You can configure how `askgpt` makes that start with `askgpt_*`. 80 | For example, to use a different model to use in `askgpt()` use `options(askgpt_chat_model = "gpt-3.5-turbo-0301")` (or `options(askgpt_chat_model = "gpt-4")` if you have beta access to the newer model). 81 | If you use the completions instead of the chat API (`chat = FALSE` in `askgpt()`) use `options(askgpt_completions_model = "text-curie-001")`. 82 | It does not matter if the API parameter is listed in the function or not. 83 | All are used. 84 | See the complete list [here](https://platform.openai.com/docs/api-reference/chat) and [here](https://platform.openai.com/docs/api-reference/completions). 85 | 86 | The most important setting, however, is `askgpt_config`. 87 | This can be used to configure the chat using plain English: 88 | 89 | ```{r} 90 | options(askgpt_config = "I'm 8 years old, please explain things easily") 91 | askgpt("What is an R function?") 92 | #> 93 | #> ── Answer ────────────────────────────────────────────────────────────────────── 94 | #> An R function is like giving your friend a set of instructions to perform a 95 | #> particular task. In R programming, a function is a set of instructions or steps 96 | #> that is given a name, and when you call that name, the function will perform 97 | #> those instructions. A function can take information or inputs, do something 98 | #> with those inputs (like adding or subtracting), and then give the result back 99 | #> as output. 100 | #> 101 | #> For example, think about giving your friend the instructions to make a peanut 102 | #> butter sandwich. The instructions might be: 103 | #> 104 | #> 1. Take two slices of bread 2. Spread peanut butter on one slice 3. Spread 105 | #> jelly on the other slice 4. Put the two slices together 106 | #> 107 | #> In R, a function might take a number (like 5) and add 1 to it, and then return 108 | #> the result (which would be 6). 109 | #> 110 | #> Functions in R are used to make code easier to use, understand, and reuse. They 111 | #> can also help programmers write complex and efficient programs. 112 | ``` 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # askgpt 5 | 6 | 7 | 8 | [![Lifecycle: 9 | stable](https://img.shields.io/badge/lifecycle-stable-green.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 10 | [![R-CMD-check](https://github.com/JBGruber/askgpt/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/JBGruber/askgpt/actions/workflows/R-CMD-check.yaml) 11 | [![Codecov test 12 | coverage](https://codecov.io/gh/JBGruber/askgpt/branch/main/graph/badge.svg)](https://app.codecov.io/gh/JBGruber/askgpt?branch=main) 13 | [![CRAN 14 | status](https://www.r-pkg.org/badges/version/askgpt)](https://CRAN.R-project.org/package=askgpt) 15 | [![CRAN_Download_Badge](https://cranlogs.r-pkg.org/badges/grand-total/askgpt)](https://cran.r-project.org/package=askgpt) 16 | 17 | 18 | You’re new to R? You don’t quite understand the code you copied from 19 | that tutorial? You get error messages that make no sense to you? Don’t 20 | worry, just *askgpt*! 21 | 22 | `askgpt` is basically ChatGPT but from R (technically, it sends prompts 23 | to [OpenAI’s API](https://openai.com/api/) directly from R). It also has 24 | some additional functionality: 25 | 26 | - Prompt *“What is wrong with my last command?”* (or *“help!”*) to get 27 | help on the last error R emitted 28 | - Use the RStudio addin to comment, annotate or explain highlighted code 29 | 30 | See more under [Usage](#usage). 31 | 32 | ## Installation 33 | 34 | You can install release version of askgpt like so: 35 | 36 | ``` r 37 | install.packages("askgpt") 38 | ``` 39 | 40 | You can install the development version of askgpt like so: 41 | 42 | ``` r 43 | remotes::install_github("JBGruber/askgpt") 44 | ``` 45 | 46 | ## Log In 47 | 48 | You can log into OpenAI’s API running the dedicated `login()` function 49 | or just ask something with `askgpt()`: 50 | 51 | ``` r 52 | library(askgpt) 53 | login() 54 | #> ℹ It looks like you have not provided an API key yet. Let me guide you through the process: 55 | #> 1. Go to 56 | #> 2. (Log into your account if you haven't done so yet) 57 | #> 3. On the site, click the button + Create new secret key 58 | #> to create an API key 59 | #> 4. Copy this key into R/RStudio 60 | ``` 61 | 62 |
63 | 64 | Copy the API key from OpenAI's website 65 |

66 | Copy the API key from OpenAI’s website 67 |

68 | 69 |
70 | 71 |
72 | 73 | And paste it into RStudio 74 |

75 | And paste it into RStudio 76 |

77 | 78 |
79 | 80 | You will not need to do this again after the first time. (Technical 81 | detail: This will store an encrypted version of your key in the 82 | directory returned by `rappdirs::user_cache_dir("askgpt")`.) If your old 83 | API key does not work any more, you can store a new one with: 84 | `login(force_refresh = TRUE)`. 85 | 86 | ## Usage 87 | 88 | To enable error logging (which you need if you want askgpt to explain 89 | errors to you) first run: 90 | 91 | ``` r 92 | log_init() 93 | ``` 94 | 95 | After this, the key phrase **“What is wrong with my last command?”** (or 96 | just “help!”) will make `askgpt` look up your last command and error 97 | message and return some help for you. The other important key phrase is 98 | **“Can you elaborate on that?”** (or just “What?”), which will elaborate 99 | on the previous answer. You can ask basically any question you want 100 | though: 101 | 102 |
103 | 108 | 109 |
110 | 111 | ## Configure 112 | 113 | You can configure how `askgpt` makes that start with `askgpt_*`. For 114 | example, to use a different model to use in `askgpt()` use 115 | `options(askgpt_chat_model = "gpt-3.5-turbo-0301")` (or 116 | `options(askgpt_chat_model = "gpt-4")` if you have beta access to the 117 | newer model). If you use the completions instead of the chat API 118 | (`chat = FALSE` in `askgpt()`) use 119 | `options(askgpt_completions_model = "text-curie-001")`. It does not 120 | matter if the API parameter is listed in the function or not. All are 121 | used. See the complete list 122 | [here](https://platform.openai.com/docs/api-reference/chat) and 123 | [here](https://platform.openai.com/docs/api-reference/completions). 124 | 125 | The most important setting, however, is `askgpt_config`. This can be 126 | used to configure the chat using plain English: 127 | 128 | ``` r 129 | options(askgpt_config = "I'm 8 years old, please explain things easily") 130 | askgpt("What is an R function?") 131 | #> 132 | #> ── Answer ────────────────────────────────────────────────────────────────────── 133 | #> An R function is like giving your friend a set of instructions to perform a 134 | #> particular task. In R programming, a function is a set of instructions or steps 135 | #> that is given a name, and when you call that name, the function will perform 136 | #> those instructions. A function can take information or inputs, do something 137 | #> with those inputs (like adding or subtracting), and then give the result back 138 | #> as output. 139 | #> 140 | #> For example, think about giving your friend the instructions to make a peanut 141 | #> butter sandwich. The instructions might be: 142 | #> 143 | #> 1. Take two slices of bread 2. Spread peanut butter on one slice 3. Spread 144 | #> jelly on the other slice 4. Put the two slices together 145 | #> 146 | #> In R, a function might take a number (like 5) and add 1 to it, and then return 147 | #> the result (which would be 6). 148 | #> 149 | #> Functions in R are used to make code easier to use, understand, and reuse. They 150 | #> can also help programmers write complex and efficient programs. 151 | ``` 152 | -------------------------------------------------------------------------------- /R/shiny_addins.r: -------------------------------------------------------------------------------- 1 | #' Turn R code into a tutorial 2 | #' 3 | #' @description `tutorialise_addin()` opens an [RStudio 4 | #' gadget](https://shiny.rstudio.com/articles/gadgets.html) and 5 | #' [addin](http://rstudio.github.io/rstudioaddins/) that turns selected code 6 | #' into an R Markdown/Quarto Tutorial. 7 | #' 8 | #' @return No return value, opens a new file in RStudio 9 | tutorialise_addin <- function() { 10 | 11 | rlang::check_installed( 12 | c("shiny", "miniUI", "shinycssloaders"), 13 | "in order to use the tutorialise addin" 14 | ) 15 | 16 | p <- the$tutorialise_prompt 17 | if (is.null(p)) p <- "Turn this into a tutorial for beginners and explain how this code works, return it as an R Markdown document:" 18 | 19 | ui <- miniUI::miniPage( 20 | miniUI::gadgetTitleBar( 21 | shiny::p( 22 | "Turn this code into a Tutorial" 23 | ), 24 | right = miniUI::miniTitleBarButton("done", "Tutorialise!", primary = TRUE) 25 | ), 26 | miniUI::miniContentPanel( 27 | shinycssloaders::withSpinner(shiny::textOutput("spinner"), type = 7, size = 3), 28 | shiny::textInput( 29 | "prompt", 30 | "Prompt", 31 | value = p, 32 | width = "100%" 33 | ), 34 | shiny::tags$hr(), 35 | shiny::textAreaInput( 36 | "code", 37 | "Use this code?", 38 | value = rstudio_selection(), 39 | width = "100%", 40 | height = "400px" 41 | ) 42 | ) 43 | ) 44 | 45 | server <- function(input, output, session) { 46 | 47 | # does not really render text but needed to show spinner 48 | prcs <- shiny::eventReactive(input$done, "GPT is thinking") 49 | output$spinner <- shiny::renderText({ 50 | prcs() 51 | shiny::stopApp({ 52 | the$tutorialise_prompt <- input$prompt 53 | out <- make_request(input$prompt, input$code) 54 | f <- paste0("tutorial_", gsub(pattern = "\\s+", "-", substr(input$prompt, 1, 50)), ".rmd") 55 | writeLines(out, f) 56 | rstudioapi::documentOpen(f) 57 | }) 58 | }) 59 | 60 | } 61 | 62 | app <- shiny::shinyApp(ui, server, options = list(quiet = TRUE)) 63 | invisible(shiny::runGadget(app, viewer = shiny::dialogViewer("Tutorialise R Code using ChatGPT"))) 64 | 65 | } 66 | 67 | 68 | #' Improve code/documentation/writing using a prompt 69 | #' 70 | #' @description `tutorialise_addin()` opens an [RStudio 71 | #' gadget](https://shiny.rstudio.com/articles/gadgets.html) and 72 | #' [addin](http://rstudio.github.io/rstudioaddins/) that can be used to 73 | #' improve existing code, documentation, or writing. 74 | #' 75 | #' @return No return value, opens a new file in RStudio 76 | improve_addin <- function() { 77 | 78 | rlang::check_installed( 79 | c("shiny", "miniUI", "shinycssloaders"), 80 | "in order to use the tutorialise addin" 81 | ) 82 | 83 | p <- the$improve_prompt 84 | if (is.null(p)) p <- "Improve this code/documentation/writing:" 85 | 86 | ui <- miniUI::miniPage( 87 | miniUI::gadgetTitleBar( 88 | shiny::p( 89 | "Improve code/documentation/writing" 90 | ), 91 | right = miniUI::miniTitleBarButton("done", "Improve!", primary = TRUE) 92 | ), 93 | miniUI::miniContentPanel( 94 | shinycssloaders::withSpinner(shiny::textOutput("spinner"), type = 7, size = 3), 95 | shiny::textInput( 96 | "prompt", 97 | "Prompt", 98 | value = p, 99 | width = "100%" 100 | ), 101 | shiny::tags$hr(), 102 | shiny::textAreaInput( 103 | "code", 104 | "Use this?", 105 | value = rstudio_selection(), 106 | width = "100%", 107 | height = "400px" 108 | ) 109 | ) 110 | ) 111 | 112 | server <- function(input, output, session) { 113 | 114 | # does not really render text but needed to show spinner 115 | prcs <- shiny::eventReactive(input$done, "GPT is thinking") 116 | output$spinner <- shiny::renderText({ 117 | prcs() 118 | shiny::stopApp({ 119 | the$improve_prompt <- input$prompt 120 | out <- make_request(input$prompt, input$code) 121 | f <- tempfile(fileext = ".txt") 122 | writeLines(out, f) 123 | rstudioapi::documentOpen(f) 124 | }) 125 | }) 126 | 127 | } 128 | 129 | app <- shiny::shinyApp(ui, server, options = list(quiet = TRUE)) 130 | invisible(shiny::runGadget(app, viewer = shiny::dialogViewer("Improve input using ChatGPT"))) 131 | 132 | } 133 | 134 | 135 | #' @importFrom rlang `%||%` 136 | make_request <- function(prompt, code) { 137 | 138 | # break into API size pieces 139 | mod <- getOption("askgpt_chat_model") %||% "gpt-3.5-turbo" 140 | max_tokens <- getOption("askgpt_max_tokens") %||% 2048L 141 | tok_max <- askgpt::token_limits[askgpt::token_limits$model == mod, "limit"] - max_tokens 142 | 143 | tokens <- estimate_token(paste(prompt, code)) 144 | 145 | if (tokens > tok_max) { 146 | cli::cli_alert_info( 147 | c("The request is too long and is split into several prompts, which can take a ", 148 | "long time to process. The final tutorial will have a line with `----` where ", 149 | "responses were combined."), wrap = TRUE 150 | ) 151 | 152 | # split into paragraphs 153 | prompts <- split_prompt(code, tok_max = tok_max - estimate_token(prompt)) 154 | # glue prompts + this is the nth part + code chunk 155 | prompts <- vapply(seq_along(prompts), function(i) { 156 | glue::glue("{prompt}. This is the {i}th part of the code:\n{prompts[i]}") 157 | }, FUN.VALUE = character(1L)) 158 | 159 | vapply(prompts, function(prompt) { 160 | parse_response(chat_api(prompt = prompt)) 161 | }, FUN.VALUE = character(1L)) |> 162 | paste(collapse = "\n----\n") 163 | } else { 164 | prompt <- paste0(prompt, "\n", code) 165 | parse_response(chat_api(prompt = prompt)) 166 | } 167 | } 168 | 169 | # split long prompts 170 | split_prompt <- function(x, tok_max) { 171 | pars <- strsplit(x, "\n")[[1]] 172 | lens <- estimate_token(pars) 173 | # leave a margin of 20 for safety 174 | bins <- cumsum(lens) %/% tok_max + 20 175 | split_pars <- split(pars, bins) 176 | vapply(split_pars, paste, collapse = "\n", FUN.VALUE = character(1)) 177 | } 178 | 179 | 180 | rstudio_selection <- function() { 181 | context <- rstudioapi::getActiveDocumentContext() 182 | out <- context$selection[[1L]]$text 183 | if (isTRUE(out == "") || length(out) == 0) out <- context$contents 184 | if (isTRUE(out == "") || length(out) == 0) out <- rstudioapi::getSourceEditorContext()$contents 185 | if (isTRUE(out == "") || length(out) == 0) out <- "" 186 | return(paste(out, collapse = "\n")) 187 | } 188 | -------------------------------------------------------------------------------- /R/api.r: -------------------------------------------------------------------------------- 1 | #' Request answer from openai's completions API 2 | #' 3 | #' Mostly used under the hood for \code{\link{askgpt}}. 4 | #' 5 | #' @param prompt character string of the prompt to be completed. 6 | #' @param model character string of the model to be used (defaults to 7 | #' "gpt-3.5-turbo-instruct"). 8 | #' @param temperature numeric value between 0 and 1 to control the randomness of 9 | #' the output (defaults to 0.2; lower values like 0.2 will make answers more 10 | #' focused and deterministic). 11 | #' @param max_tokens The maximum number of tokens to generate in the completion. 12 | #' 2048L is the maximum the models accept. 13 | #' @param api_key set the API key. If NULL, looks for the env OPENAI_API_KEY. 14 | #' @param ... additional parameters to be passed to the API (see [the API 15 | #' documentation](https://platform.openai.com/docs/api-reference/completions) 16 | #' 17 | #' @details Only a few parameters are implemented by name. Most can be sent 18 | #' through the \code{...}. For example, you could use the \code{n} parameter 19 | #' just like this \code{completions_api("The quick brown fox", n = 2)}. 20 | #' 21 | #' A couple of defaults are used by the package: 22 | #' \itemize{ 23 | #' \item{the model used by default is "gpt-3.5-turbo-instruct"} 24 | #' \item{the default temperature is 0.2} 25 | #' \item{the default for max_tokens is 2048L} 26 | #' } 27 | #' 28 | #' You can configure how \code{\link{askgpt}} makes requests by setting 29 | #' options that start with \code{askgpt_*}. For example, to use a different 30 | #' model use \code{options(askgpt_model = "text-curie-001")}. It does not 31 | #' matter if the API parameter ist listed in the function or not. All are 32 | #' used. 33 | #' 34 | #' @importFrom rlang `%||%` 35 | #' 36 | #' @return a httr2 response object 37 | #' @export 38 | #' 39 | #' @examples 40 | #' \dontrun{ 41 | #' completions_api("The quick brown fox") 42 | #' } 43 | completions_api <- function(prompt, 44 | model = NULL, 45 | temperature = NULL, 46 | max_tokens = NULL, 47 | api_key = NULL, 48 | ...) { 49 | 50 | model <- model %||% getOption("askgpt_completions_model") %||% "gpt-3.5-turbo-instruct" 51 | temperature <- temperature %||% getOption("askgpt_temperature") %||% 0.2 52 | max_tokens <- max_tokens %||% getOption("askgpt_max_tokens") %||% 2048L 53 | api_key <- api_key %||% login() 54 | 55 | # collect additional options 56 | params <- list(...) 57 | 58 | if (!is.null(params$stream)) cli::cli_warn("The streaming feature has been removed from the package.") 59 | # Todo: find more elegant way to remove these 60 | params$hist <- NULL 61 | params$config <- NULL 62 | 63 | for (par in get_askopts()) { 64 | params[gsub("askgpt_", "", par, fixed = TRUE)] <- getOption(par) 65 | } 66 | 67 | body <- c(list( 68 | model = model, 69 | prompt = prompt, 70 | temperature = temperature, 71 | max_tokens = max_tokens 72 | ), params) 73 | 74 | body <- Filter(Negate(is.null), body) 75 | 76 | req <- httr2::request("https://api.openai.com/v1/completions") |> 77 | httr2::req_method("POST") |> 78 | httr2::req_headers( 79 | "Content-Type" = "application/json", 80 | "Authorization" = glue::glue("Bearer {api_key}") 81 | ) |> 82 | httr2::req_body_json(body) 83 | 84 | resp <- httr2::req_perform(req) |> 85 | httr2::resp_body_json() 86 | 87 | resp$call <- req 88 | resp$api <- "completions" 89 | return(resp) 90 | } 91 | 92 | 93 | #' Request answer from openai's chat API 94 | #' 95 | #' @param config a configuration prompt to tell the model how it should behave. 96 | #' 97 | #' @inheritParams completions_api 98 | #' 99 | #' @return A tibble with available models 100 | #' 101 | #' @importFrom rlang `%||%` 102 | #' 103 | #' @return a httr2 response object 104 | #' @export 105 | #' 106 | #' @examples 107 | #' \dontrun{ 108 | #' chat_api("Hi, how are you?", config = "answer as a friendly chat bot") 109 | #' } 110 | chat_api <- function(prompt, 111 | model = NULL, 112 | config = NULL, 113 | max_tokens = NULL, 114 | api_key = NULL, 115 | ...) { 116 | 117 | model <- model %||% getOption("askgpt_chat_model") %||% "gpt-3.5-turbo" 118 | config <- config %||% getOption("askgpt_config") 119 | max_tokens <- max_tokens %||% getOption("askgpt_max_tokens") %||% 2048L 120 | api_key <- api_key %||% login() 121 | 122 | # collect additional options 123 | params <- list(...) 124 | if (!is.null(params$stream)) cli::cli_warn("The streaming feature has been removed from the package.") 125 | 126 | for (par in get_askopts()) { 127 | params[gsub("askgpt_", "", par, fixed = TRUE)] <- getOption(par) 128 | } 129 | 130 | hist <- params$hist %||% c(rbind(prompt_history(), response_history())) 131 | params$hist <- NULL 132 | 133 | messages <- dplyr::bind_rows(list( 134 | if (!is.null(config)) data.frame(role = "system", 135 | content = config), 136 | if (length(hist) > 0) data.frame(role = c("user", "assistant"), 137 | content = hist), 138 | if (!methods::is(prompt, "data.frame")) data.frame(role = "user", 139 | content = prompt) else prompt 140 | )) 141 | 142 | body <- c(list( 143 | model = model, 144 | messages = messages, 145 | max_tokens = max_tokens 146 | ), params) 147 | 148 | body <- Filter(Negate(is.null), body) 149 | 150 | req <- httr2::request("https://api.openai.com/v1/chat/completions") |> 151 | httr2::req_method("POST") |> 152 | httr2::req_headers( 153 | "Content-Type" = "application/json", 154 | "Authorization" = glue::glue("Bearer {api_key}") 155 | ) |> 156 | httr2::req_body_json(body) |> 157 | httr2::req_error(body = error_body) 158 | 159 | resp <- httr2::req_perform(req) |> 160 | httr2::resp_body_json() 161 | resp$call <- req 162 | resp$api <- "chat" 163 | return(resp) 164 | } 165 | 166 | error_body <- function(resp) { 167 | msg <- httr2::resp_body_json(resp)$error$message 168 | if (httr2::resp_status(resp) == 401L) { 169 | msg <- c(msg, "This error can also mean that you ran out of credit. Check https://platform.openai.com/account/usage") 170 | } 171 | msg 172 | } 173 | 174 | 175 | #' List Models 176 | #' 177 | #' List the models available in the API. You can refer to the [Models 178 | #' documentation](https://platform.openai.com/docs/models) to understand what 179 | #' models are available and the differences between them. 180 | #' 181 | #' 182 | #' @inheritParams completions_api 183 | #' 184 | #' @return A tibble with available models 185 | #' 186 | #' @importFrom rlang `%||%` 187 | #' 188 | #' @export 189 | #' 190 | #' @examples 191 | #' \dontrun{ 192 | #' completions_api("The quick brown fox") 193 | #' } 194 | list_models <- function(api_key = NULL) { 195 | 196 | api_key <- api_key %||% login() 197 | 198 | req <- httr2::request("https://api.openai.com/v1/models") |> 199 | httr2::req_method("GET") |> 200 | httr2::req_headers( 201 | "Content-Type" = "application/json", 202 | "Authorization" = glue::glue("Bearer {api_key}") 203 | ) 204 | 205 | resp <- httr2::req_perform(req) |> 206 | httr2::resp_body_json() 207 | 208 | dplyr::bind_rows(resp$data) 209 | } 210 | 211 | 212 | create_img <- function(prompt, 213 | n = 4, 214 | size = c("256x256", "512x512", "1024x1024"), 215 | download_folder = NULL, 216 | api_key = NULL) { 217 | 218 | api_key <- api_key %||% login() 219 | 220 | body <- list( 221 | prompt = prompt, 222 | n = n, 223 | size = size[1], 224 | response_format = "url" 225 | ) 226 | 227 | req <- httr2::request("https://api.openai.com/v1/images/generations") |> 228 | httr2::req_headers( 229 | "Content-Type" = "application/json", 230 | "Authorization" = glue::glue("Bearer {api_key}") 231 | ) |> 232 | httr2::req_body_json(body) |> 233 | httr2::req_error(body = error_body) 234 | 235 | resp <- httr2::req_perform(req) |> 236 | httr2::resp_body_json() 237 | 238 | links <- purrr::map_chr(resp$data, "url") 239 | if (!is.null(download_folder)) { 240 | curl::multi_download( 241 | links, destfiles = file.path(download_folder, paste0(seq_len(n), ".png")) 242 | ) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU General Public License 2 | ========================== 3 | 4 | _Version 3, 29 June 2007_ 5 | _Copyright © 2007 Free Software Foundation, Inc. <>_ 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | ## Preamble 11 | 12 | The GNU General Public License is a free, copyleft license for software and other 13 | kinds of works. 14 | 15 | The licenses for most software and other practical works are designed to take away 16 | your freedom to share and change the works. By contrast, the GNU General Public 17 | License is intended to guarantee your freedom to share and change all versions of a 18 | program--to make sure it remains free software for all its users. We, the Free 19 | Software Foundation, use the GNU General Public License for most of our software; it 20 | applies also to any other work released this way by its authors. You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our General 24 | Public Licenses are designed to make sure that you have the freedom to distribute 25 | copies of free software (and charge for them if you wish), that you receive source 26 | code or can get it if you want it, that you can change the software or use pieces of 27 | it in new free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you these rights or 30 | asking you to surrender the rights. Therefore, you have certain responsibilities if 31 | you distribute copies of the software, or if you modify it: responsibilities to 32 | respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether gratis or for a fee, 35 | you must pass on to the recipients the same freedoms that you received. You must make 36 | sure that they, too, receive or can get the source code. And you must show them these 37 | terms so they know their rights. 38 | 39 | Developers that use the GNU GPL protect your rights with two steps: **(1)** assert 40 | copyright on the software, and **(2)** offer you this License giving you legal permission 41 | to copy, distribute and/or modify it. 42 | 43 | For the developers' and authors' protection, the GPL clearly explains that there is 44 | no warranty for this free software. For both users' and authors' sake, the GPL 45 | requires that modified versions be marked as changed, so that their problems will not 46 | be attributed erroneously to authors of previous versions. 47 | 48 | Some devices are designed to deny users access to install or run modified versions of 49 | the software inside them, although the manufacturer can do so. This is fundamentally 50 | incompatible with the aim of protecting users' freedom to change the software. The 51 | systematic pattern of such abuse occurs in the area of products for individuals to 52 | use, which is precisely where it is most unacceptable. Therefore, we have designed 53 | this version of the GPL to prohibit the practice for those products. If such problems 54 | arise substantially in other domains, we stand ready to extend this provision to 55 | those domains in future versions of the GPL, as needed to protect the freedom of 56 | users. 57 | 58 | Finally, every program is threatened constantly by software patents. States should 59 | not allow patents to restrict development and use of software on general-purpose 60 | computers, but in those that do, we wish to avoid the special danger that patents 61 | applied to a free program could make it effectively proprietary. To prevent this, the 62 | GPL assures that patents cannot be used to render the program non-free. 63 | 64 | The precise terms and conditions for copying, distribution and modification follow. 65 | 66 | ## TERMS AND CONDITIONS 67 | 68 | ### 0. Definitions 69 | 70 | “This License” refers to version 3 of the GNU General Public License. 71 | 72 | “Copyright” also means copyright-like laws that apply to other kinds of 73 | works, such as semiconductor masks. 74 | 75 | “The Program” refers to any copyrightable work licensed under this 76 | License. Each licensee is addressed as “you”. “Licensees” and 77 | “recipients” may be individuals or organizations. 78 | 79 | To “modify” a work means to copy from or adapt all or part of the work in 80 | a fashion requiring copyright permission, other than the making of an exact copy. The 81 | resulting work is called a “modified version” of the earlier work or a 82 | work “based on” the earlier work. 83 | 84 | A “covered work” means either the unmodified Program or a work based on 85 | the Program. 86 | 87 | To “propagate” a work means to do anything with it that, without 88 | permission, would make you directly or secondarily liable for infringement under 89 | applicable copyright law, except executing it on a computer or modifying a private 90 | copy. Propagation includes copying, distribution (with or without modification), 91 | making available to the public, and in some countries other activities as well. 92 | 93 | To “convey” a work means any kind of propagation that enables other 94 | parties to make or receive copies. Mere interaction with a user through a computer 95 | network, with no transfer of a copy, is not conveying. 96 | 97 | An interactive user interface displays “Appropriate Legal Notices” to the 98 | extent that it includes a convenient and prominently visible feature that **(1)** 99 | displays an appropriate copyright notice, and **(2)** tells the user that there is no 100 | warranty for the work (except to the extent that warranties are provided), that 101 | licensees may convey the work under this License, and how to view a copy of this 102 | License. If the interface presents a list of user commands or options, such as a 103 | menu, a prominent item in the list meets this criterion. 104 | 105 | ### 1. Source Code 106 | 107 | The “source code” for a work means the preferred form of the work for 108 | making modifications to it. “Object code” means any non-source form of a 109 | work. 110 | 111 | A “Standard Interface” means an interface that either is an official 112 | standard defined by a recognized standards body, or, in the case of interfaces 113 | specified for a particular programming language, one that is widely used among 114 | developers working in that language. 115 | 116 | The “System Libraries” of an executable work include anything, other than 117 | the work as a whole, that **(a)** is included in the normal form of packaging a Major 118 | Component, but which is not part of that Major Component, and **(b)** serves only to 119 | enable use of the work with that Major Component, or to implement a Standard 120 | Interface for which an implementation is available to the public in source code form. 121 | A “Major Component”, in this context, means a major essential component 122 | (kernel, window system, and so on) of the specific operating system (if any) on which 123 | the executable work runs, or a compiler used to produce the work, or an object code 124 | interpreter used to run it. 125 | 126 | The “Corresponding Source” for a work in object code form means all the 127 | source code needed to generate, install, and (for an executable work) run the object 128 | code and to modify the work, including scripts to control those activities. However, 129 | it does not include the work's System Libraries, or general-purpose tools or 130 | generally available free programs which are used unmodified in performing those 131 | activities but which are not part of the work. For example, Corresponding Source 132 | includes interface definition files associated with source files for the work, and 133 | the source code for shared libraries and dynamically linked subprograms that the work 134 | is specifically designed to require, such as by intimate data communication or 135 | control flow between those subprograms and other parts of the work. 136 | 137 | The Corresponding Source need not include anything that users can regenerate 138 | automatically from other parts of the Corresponding Source. 139 | 140 | The Corresponding Source for a work in source code form is that same work. 141 | 142 | ### 2. Basic Permissions 143 | 144 | All rights granted under this License are granted for the term of copyright on the 145 | Program, and are irrevocable provided the stated conditions are met. This License 146 | explicitly affirms your unlimited permission to run the unmodified Program. The 147 | output from running a covered work is covered by this License only if the output, 148 | given its content, constitutes a covered work. This License acknowledges your rights 149 | of fair use or other equivalent, as provided by copyright law. 150 | 151 | You may make, run and propagate covered works that you do not convey, without 152 | conditions so long as your license otherwise remains in force. You may convey covered 153 | works to others for the sole purpose of having them make modifications exclusively 154 | for you, or provide you with facilities for running those works, provided that you 155 | comply with the terms of this License in conveying all material for which you do not 156 | control copyright. Those thus making or running the covered works for you must do so 157 | exclusively on your behalf, under your direction and control, on terms that prohibit 158 | them from making any copies of your copyrighted material outside their relationship 159 | with you. 160 | 161 | Conveying under any other circumstances is permitted solely under the conditions 162 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 163 | 164 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law 165 | 166 | No covered work shall be deemed part of an effective technological measure under any 167 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 168 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 169 | of such measures. 170 | 171 | When you convey a covered work, you waive any legal power to forbid circumvention of 172 | technological measures to the extent such circumvention is effected by exercising 173 | rights under this License with respect to the covered work, and you disclaim any 174 | intention to limit operation or modification of the work as a means of enforcing, 175 | against the work's users, your or third parties' legal rights to forbid circumvention 176 | of technological measures. 177 | 178 | ### 4. Conveying Verbatim Copies 179 | 180 | You may convey verbatim copies of the Program's source code as you receive it, in any 181 | medium, provided that you conspicuously and appropriately publish on each copy an 182 | appropriate copyright notice; keep intact all notices stating that this License and 183 | any non-permissive terms added in accord with section 7 apply to the code; keep 184 | intact all notices of the absence of any warranty; and give all recipients a copy of 185 | this License along with the Program. 186 | 187 | You may charge any price or no price for each copy that you convey, and you may offer 188 | support or warranty protection for a fee. 189 | 190 | ### 5. Conveying Modified Source Versions 191 | 192 | You may convey a work based on the Program, or the modifications to produce it from 193 | the Program, in the form of source code under the terms of section 4, provided that 194 | you also meet all of these conditions: 195 | 196 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 197 | relevant date. 198 | * **b)** The work must carry prominent notices stating that it is released under this 199 | License and any conditions added under section 7. This requirement modifies the 200 | requirement in section 4 to “keep intact all notices”. 201 | * **c)** You must license the entire work, as a whole, under this License to anyone who 202 | comes into possession of a copy. This License will therefore apply, along with any 203 | applicable section 7 additional terms, to the whole of the work, and all its parts, 204 | regardless of how they are packaged. This License gives no permission to license the 205 | work in any other way, but it does not invalidate such permission if you have 206 | separately received it. 207 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 208 | Notices; however, if the Program has interactive interfaces that do not display 209 | Appropriate Legal Notices, your work need not make them do so. 210 | 211 | A compilation of a covered work with other separate and independent works, which are 212 | not by their nature extensions of the covered work, and which are not combined with 213 | it such as to form a larger program, in or on a volume of a storage or distribution 214 | medium, is called an “aggregate” if the compilation and its resulting 215 | copyright are not used to limit the access or legal rights of the compilation's users 216 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 217 | does not cause this License to apply to the other parts of the aggregate. 218 | 219 | ### 6. Conveying Non-Source Forms 220 | 221 | You may convey a covered work in object code form under the terms of sections 4 and 222 | 5, provided that you also convey the machine-readable Corresponding Source under the 223 | terms of this License, in one of these ways: 224 | 225 | * **a)** Convey the object code in, or embodied in, a physical product (including a 226 | physical distribution medium), accompanied by the Corresponding Source fixed on a 227 | durable physical medium customarily used for software interchange. 228 | * **b)** Convey the object code in, or embodied in, a physical product (including a 229 | physical distribution medium), accompanied by a written offer, valid for at least 230 | three years and valid for as long as you offer spare parts or customer support for 231 | that product model, to give anyone who possesses the object code either **(1)** a copy of 232 | the Corresponding Source for all the software in the product that is covered by this 233 | License, on a durable physical medium customarily used for software interchange, for 234 | a price no more than your reasonable cost of physically performing this conveying of 235 | source, or **(2)** access to copy the Corresponding Source from a network server at no 236 | charge. 237 | * **c)** Convey individual copies of the object code with a copy of the written offer to 238 | provide the Corresponding Source. This alternative is allowed only occasionally and 239 | noncommercially, and only if you received the object code with such an offer, in 240 | accord with subsection 6b. 241 | * **d)** Convey the object code by offering access from a designated place (gratis or for 242 | a charge), and offer equivalent access to the Corresponding Source in the same way 243 | through the same place at no further charge. You need not require recipients to copy 244 | the Corresponding Source along with the object code. If the place to copy the object 245 | code is a network server, the Corresponding Source may be on a different server 246 | (operated by you or a third party) that supports equivalent copying facilities, 247 | provided you maintain clear directions next to the object code saying where to find 248 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 249 | you remain obligated to ensure that it is available for as long as needed to satisfy 250 | these requirements. 251 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 252 | other peers where the object code and Corresponding Source of the work are being 253 | offered to the general public at no charge under subsection 6d. 254 | 255 | A separable portion of the object code, whose source code is excluded from the 256 | Corresponding Source as a System Library, need not be included in conveying the 257 | object code work. 258 | 259 | A “User Product” is either **(1)** a “consumer product”, which 260 | means any tangible personal property which is normally used for personal, family, or 261 | household purposes, or **(2)** anything designed or sold for incorporation into a 262 | dwelling. In determining whether a product is a consumer product, doubtful cases 263 | shall be resolved in favor of coverage. For a particular product received by a 264 | particular user, “normally used” refers to a typical or common use of 265 | that class of product, regardless of the status of the particular user or of the way 266 | in which the particular user actually uses, or expects or is expected to use, the 267 | product. A product is a consumer product regardless of whether the product has 268 | substantial commercial, industrial or non-consumer uses, unless such uses represent 269 | the only significant mode of use of the product. 270 | 271 | “Installation Information” for a User Product means any methods, 272 | procedures, authorization keys, or other information required to install and execute 273 | modified versions of a covered work in that User Product from a modified version of 274 | its Corresponding Source. The information must suffice to ensure that the continued 275 | functioning of the modified object code is in no case prevented or interfered with 276 | solely because modification has been made. 277 | 278 | If you convey an object code work under this section in, or with, or specifically for 279 | use in, a User Product, and the conveying occurs as part of a transaction in which 280 | the right of possession and use of the User Product is transferred to the recipient 281 | in perpetuity or for a fixed term (regardless of how the transaction is 282 | characterized), the Corresponding Source conveyed under this section must be 283 | accompanied by the Installation Information. But this requirement does not apply if 284 | neither you nor any third party retains the ability to install modified object code 285 | on the User Product (for example, the work has been installed in ROM). 286 | 287 | The requirement to provide Installation Information does not include a requirement to 288 | continue to provide support service, warranty, or updates for a work that has been 289 | modified or installed by the recipient, or for the User Product in which it has been 290 | modified or installed. Access to a network may be denied when the modification itself 291 | materially and adversely affects the operation of the network or violates the rules 292 | and protocols for communication across the network. 293 | 294 | Corresponding Source conveyed, and Installation Information provided, in accord with 295 | this section must be in a format that is publicly documented (and with an 296 | implementation available to the public in source code form), and must require no 297 | special password or key for unpacking, reading or copying. 298 | 299 | ### 7. Additional Terms 300 | 301 | “Additional permissions” are terms that supplement the terms of this 302 | License by making exceptions from one or more of its conditions. Additional 303 | permissions that are applicable to the entire Program shall be treated as though they 304 | were included in this License, to the extent that they are valid under applicable 305 | law. If additional permissions apply only to part of the Program, that part may be 306 | used separately under those permissions, but the entire Program remains governed by 307 | this License without regard to the additional permissions. 308 | 309 | When you convey a copy of a covered work, you may at your option remove any 310 | additional permissions from that copy, or from any part of it. (Additional 311 | permissions may be written to require their own removal in certain cases when you 312 | modify the work.) You may place additional permissions on material, added by you to a 313 | covered work, for which you have or can give appropriate copyright permission. 314 | 315 | Notwithstanding any other provision of this License, for material you add to a 316 | covered work, you may (if authorized by the copyright holders of that material) 317 | supplement the terms of this License with terms: 318 | 319 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 320 | sections 15 and 16 of this License; or 321 | * **b)** Requiring preservation of specified reasonable legal notices or author 322 | attributions in that material or in the Appropriate Legal Notices displayed by works 323 | containing it; or 324 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 325 | modified versions of such material be marked in reasonable ways as different from the 326 | original version; or 327 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 328 | material; or 329 | * **e)** Declining to grant rights under trademark law for use of some trade names, 330 | trademarks, or service marks; or 331 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 332 | who conveys the material (or modified versions of it) with contractual assumptions of 333 | liability to the recipient, for any liability that these contractual assumptions 334 | directly impose on those licensors and authors. 335 | 336 | All other non-permissive additional terms are considered “further 337 | restrictions” within the meaning of section 10. If the Program as you received 338 | it, or any part of it, contains a notice stating that it is governed by this License 339 | along with a term that is a further restriction, you may remove that term. If a 340 | license document contains a further restriction but permits relicensing or conveying 341 | under this License, you may add to a covered work material governed by the terms of 342 | that license document, provided that the further restriction does not survive such 343 | relicensing or conveying. 344 | 345 | If you add terms to a covered work in accord with this section, you must place, in 346 | the relevant source files, a statement of the additional terms that apply to those 347 | files, or a notice indicating where to find the applicable terms. 348 | 349 | Additional terms, permissive or non-permissive, may be stated in the form of a 350 | separately written license, or stated as exceptions; the above requirements apply 351 | either way. 352 | 353 | ### 8. Termination 354 | 355 | You may not propagate or modify a covered work except as expressly provided under 356 | this License. Any attempt otherwise to propagate or modify it is void, and will 357 | automatically terminate your rights under this License (including any patent licenses 358 | granted under the third paragraph of section 11). 359 | 360 | However, if you cease all violation of this License, then your license from a 361 | particular copyright holder is reinstated **(a)** provisionally, unless and until the 362 | copyright holder explicitly and finally terminates your license, and **(b)** permanently, 363 | if the copyright holder fails to notify you of the violation by some reasonable means 364 | prior to 60 days after the cessation. 365 | 366 | Moreover, your license from a particular copyright holder is reinstated permanently 367 | if the copyright holder notifies you of the violation by some reasonable means, this 368 | is the first time you have received notice of violation of this License (for any 369 | work) from that copyright holder, and you cure the violation prior to 30 days after 370 | your receipt of the notice. 371 | 372 | Termination of your rights under this section does not terminate the licenses of 373 | parties who have received copies or rights from you under this License. If your 374 | rights have been terminated and not permanently reinstated, you do not qualify to 375 | receive new licenses for the same material under section 10. 376 | 377 | ### 9. Acceptance Not Required for Having Copies 378 | 379 | You are not required to accept this License in order to receive or run a copy of the 380 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 381 | using peer-to-peer transmission to receive a copy likewise does not require 382 | acceptance. However, nothing other than this License grants you permission to 383 | propagate or modify any covered work. These actions infringe copyright if you do not 384 | accept this License. Therefore, by modifying or propagating a covered work, you 385 | indicate your acceptance of this License to do so. 386 | 387 | ### 10. Automatic Licensing of Downstream Recipients 388 | 389 | Each time you convey a covered work, the recipient automatically receives a license 390 | from the original licensors, to run, modify and propagate that work, subject to this 391 | License. You are not responsible for enforcing compliance by third parties with this 392 | License. 393 | 394 | An “entity transaction” is a transaction transferring control of an 395 | organization, or substantially all assets of one, or subdividing an organization, or 396 | merging organizations. If propagation of a covered work results from an entity 397 | transaction, each party to that transaction who receives a copy of the work also 398 | receives whatever licenses to the work the party's predecessor in interest had or 399 | could give under the previous paragraph, plus a right to possession of the 400 | Corresponding Source of the work from the predecessor in interest, if the predecessor 401 | has it or can get it with reasonable efforts. 402 | 403 | You may not impose any further restrictions on the exercise of the rights granted or 404 | affirmed under this License. For example, you may not impose a license fee, royalty, 405 | or other charge for exercise of rights granted under this License, and you may not 406 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 407 | that any patent claim is infringed by making, using, selling, offering for sale, or 408 | importing the Program or any portion of it. 409 | 410 | ### 11. Patents 411 | 412 | A “contributor” is a copyright holder who authorizes use under this 413 | License of the Program or a work on which the Program is based. The work thus 414 | licensed is called the contributor's “contributor version”. 415 | 416 | A contributor's “essential patent claims” are all patent claims owned or 417 | controlled by the contributor, whether already acquired or hereafter acquired, that 418 | would be infringed by some manner, permitted by this License, of making, using, or 419 | selling its contributor version, but do not include claims that would be infringed 420 | only as a consequence of further modification of the contributor version. For 421 | purposes of this definition, “control” includes the right to grant patent 422 | sublicenses in a manner consistent with the requirements of this License. 423 | 424 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 425 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 426 | import and otherwise run, modify and propagate the contents of its contributor 427 | version. 428 | 429 | In the following three paragraphs, a “patent license” is any express 430 | agreement or commitment, however denominated, not to enforce a patent (such as an 431 | express permission to practice a patent or covenant not to sue for patent 432 | infringement). To “grant” such a patent license to a party means to make 433 | such an agreement or commitment not to enforce a patent against the party. 434 | 435 | If you convey a covered work, knowingly relying on a patent license, and the 436 | Corresponding Source of the work is not available for anyone to copy, free of charge 437 | and under the terms of this License, through a publicly available network server or 438 | other readily accessible means, then you must either **(1)** cause the Corresponding 439 | Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the 440 | patent license for this particular work, or **(3)** arrange, in a manner consistent with 441 | the requirements of this License, to extend the patent license to downstream 442 | recipients. “Knowingly relying” means you have actual knowledge that, but 443 | for the patent license, your conveying the covered work in a country, or your 444 | recipient's use of the covered work in a country, would infringe one or more 445 | identifiable patents in that country that you have reason to believe are valid. 446 | 447 | If, pursuant to or in connection with a single transaction or arrangement, you 448 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 449 | license to some of the parties receiving the covered work authorizing them to use, 450 | propagate, modify or convey a specific copy of the covered work, then the patent 451 | license you grant is automatically extended to all recipients of the covered work and 452 | works based on it. 453 | 454 | A patent license is “discriminatory” if it does not include within the 455 | scope of its coverage, prohibits the exercise of, or is conditioned on the 456 | non-exercise of one or more of the rights that are specifically granted under this 457 | License. You may not convey a covered work if you are a party to an arrangement with 458 | a third party that is in the business of distributing software, under which you make 459 | payment to the third party based on the extent of your activity of conveying the 460 | work, and under which the third party grants, to any of the parties who would receive 461 | the covered work from you, a discriminatory patent license **(a)** in connection with 462 | copies of the covered work conveyed by you (or copies made from those copies), or **(b)** 463 | primarily for and in connection with specific products or compilations that contain 464 | the covered work, unless you entered into that arrangement, or that patent license 465 | was granted, prior to 28 March 2007. 466 | 467 | Nothing in this License shall be construed as excluding or limiting any implied 468 | license or other defenses to infringement that may otherwise be available to you 469 | under applicable patent law. 470 | 471 | ### 12. No Surrender of Others' Freedom 472 | 473 | If conditions are imposed on you (whether by court order, agreement or otherwise) 474 | that contradict the conditions of this License, they do not excuse you from the 475 | conditions of this License. If you cannot convey a covered work so as to satisfy 476 | simultaneously your obligations under this License and any other pertinent 477 | obligations, then as a consequence you may not convey it at all. For example, if you 478 | agree to terms that obligate you to collect a royalty for further conveying from 479 | those to whom you convey the Program, the only way you could satisfy both those terms 480 | and this License would be to refrain entirely from conveying the Program. 481 | 482 | ### 13. Use with the GNU Affero General Public License 483 | 484 | Notwithstanding any other provision of this License, you have permission to link or 485 | combine any covered work with a work licensed under version 3 of the GNU Affero 486 | General Public License into a single combined work, and to convey the resulting work. 487 | The terms of this License will continue to apply to the part which is the covered 488 | work, but the special requirements of the GNU Affero General Public License, section 489 | 13, concerning interaction through a network will apply to the combination as such. 490 | 491 | ### 14. Revised Versions of this License 492 | 493 | The Free Software Foundation may publish revised and/or new versions of the GNU 494 | General Public License from time to time. Such new versions will be similar in spirit 495 | to the present version, but may differ in detail to address new problems or concerns. 496 | 497 | Each version is given a distinguishing version number. If the Program specifies that 498 | a certain numbered version of the GNU General Public License “or any later 499 | version” applies to it, you have the option of following the terms and 500 | conditions either of that numbered version or of any later version published by the 501 | Free Software Foundation. If the Program does not specify a version number of the GNU 502 | General Public License, you may choose any version ever published by the Free 503 | Software Foundation. 504 | 505 | If the Program specifies that a proxy can decide which future versions of the GNU 506 | General Public License can be used, that proxy's public statement of acceptance of a 507 | version permanently authorizes you to choose that version for the Program. 508 | 509 | Later license versions may give you additional or different permissions. However, no 510 | additional obligations are imposed on any author or copyright holder as a result of 511 | your choosing to follow a later version. 512 | 513 | ### 15. Disclaimer of Warranty 514 | 515 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 516 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 517 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 518 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 519 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 520 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 521 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 522 | 523 | ### 16. Limitation of Liability 524 | 525 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 526 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 527 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 528 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 529 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 530 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 531 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 532 | POSSIBILITY OF SUCH DAMAGES. 533 | 534 | ### 17. Interpretation of Sections 15 and 16 535 | 536 | If the disclaimer of warranty and limitation of liability provided above cannot be 537 | given local legal effect according to their terms, reviewing courts shall apply local 538 | law that most closely approximates an absolute waiver of all civil liability in 539 | connection with the Program, unless a warranty or assumption of liability accompanies 540 | a copy of the Program in return for a fee. 541 | 542 | _END OF TERMS AND CONDITIONS_ 543 | 544 | ## How to Apply These Terms to Your New Programs 545 | 546 | If you develop a new program, and you want it to be of the greatest possible use to 547 | the public, the best way to achieve this is to make it free software which everyone 548 | can redistribute and change under these terms. 549 | 550 | To do so, attach the following notices to the program. It is safest to attach them 551 | to the start of each source file to most effectively state the exclusion of warranty; 552 | and each file should have at least the “copyright” line and a pointer to 553 | where the full notice is found. 554 | 555 | 556 | Copyright (C) 557 | 558 | This program is free software: you can redistribute it and/or modify 559 | it under the terms of the GNU General Public License as published by 560 | the Free Software Foundation, either version 3 of the License, or 561 | (at your option) any later version. 562 | 563 | This program is distributed in the hope that it will be useful, 564 | but WITHOUT ANY WARRANTY; without even the implied warranty of 565 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 566 | GNU General Public License for more details. 567 | 568 | You should have received a copy of the GNU General Public License 569 | along with this program. If not, see . 570 | 571 | Also add information on how to contact you by electronic and paper mail. 572 | 573 | If the program does terminal interaction, make it output a short notice like this 574 | when it starts in an interactive mode: 575 | 576 | Copyright (C) 577 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 578 | This is free software, and you are welcome to redistribute it 579 | under certain conditions; type 'show c' for details. 580 | 581 | The hypothetical commands `show w` and `show c` should show the appropriate parts of 582 | the General Public License. Of course, your program's commands might be different; 583 | for a GUI interface, you would use an “about box”. 584 | 585 | You should also get your employer (if you work as a programmer) or school, if any, to 586 | sign a “copyright disclaimer” for the program, if necessary. For more 587 | information on this, and how to apply and follow the GNU GPL, see 588 | <>. 589 | 590 | The GNU General Public License does not permit incorporating your program into 591 | proprietary programs. If your program is a subroutine library, you may consider it 592 | more useful to permit linking proprietary applications with the library. If this is 593 | what you want to do, use the GNU Lesser General Public License instead of this 594 | License. But first, please read 595 | <>. 596 | --------------------------------------------------------------------------------