Skip to content

Commit

Permalink
Merge pull request #36 from jcrodriguez1989/develop
Browse files Browse the repository at this point in the history
Release v0.2.3
  • Loading branch information
jcrodriguez1989 authored May 5, 2023
2 parents a8ff288 + cbf468e commit ab872f9
Show file tree
Hide file tree
Showing 16 changed files with 321 additions and 174 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: chatgpt
Type: Package
Title: Interface to 'ChatGPT' from R
Version: 0.2.2
Version: 0.2.3
Authors@R: c(
person(
given = "Juan Cruz", family = "Rodriguez", role = c("aut", "cre"),
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ export(explain_code)
export(find_issues_in_code)
export(optimize_code)
export(refactor_code)
export(reset_chat_session)
importFrom(clipr,read_clip)
importFrom(httr,POST)
importFrom(httr,add_headers)
importFrom(httr,content)
importFrom(httr,content_type_json)
importFrom(httr,use_proxy)
importFrom(jsonlite,toJSON)
importFrom(miniUI,gadgetTitleBar)
importFrom(miniUI,miniPage)
Expand All @@ -25,6 +27,7 @@ importFrom(shiny,actionButton)
importFrom(shiny,br)
importFrom(shiny,icon)
importFrom(shiny,observeEvent)
importFrom(shiny,onStop)
importFrom(shiny,runGadget)
importFrom(shiny,stopApp)
importFrom(shiny,textAreaInput)
Expand Down
13 changes: 9 additions & 4 deletions R/addins.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ run_addin <- function(addin_name) {
}
modifyRange(doc_range, out, doc_context$id)
} else if (as.logical(Sys.getenv("OPENAI_VERBOSE", TRUE))) {
cat(paste0("\n*** ChatGPT output:\n\n", out, "\n"))
message(paste0("\n*** ChatGPT output:\n\n", out, "\n"))
} else {
warning("Please set one of `OPENAI_ADDIN_REPLACE=TRUE` or `OPENAI_VERBOSE=TRUE`")
}
Expand All @@ -58,11 +58,12 @@ run_addin_refactor_code <- function() run_addin("refactor_code")
#' Opens an interactive chat session with ChatGPT
#'
#' @importFrom miniUI gadgetTitleBar miniPage
#' @importFrom shiny actionButton br icon observeEvent runGadget stopApp textAreaInput
#' @importFrom shiny actionButton br icon observeEvent onStop runGadget stopApp textAreaInput
#' @importFrom shiny updateTextAreaInput wellPanel
#' @importFrom utils getFromNamespace
#'
run_addin_ask_chatgpt <- function() {
reset_chat_session()
ui <- miniPage(wellPanel(
gadgetTitleBar("Ask ChatGPT", NULL),
textAreaInput("question", "Question:", width = "100%", height = "150px"),
Expand All @@ -74,11 +75,15 @@ run_addin_ask_chatgpt <- function() {
observeEvent(input$ask_button, {
chatgpt_reply <- ask_chatgpt(input$question)
if (as.logical(Sys.getenv("OPENAI_VERBOSE", TRUE))) {
cat(paste0("\n*** ChatGPT output:\n\n", chatgpt_reply, "\n"))
message(paste0("\n*** ChatGPT output:\n\n", chatgpt_reply, "\n"))
}
updateTextAreaInput(session, "answer", value = chatgpt_reply)
})
observeEvent(input$done, stopApp())
observeEvent(input$done, {
reset_chat_session()
stopApp()
})
onStop(reset_chat_session)
}
runGadget(ui, server)
}
15 changes: 14 additions & 1 deletion R/ask_chatgpt.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#' Ask ChatGPT
#'
#' Note: See also `reset_chat_session`.
#'
#' @param question The question to ask ChatGPT.
#'
#' @examples
Expand All @@ -12,5 +14,16 @@
#' @export
#'
ask_chatgpt <- function(question) {
parse_response(gpt_get_completions(question))
# Get the existing chat session messages, and add the new message.
chat_session_messages <- append(get("chat_session_messages", envir = .state), list(
list(role = "user", content = question)
))
# Send the query to ChatGPT.
chat_gpt_reply <- parse_response(gpt_get_completions(question, messages = chat_session_messages))
chat_session_messages <- append(chat_session_messages, list(
list(role = "assistant", content = chat_gpt_reply)
))
# Update the chat session messages with the new question and the reply.
assign("chat_session_messages", chat_session_messages, .state)
chat_gpt_reply
}
13 changes: 13 additions & 0 deletions R/chatgpt-package.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#' 'OpenAI's 'ChatGPT' <https://chat.openai.com/> coding assistant for 'RStudio'. A set
#' of functions and 'RStudio' addins that aim to help the R developer in tedious coding tasks.
#'
"_PACKAGE"

.state <- new.env(parent = emptyenv())

# Empty chat session messages at startup.
assign(
"chat_session_messages",
list(list(role = "system", content = "You are a helpful assistant.")),
envir = .state
)
2 changes: 1 addition & 1 deletion R/create_unit_tests.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
create_unit_tests <- function(code = clipr::read_clip(allow_non_interactive = TRUE)) {
code <- paste(gsub('"', "'", code), collapse = "\n")
prompt <- paste0(
'Using testthat 3e, version over 3.0.0, create a full testthat file, with test cases for the ',
"Using testthat 3e, version over 3.0.0, create a full testthat file, with test cases for the ",
'following R code: "', code, '"'
)
parse_response(gpt_get_completions(prompt))
Expand Down
73 changes: 50 additions & 23 deletions R/gpt_get_completions.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@
#'
#' @param prompt The prompt to generate completions for.
#' @param openai_api_key OpenAI's API key.
#' @param messages Available variable, to send the needed messages list to ChatGPT.
#'
#' @importFrom httr add_headers content content_type_json POST
#' @importFrom httr add_headers content content_type_json POST use_proxy
#' @importFrom jsonlite toJSON
#'
gpt_get_completions <- function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY")) {
gpt_get_completions <- function(prompt, openai_api_key = Sys.getenv("OPENAI_API_KEY"),
messages = NULL) {
if (nchar(openai_api_key) == 0) {
stop("`OPENAI_API_KEY` not provided.")
}
# See https://platform.openai.com/docs/api-reference/chat
# and https://beta.openai.com/docs/api-reference/completions/create
model <- Sys.getenv("OPENAI_MODEL", "gpt-3.5-turbo")
# See https://platform.openai.com/docs/api-reference/chat .
params <- list(
model = model,
model = Sys.getenv("OPENAI_MODEL", "gpt-3.5-turbo"),
max_tokens = as.numeric(Sys.getenv("OPENAI_MAX_TOKENS", 256)),
temperature = as.numeric(Sys.getenv("OPENAI_TEMPERATURE", 1)),
top_p = as.numeric(Sys.getenv("OPENAI_TOP_P", 1)),
frequency_penalty = as.numeric(Sys.getenv("OPENAI_FREQUENCY_PENALTY", 0)),
presence_penalty = as.numeric(Sys.getenv("OPENAI_PRESENCE_PENALTY", 0))
)
if (as.logical(Sys.getenv("OPENAI_VERBOSE", TRUE))) {
cat(paste0("\n*** ChatGPT input:\n\n", prompt, "\n"))
message(paste0("\n*** ChatGPT input:\n\n", prompt, "\n"))
}
if (grepl("gpt-3.5-turbo", model)) {
return_language <- Sys.getenv("OPENAI_RETURN_LANGUAGE")
if (nchar(return_language) > 0) {
return_language <- paste0("You return all your replies in ", return_language, ".")
}
return_language <- Sys.getenv("OPENAI_RETURN_LANGUAGE")
if (nchar(return_language) > 0) {
return_language <- paste0("You return all your replies in ", return_language, ".")
}
if (is.null(messages)) {
messages <- list(
list(
role = "system",
Expand All @@ -39,22 +39,49 @@ gpt_get_completions <- function(prompt, openai_api_key = Sys.getenv("OPENAI_API_
),
list(role = "user", content = prompt)
)
} else {
# If there are messages provided, then add the `return_language` if available.
messages[[which(sapply(messages, function(message) message$role == "system"))]]$content <-
paste(
messages[[which(sapply(messages, function(message) message$role == "system"))]]$content,
return_language
)
}
# Get the proxy to use, if provided.
proxy <- NULL
if (nchar(Sys.getenv("OPENAI_PROXY")) > 0) {
proxy <- Sys.getenv("OPENAI_PROXY")
if (grepl("^(?:\\d{1,3}\\.){3}\\d{1,3}:\\d{2,5}$", proxy)) {
proxy <- use_proxy(gsub(":.*", "", proxy), as.numeric(gsub(".*:", "", proxy)))
} else {
stop("Invalid proxy provided in `OPENAI_PROXY`: ", proxy)
}
}
# Run the API query.
final_res <- list()
keep_querying <- TRUE
while (keep_querying) {
post_res <- POST(
"https://api.openai.com/v1/chat/completions",
add_headers("Authorization" = paste("Bearer", openai_api_key)),
content_type_json(),
body = toJSON(c(params, list(messages = messages)), auto_unbox = TRUE)
body = toJSON(c(params, list(messages = messages)), auto_unbox = TRUE),
proxy
)
} else {
post_res <- POST(
"https://api.openai.com/v1/completions",
add_headers("Authorization" = paste("Bearer", openai_api_key)),
content_type_json(),
body = toJSON(c(params, list(prompt = prompt)), auto_unbox = TRUE)
if (!post_res$status_code %in% 200:299) {
stop(content(post_res))
}
post_res <- content(post_res)
final_res <- append(final_res, list(post_res))
# In the case the finish_reason is the length of the message, then we need to keep querying.
keep_querying <- all(sapply(post_res$choices, function(x) x$finish_reason == "length"))
# And update the messages sent to ChatGPT, in order to continue the current session.
messages <- append(
append(
messages, list(list(role = "assistant", content = parse_response(list(post_res))))
),
list(list(role = "user", content = "continue"))
)
}
if (!post_res$status_code %in% 200:299) {
stop(content(post_res))
}
content(post_res)
final_res
}
15 changes: 6 additions & 9 deletions R/parse_response.R
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
#' Parse OpenAI API Response
#'
#' Takes the raw response from the OpenAI API and extracts the text content from it.
#' This function is currently designed to differentiate between gpt-3.5-turbo and others.
#'
#' @param raw_response The raw response object returned by the OpenAI API.
#' @param raw_responses The raw response object returned by the OpenAI API.
#'
#' @return Returns a character vector containing the text content of the response.
#'
parse_response <- function(raw_response) {
# If the model is from the `gpt-3.5-turbo` family, it parses in a different way.
if (grepl("gpt-3.5-turbo", Sys.getenv("OPENAI_MODEL", "gpt-3.5-turbo"))) {
trimws(sapply(raw_response$choices, function(x) x$message$content))
} else {
trimws(sapply(raw_response$choices, function(x) x$text))
}
parse_response <- function(raw_responses) {
# Parse the message content of the list of raw_responses. Trim those message, and paste them.
paste(trimws(sapply(raw_responses, function(response) {
sapply(response$choices, function(x) x$message$content)
})), collapse = "")
}
15 changes: 15 additions & 0 deletions R/reset_chat_session.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#' Reset Chat Session
#'
#' This function is intended to be used with `ask_chatgpt`. If we are using `ask_chatgpt` to chat with ChatGPT, and
#' we want to start a new conversation, we must call `reset_chat_session`.
#'
#' @param system_role ChatGPT's role as an AI assistant.
#'
#' @export
#'
reset_chat_session <- function(system_role = "You are a helpful assistant.") {
assign(
"chat_session_messages", list(list(role = "system", content = system_role)),
envir = .state
)
}
30 changes: 27 additions & 3 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ output: github_document

```{r, include = FALSE}
library("chatgpt")
Sys.setenv("OPENAI_VERBOSE" = FALSE)
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
Expand All @@ -22,9 +23,16 @@ knitr::opts_chunk$set(

## Installation

You can install the development version of {chatgpt} from [GitHub](https://github.com/jcrodriguez1989/chatgpt) with:
Install the current released version of `{chatgpt}` from
[CRAN](https://cran.r-project.org/package=chatgpt):

``` r
```{r eval = FALSE}
install.packages("chatgpt")
```

Or install the development version from [GitHub](https://github.com/jcrodriguez1989/chatgpt) with:

```{r eval = FALSE}
# install.packages("remotes")
remotes::install_github("jcrodriguez1989/chatgpt")
```
Expand Down Expand Up @@ -67,11 +75,22 @@ cat(paste(

**Note:** When no code is selected, it will use the whole file's code.

## Common Errors

#### You exceeded your current quota, please check your plan and billing details

```r
Error in gpt_get_completions:
list(message = "You exceeded your current quota, please check your plan and billing details.", type = "insufficient_quota", param = NULL, code = NULL)
```

To fix this, you need to **provide a billing method** in OpenAI. More information can be found in this [article](https://help.openai.com/en/articles/6891831-error-code-429-you-exceeded-your-current-quota-please-check-your-plan-and-billing-details).

## Code Examples

```{r, echo=FALSE, results='asis'}
pkg_name <- "chatgpt"
exported_functions <- sort(getNamespaceExports(pkg_name))
exported_functions <- setdiff(sort(getNamespaceExports(pkg_name)), "reset_chat_session")
examples_usage <- sapply(exported_functions, function(exported_function) {
function_example <- example(exported_function, pkg_name, character.only = TRUE, give.lines = TRUE)
function_example <- function_example[grepl("^##D ", function_example)]
Expand Down Expand Up @@ -109,6 +128,11 @@ Sys.setenv("OPENAI_RETURN_LANGUAGE" = "Español")
cat(chatgpt::explain_code("for (i in 1:10) {\n print(i ** 2)\n}"))
```

### Use ChatGPT behind a proxy

In order to run ChatGPT queries behind a proxy, set the `OPENAI_PROXY` environment variable with a valid `IP:PORT` proxy.
E.g., `Sys.setenv("OPENAI_PROXY" = "81.94.255.13:8080")`.

### ChatGPT Model Tweaks

ChatGPT model parameters can be tweaked by using environment variables.
Expand Down
Loading

0 comments on commit ab872f9

Please sign in to comment.