From 4c0ba72e4df5c43d4327b7b22a5b2a8a37f63861 Mon Sep 17 00:00:00 2001 From: abigailbarnett Date: Fri, 11 Sep 2026 19:25:48 +0000 Subject: [PATCH 1/2] New features --- DESCRIPTION | 1 + NAMESPACE | 3 + R/chatrbox_ai_services.R | 14 +++ R/documentation_interpolation.R | 123 +++++++++++++++++++++ R/generate_ai_response.R | 41 +++++-- R/object_methods.R | 37 +++++++ inst/prompt/prompt.md | 5 + man/ChatRBox_obj.Rd | 4 + man/ChatRBox_update.Rd | 6 + man/R6.Rd | 8 ++ man/build_tool_docs.Rd | 54 +++++++++ man/code_extract.Rd | 2 +- man/code_remove.Rd | 2 +- man/examples/examples_tool_docs.R | 22 ++++ man/object_generate.Rd | 6 + man/object_update.Rd | 6 + man/property_generate.Rd | 6 + tests/testthat/test_generate_ai_response.R | 35 ++++++ tests/testthat/test_object_methods.R | 34 ++++++ tests/testthat/test_tool_documentation.R | 39 +++++++ 20 files changed, 436 insertions(+), 12 deletions(-) create mode 100644 R/documentation_interpolation.R create mode 100644 man/build_tool_docs.Rd create mode 100644 man/examples/examples_tool_docs.R create mode 100644 tests/testthat/test_tool_documentation.R diff --git a/DESCRIPTION b/DESCRIPTION index 5803459..2f7a40e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -32,6 +32,7 @@ Imports: S7, stats, stringr, + tools, yaml Suggests: config, diff --git a/NAMESPACE b/NAMESPACE index 02a25c5..c7d6ce1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -10,6 +10,7 @@ export(add_http_status_guidance) export(apply_httr2_config) export(build_api_client) export(build_api_client_env) +export(build_tool_docs) export(check_duplicate_names) export(check_names) export(code_extract) @@ -111,6 +112,8 @@ importFrom(stats,setNames) importFrom(stringr,str_detect) importFrom(stringr,str_extract) importFrom(stringr,str_remove_all) +importFrom(tools,Rd2txt) +importFrom(tools,Rd_db) importFrom(utils,capture.output) importFrom(utils,modifyList) importFrom(utils,str) diff --git a/R/chatrbox_ai_services.R b/R/chatrbox_ai_services.R index e134d94..6a0e29d 100644 --- a/R/chatrbox_ai_services.R +++ b/R/chatrbox_ai_services.R @@ -53,6 +53,10 @@ ChatRBox <- R6::R6Class( #' Environment to which data frames are added. Defaults to \code{NULL} (creates a new environment). #' @param object_env (`environment()`)\cr #' Environment in which past API service/tool outputs are stored as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to \code{NULL} (creates a new environment). + #' @param tool_docs (`logical(1)`)\cr + #' Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{FALSE}. + #' @param include_examples (`logical(1)`)\cr + #' Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}. #' @param prompt_template (`character(1)`)\cr #' Prompt template for the AI, for parameter extraction and user interaction. Defaults to the ChatRBox conversational template. #' @param summary_list (`list()`)\cr @@ -84,6 +88,8 @@ ChatRBox <- R6::R6Class( tools_env = NULL, data_env = NULL, object_env = NULL, + tool_docs = FALSE, + include_examples = FALSE, prompt_template = ChatRBox::load_prompt_template(), summary_list = list(), final_summary_prompt = "", @@ -104,6 +110,8 @@ ChatRBox <- R6::R6Class( tools_env = tools_env, data_env = data_env, object_env = object_env, + tool_docs = tool_docs, + include_examples = include_examples, prompt_template = prompt_template, summary_list = summary_list, final_summary_prompt = final_summary_prompt, @@ -285,6 +293,8 @@ ChatRBox <- R6::R6Class( #' @param tools_env Environment. Tool functions are added to this environment. Defaults to current \code{S7} object tools environment. #' @param data_env Environment. Data frames are added to this environment. Defaults to current \code{S7} object data environment. #' @param object_env Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to current \code{S7} object object environment. +#' @param tool_docs Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{S7} object current state to preserve original user intent during update. +#' @param include_examples Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}. #' @param prompt_template Character. General AI prompt for outputting parameter key-value pairs for available API services/tools. Defaults to current \code{S7} object prompt, likely interpolating function paths and arguments. #' @param summary_list List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list. #' @param final_summary_prompt Character. Single synthesis instruction steering \code{summarize = "final"}, as opposed to the per-service \code{summary_list} used by \code{summarize = TRUE}. Supplied as a literal string or the path to a Markdown file, resolved identically to \code{prompt_template}. Updated additively; defaults to the current object value. @@ -312,6 +322,8 @@ ChatRBox_update <- function(object, tools_env = object$chat_object@tools_env, data_env = object$chat_object@data_env, object_env = object$chat_object@object_env, + tool_docs = isTRUE(nzchar(object$chat_object@tool_docs)), + include_examples = FALSE, prompt_template = object$chat_object@prompt_template, summary_list = list(), final_summary_prompt = object$chat_object@final_summary_prompt, @@ -331,6 +343,8 @@ ChatRBox_update <- function(object, tools_env = tools_env, data_env = data_env, object_env = object_env, + tool_docs = tool_docs, + include_examples = include_examples, prompt_template = prompt_template, summary_list = summary_list, final_summary_prompt = final_summary_prompt, diff --git a/R/documentation_interpolation.R b/R/documentation_interpolation.R new file mode 100644 index 0000000..c45d4e4 --- /dev/null +++ b/R/documentation_interpolation.R @@ -0,0 +1,123 @@ +#' Builds Tool Documentation for AI Prompt Interpolation +#' +#' This function renders the help documentation of every tool function contained within a tools environment into a single Markdown-formatted character string. The result is intended for interpolation into the AI prompt via \code{\link{object_generate}}, providing the LLM with detailed reference documentation for each available tool alongside the service paths and arguments already supplied by \code{\link{get_function_path}} and \code{\link{get_function_args}}. +#' +#' For each tool function, the package documentation (Rd) is located and rendered to plain text. Where a function has no associated Rd documentation, or is not defined within a package namespace, the function falls back to its argument signature so that the LLM is never left without context. +#' @details +#' Documentation is extracted from the installed package Rd database using \code{tools::Rd_db} and rendered with \code{tools::Rd2txt}, so it reuses the tool author's own maintained documentation rather than duplicating it. The \code{\\examples} section is dropped by default to keep the interpolated prompt concise, and may be retained via \code{include_examples}. All curly braces in the assembled documentation are escaped exactly once so the returned string is safe to pass through \code{glue::glue()}, consistent with how \code{paths_string}, \code{args_string} and \code{past_outputs} are interpolated into \code{prompt_template} within \code{\link{object_generate}}. This function underpins the optional \code{tool_docs} argument of \code{\link{ChatRBox}}, \code{\link{object_generate}} and \code{\link{ChatRBox_update}}. When \code{tool_docs = TRUE}, the returned string is interpolated into the \code{{tool_docs}} placeholder of the default prompt template. Otherwise, the default prompt remains which only provides LLMs with service names and arguments. Notably, this function locates tool functions by searching the global environment, such that it was intended for package functions supplied to chatbots in R using \code{pkg::fn()} notation. Written functions and API endpoints do not have documentation suitable for rendering via this method. +#' @param tools_env Environment. Environment containing tool functions, as stored in the \code{tools_env} property of a \code{\link{ChatRBox_obj}} \code{S7} object. Defaults to \code{NULL}, which returns an empty string. +#' @param width Integer. Wrapping width passed to \code{tools::Rd2txt}. Defaults to \code{1000}. +#' @param include_examples Logical. Whether to retain the \code{\\examples} section of each tool's Rd documentation. Defaults to \code{FALSE} to keep the interpolated prompt concise. +#' @return A brace-escaped character string documenting each tool function, separated by horizontal rules. Returns \code{""} when \code{tools_env} is \code{NULL}, is not an environment, or contains no functions. +#' @seealso \code{\link{object_generate}}, \code{\link{get_function_path}}, \code{\link{get_function_args}}, \code{\link{load_prompt_template}} +#' @importFrom tools Rd_db Rd2txt +#' @importFrom utils capture.output +#' @example man/examples/examples_tool_docs.R +#' @export +build_tool_docs <- function(tools_env = NULL, + width = 1000L, + include_examples = FALSE) { + + if (is.null(tools_env) || !is.environment(tools_env)) return("") + + nms <- ls(envir = tools_env) + nms <- nms[vapply(nms, function(n) is.function(get(n, envir = tools_env)), + logical(1))] + if (length(nms) == 0L) return("") + + blocks <- vapply(nms, function(service_name) { + fun <- get(service_name, envir = tools_env) + docs <- .tool_documentation(fun, width = width, + include_examples = include_examples) + if (any_is_empty(docs)) { + docs <- paste0("(No documentation available. Arguments: ", + paste(names(formals(fun)), collapse = ", "), ")") + } + paste0("### Service: `", service_name, "`\n\n", docs) + }, character(1)) + + .escape_braces(paste(blocks, collapse = "\n\n---\n\n")) +} + +#' @noRd +.escape_braces <- function(text) { + if (any_is_empty(text)) return("") + text <- gsub("{", "{{", text, fixed = TRUE) + text <- gsub("}", "}}", text, fixed = TRUE) + text +} + +#' Deliberately does NOT escape braces; that is done once in build_tool_docs(). +#' @noRd +.clean_tool_docs <- function(text = NULL) { + if (any_is_empty(text)) return("") + text <- gsub("_\b", "", text, fixed = TRUE) + text <- gsub("\b", "", text, fixed = TRUE) + text <- gsub("\n{3,}", "\n\n", text) + trimws(text) +} + +#' @noRd +.tool_documentation <- function(fun, + width = 1000L, + include_examples = FALSE) { + if (!is.function(fun)) return(NULL) + + ns <- environment(fun) + if (is.null(ns) || !isNamespace(ns)) { + args <- paste(names(formals(fun)), collapse = ", ") + return(.clean_tool_docs(paste0( + "Arguments: ", if (nzchar(args)) args else "none", + ". (No package documentation available.)"))) + } + + pkg <- getNamespaceName(ns) + rd <- tryCatch(.find_rd(pkg, fun), error = function(e) NULL) + if (is.null(rd)) { + args <- paste(names(formals(fun)), collapse = ", ") + return(.clean_tool_docs(paste0( + "Arguments: ", if (nzchar(args)) args else "none", + ". (No Rd documentation in package ", pkg, ".)"))) + } + + if (!isTRUE(include_examples)) { + keep <- vapply(rd, function(node) { + tag <- attr(node, "Rd_tag", exact = TRUE) + is.null(tag) || tag != "\\examples" + }, logical(1)) + rd <- structure(rd[keep], class = class(rd), Rd_tag = attr(rd, "Rd_tag")) + } + + txt <- utils::capture.output( + tools::Rd2txt(rd, options = list(underline_titles = FALSE, + width = width, code_quote = FALSE)) + ) + .clean_tool_docs(paste(txt, collapse = "\n")) +} + +#' @noRd +.find_rd <- function(pkg, fun) { + fun_name <- NULL + ns <- asNamespace(pkg) + for (nm in getNamespaceExports(pkg)) { + obj <- tryCatch(get(nm, envir = ns), error = function(e) NULL) + if (!is.null(obj) && identical(obj, fun)) { + fun_name <- nm + break + } + } + if (is.null(fun_name)) return(NULL) + db <- tryCatch(tools::Rd_db(pkg), error = function(e) NULL) + if (is.null(db) || length(db) == 0L) return(NULL) + aliases <- lapply(db, function(rd) { + tags <- vapply(rd, function(node) { + tag <- attr(node, "Rd_tag", exact = TRUE) + if (is.null(tag)) "" else tag + }, character(1)) + unlist(lapply(rd[tags == "\\alias"], + function(a) trimws(paste(unlist(a), collapse = "")))) + }) + hit <- which(vapply(aliases, function(a) fun_name %in% a, logical(1))) + if (length(hit) == 0L) return(NULL) + db[[hit[1]]] +} diff --git a/R/generate_ai_response.R b/R/generate_ai_response.R index 59bd6e6..27d7bc9 100644 --- a/R/generate_ai_response.R +++ b/R/generate_ai_response.R @@ -1,7 +1,28 @@ -# Supported code block languages for extraction/removal -# Extend this vector to support additional structured output formats -# See CONTRIBUTING.md ('Adding a New Structured Output Language') -.SUPPORTED_CODE_LANGUAGES <- c("json", "display") +# Supported code block languages for extraction/removal. +# Extend this vector to support additional structured output formats. +# See CONTRIBUTING.md ('Adding a New Structured Output Language'). +.SUPPORTED_CODE_LANGUAGES <- c("json", "display", "yaml", "yml", "tsv", "csv", "md") + +#' @noRd +.code_block <- function(string = NULL, + language = NULL, + action = c("extract", "remove")) { + + if (any_is_empty(string)) stop("string is empty") + if (any_is_empty(language)) stop("language is empty") + + action <- match.arg(action) + + if (identical(action, "extract")) { + stringr::str_extract( + string, + glue::glue("(?s)(?<=```{language}\n)(.+?)(?=\n?```)")) + } else { + stringr::str_remove_all( + string, + glue::glue("(?s)```{language}(?=\\s)\\s?.+?```")) + } +} #' Redacts Sensitive Values from an Argument List for Safe Display #' @@ -53,7 +74,7 @@ redact_sensitive <- function(x, #' This function extracts the first code block of a specified language from a Markdown-formatted string. The extracted block excludes language tags and code fences. #' #' @param string Character. The input string containing Markdown code block(s). Required. -#' @param language Character. The language tag of the code block to extract. Defaults to "json". +#' @param language Character. The language tag of the code block. One of the supported tags in \code{.SUPPORTED_CODE_LANGUAGES} (\code{"json"}, \code{"display"}, \code{"yaml"}, \code{"yml"}, \code{"tsv"}, \code{"csv"}, \code{"md"}). This may be extended by adding to the vector assignment. See CONTRIBUTING.md for further detail. Defaults to "json". #' @return The contents of the first extracted code block. #' @details #' This function is used in \code{\link{llm_api_result}} to parse the LLM response. The ChatRBox AI response given the default prompt should contain both a JSON object and text output, if an API service/tool has been used. The JSON object is used to populate the chosen API service/tool function with parameters from the input question, whereas the explanation is for user interaction. Hence, these are employed differently in downstream workflows. @@ -64,7 +85,7 @@ redact_sensitive <- function(x, #' @importFrom glue glue #' @example man/examples/examples_parse.R #' @export -code_extract <- function(string = NULL, +code_extract <- function(string = NULL, language = "json") { if (any_is_empty(string)) stop("string is empty") @@ -73,7 +94,7 @@ code_extract <- function(string = NULL, language <- match.arg(arg = language, choices = .SUPPORTED_CODE_LANGUAGES) - stringr::str_extract(string, glue::glue("(?s)(?<=```{language}\n)(.+?)(?=\n?```)")) + .code_block(string = string, language = language, action = "extract") } #' Removes Code Block from Markdown String @@ -81,7 +102,7 @@ code_extract <- function(string = NULL, #' This function removes all code blocks of a specified language from a Markdown-formatted string. The string is returned excluding code blocks and code fences. #' #' @param string Character. The input string containing Markdown code block(s). Required. -#' @param language Character. The language tag of the code block to remove. Defaults to "json". +#' @param language Character. The language tag of the code block. One of the supported tags in \code{.SUPPORTED_CODE_LANGUAGES} (\code{"json"}, \code{"display"}, \code{"yaml"}, \code{"yml"}, \code{"tsv"}, \code{"csv"}, \code{"md"}). This may be extended by adding to the vector assignment. See CONTRIBUTING.md for further detail. Defaults to "json". #' @return A character string with specified code blocks and fences removed. #' @details #' This function is used in \code{\link{llm_api_result}} to parse text and code components from the LLM response. The ChatRBox AI response given the default prompt should contain both a JSON object and plain text output, if an API service/tool has been used. The JSON object is used to populate the chosen API service/tool function with parameters from the input question, whereas the explanation is for user interaction. Hence, these are employed differently in downstream workflows. @@ -92,7 +113,7 @@ code_extract <- function(string = NULL, #' @importFrom glue glue #' @example man/examples/examples_parse.R #' @export -code_remove <- function(string = NULL, +code_remove <- function(string = NULL, language = "json") { if (any_is_empty(string)) stop("string is empty") @@ -101,7 +122,7 @@ code_remove <- function(string = NULL, language <- match.arg(arg = language, choices = .SUPPORTED_CODE_LANGUAGES) - stringr::str_remove_all(string, glue::glue("(?s)```{language}\\n?.+?```")) + .code_block(string = string, language = language, action = "remove") } #' Returns Second Value from JSON Object diff --git a/R/object_methods.R b/R/object_methods.R index 9f1d56c..e33f29e 100644 --- a/R/object_methods.R +++ b/R/object_methods.R @@ -13,6 +13,7 @@ #' \item{object_env}{Environment. Environment containing past API/tool outputs stored as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to empty environment.} #' \item{paths_string}{Character. Concatenated function paths. Defaults to \code{character(0)}.} #' \item{args_string}{Character. Concatenated function paths with arguments. Defaults to \code{character(0)}.} +#' \item{tool_docs}{Character. Rendered tool documentation interpolated into the prompt when \code{tool_docs = TRUE}. Defaults to \code{character(0)}.} #' \item{prompt_template}{Character. Template used to generate AI prompt. This may refer to \code{paths_string} and \code{args_string} which append using \code{glue::glue()}. Defaults to \code{character(0)}.} #' \item{summary_list}{List. Named list of instructions for AI-enabled summaries. Defaults to empty list.} #' \item{final_summary_prompt}{Character. Single synthesis instruction steering \code{summarize = "final"}. Defaults to an empty string.} @@ -29,6 +30,7 @@ #' @param object_env Environment. Environment containing past API/tool outputs stored as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to empty environment. #' @param paths_string Character. Concatenated function paths. Defaults to \code{character(0)}. #' @param args_string Character. Concatenated function paths with arguments. Defaults to \code{character(0)}. +#' @param tool_docs Character. Rendered tool documentation interpolated into the prompt when \code{tool_docs = TRUE}. Defaults to \code{character(0)}. #' @param prompt_template Character. Template used to generate AI prompt. Defaults to \code{character(0)}. #' @param summary_list List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list. #' @param final_summary_prompt Character. Single synthesis instruction steering \code{summarize = "final"}. Unlike the per-service \code{summary_list}, this is a single prompt applied to the one holistic answer. Supplied as a literal string or the path to a Markdown file, resolved identically to \code{prompt_template}. Defaults to an empty string. @@ -49,6 +51,7 @@ ChatRBox_obj <- S7::new_class( object_env = S7::class_environment, paths_string = S7::class_character, args_string = S7::class_character, + tool_docs = S7::class_character, prompt_template = S7::class_character, summary_list = S7::class_list, final_summary_prompt = S7::class_character, @@ -70,6 +73,8 @@ ChatRBox_obj <- S7::new_class( "@paths_string must be length 1" } else if (length(self@args_string) != 1) { "@args_string must be length 1" + } else if (length(self@tool_docs) != 1) { + "@tool_docs must be length 1" } else if (length(self@prompt_template) != 1) { "@prompt_template must be length 1" } else if (!is.list(self@summary_list)) { @@ -263,6 +268,8 @@ get_service_keys <- function(services) { #' @param tools_env Environment. Tool functions are added to this environment. Defaults to NULL which creates a new environment. #' @param data_env Environment. Data frames are added to this environment. Defaults to NULL which creates a new environment. #' @param object_env Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to NULL which creates a new environment. +#' @param tool_docs Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{FALSE}. +#' @param include_examples Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}. #' @param prompt_template Character. Customizable AI prompt to set behavior of AI model. Defaults to empty string. #' @param summary_list List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list. #' @param final_summary_prompt Character. Single synthesis instruction steering \code{summarize = "final"}, as opposed to the per-service \code{summary_list} used by \code{summarize = TRUE}. Supplied as a literal string or the path to a Markdown file, resolved identically to \code{prompt_template}. Defaults to an empty string. @@ -289,6 +296,8 @@ object_generate <- function(services_list = list(), tools_env = NULL, data_env = NULL, object_env = NULL, + tool_docs = FALSE, + include_examples = FALSE, prompt_template = ChatRBox::load_prompt_template(), summary_list = list(), final_summary_prompt = "", @@ -375,6 +384,14 @@ object_generate <- function(services_list = list(), if (length(env_strs) == 0L) "" else paste(env_strs, collapse = "\n\n") } + # Optional tool documentation interpolated into {tool_docs} + tool_docs <- if (isTRUE(tool_docs)) { + ChatRBox::build_tool_docs(tools_env, + include_examples = include_examples) + } else { + "" + } + prompt <- glue::glue(prompt_template) # Assign user-supplied summary prompts to services via name matching @@ -392,6 +409,7 @@ object_generate <- function(services_list = list(), object_env = object_env, paths_string = paths_string, args_string = args_string, + tool_docs = tool_docs, prompt_template = prompt_template, summary_list = summary_list, final_summary_prompt = final_summary_prompt, @@ -416,6 +434,8 @@ object_generate <- function(services_list = list(), #' @param tools_env Environment. Tool functions are added to this environment. Defaults to NULL which creates a new environment. #' @param data_env Environment. Data frames are added to this environment. Defaults to NULL which creates a new environment. #' @param object_env Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to NULL which creates a new environment. +#' @param tool_docs Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{FALSE}. +#' @param include_examples Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}. #' @param prompt_template Character. Customizable AI prompt to set behavior of AI model. Defaults to empty string. #' @param summary_list List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list. #' @param final_summary_prompt Character. Single synthesis instruction steering \code{summarize = "final"}, as opposed to the per-service \code{summary_list} used by \code{summarize = TRUE}. Supplied as a literal string or the path to a Markdown file, resolved identically to \code{prompt_template}. Defaults to the current object value. @@ -444,6 +464,8 @@ property_generate <- function(object, tools_env = NULL, data_env = NULL, object_env = NULL, + tool_docs = FALSE, + include_examples = FALSE, prompt_template = "", summary_list = list(), final_summary_prompt = object@final_summary_prompt, @@ -514,6 +536,13 @@ property_generate <- function(object, if (length(env_strs) == 0L) "" else paste(env_strs, collapse = "\n\n") } + # Optional tool documentation interpolated into {tool_docs} + tool_docs <- if (isTRUE(tool_docs)) { + ChatRBox::build_tool_docs(tools_env, include_examples = include_examples) + } else { + "" + } + # Create interpolated system prompt prompt <- glue::glue(prompt_template) @@ -533,6 +562,7 @@ property_generate <- function(object, object_env = object_env, paths_string = paths_string, args_string = args_string, + tool_docs = tool_docs, prompt_template = prompt_template, summary_list = summary_list, final_summary_prompt = final_summary_prompt, @@ -554,6 +584,8 @@ property_generate <- function(object, #' @param tools_env Environment. Tool functions are added to this environment. Defaults to existing object tools environment. Therefore, inputted tool functions add to, rather than replace, existing tool functions. #' @param data_env Environment. Data frames are added to this environment. Defaults to existing object data environment. Therefore, inputted data frames add to, rather than replace, existing data frames. #' @param object_env Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to existing object object environment. +#' @param tool_docs Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{S7} object current state to preserve original user intent during update. +#' @param include_examples Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}. #' @param prompt_template Character. Customizable AI prompt to set behavior of AI model. Defaults to empty string. #' @param summary_list List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list. #' @param final_summary_prompt Character. Single synthesis instruction steering \code{summarize = "final"}, as opposed to the per-service \code{summary_list} used by \code{summarize = TRUE}. Supplied as a literal string or the path to a Markdown file, resolved identically to \code{prompt_template}. Updated additively; defaults to the current object value. @@ -581,6 +613,8 @@ object_update <- function(object, tools_env = object@tools_env, data_env = object@data_env, object_env = object@object_env, + tool_docs = isTRUE(nzchar(object@tool_docs)), + include_examples = FALSE, prompt_template = object@prompt_template, summary_list = list(), final_summary_prompt = object@final_summary_prompt, @@ -600,6 +634,8 @@ object_update <- function(object, tools_env = tools_env, data_env = data_env, object_env = object_env, + tool_docs = tool_docs, + include_examples = include_examples, prompt_template = prompt_template, summary_list = summary_list, final_summary_prompt = final_summary_prompt, @@ -618,6 +654,7 @@ object_update <- function(object, object@object_env <- variables$object_env object@paths_string <- variables$paths_string object@args_string <- variables$args_string + object@tool_docs <- variables$tool_docs object@prompt_template <- variables$prompt_template object@summary_list <- variables$summary_list object@final_summary_prompt <- variables$final_summary_prompt diff --git a/inst/prompt/prompt.md b/inst/prompt/prompt.md index 6ab66b7..5760f96 100644 --- a/inst/prompt/prompt.md +++ b/inst/prompt/prompt.md @@ -45,6 +45,11 @@ IMPORTANT RULES TO FOLLOW: 5. NEVER write text on the same line as the closing ```; ALWAYS break to a new line first. 6. If a required input already exists as uploaded data, a past service output, or an earlier output created in the same response, ALWAYS use its human-readable object name as the argument value instead of repeating, embedding, serializing, or copying its contents. THIS RULE IS VERY IMPORTANT. +TOOL DOCUMENTATION: +The following is reference documentation for the available tools, provided to help you understand each tool's purpose, arguments and return values when choosing a service path and extracting arguments. Use it together with the service paths and argument list above. If this section is empty, rely on the service paths and argument list alone. + +{tool_docs} + REVEALING STORED OUTPUTS ON REQUEST: If, and only if, the user explicitly asks to see a previously produced output (for example "show me those tool outputs", "can I see the table", "what were the results"), do NOT invoke any service. Instead, output your answer as text followed by a single fenced code block tagged `display` (no other text after it): diff --git a/man/ChatRBox_obj.Rd b/man/ChatRBox_obj.Rd index fbe7651..88405de 100644 --- a/man/ChatRBox_obj.Rd +++ b/man/ChatRBox_obj.Rd @@ -12,6 +12,7 @@ ChatRBox_obj( object_env = new.env(parent = emptyenv()), paths_string = character(0), args_string = character(0), + tool_docs = character(0), prompt_template = character(0), summary_list = list(), final_summary_prompt = character(0), @@ -34,6 +35,8 @@ ChatRBox_obj( \item{args_string}{Character. Concatenated function paths with arguments. Defaults to \code{character(0)}.} +\item{tool_docs}{Character. Rendered tool documentation interpolated into the prompt when \code{tool_docs = TRUE}. Defaults to \code{character(0)}.} + \item{prompt_template}{Character. Template used to generate AI prompt. Defaults to \code{character(0)}.} \item{summary_list}{List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list.} @@ -60,6 +63,7 @@ This object class is generated in \code{\link{ChatRBox}} using \code{\link{objec \item{object_env}{Environment. Environment containing past API/tool outputs stored as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to empty environment.} \item{paths_string}{Character. Concatenated function paths. Defaults to \code{character(0)}.} \item{args_string}{Character. Concatenated function paths with arguments. Defaults to \code{character(0)}.} + \item{tool_docs}{Character. Rendered tool documentation interpolated into the prompt when \code{tool_docs = TRUE}. Defaults to \code{character(0)}.} \item{prompt_template}{Character. Template used to generate AI prompt. This may refer to \code{paths_string} and \code{args_string} which append using \code{glue::glue()}. Defaults to \code{character(0)}.} \item{summary_list}{List. Named list of instructions for AI-enabled summaries. Defaults to empty list.} \item{final_summary_prompt}{Character. Single synthesis instruction steering \code{summarize = "final"}. Defaults to an empty string.} diff --git a/man/ChatRBox_update.Rd b/man/ChatRBox_update.Rd index 0a225e6..6a6c5fa 100644 --- a/man/ChatRBox_update.Rd +++ b/man/ChatRBox_update.Rd @@ -13,6 +13,8 @@ ChatRBox_update( tools_env = object$chat_object@tools_env, data_env = object$chat_object@data_env, object_env = object$chat_object@object_env, + tool_docs = isTRUE(nzchar(object$chat_object@tool_docs)), + include_examples = FALSE, prompt_template = object$chat_object@prompt_template, summary_list = list(), final_summary_prompt = object$chat_object@final_summary_prompt, @@ -41,6 +43,10 @@ ChatRBox_update( \item{object_env}{Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to current \code{S7} object object environment.} +\item{tool_docs}{Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{S7} object current state to preserve original user intent during update.} + +\item{include_examples}{Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}.} + \item{prompt_template}{Character. General AI prompt for outputting parameter key-value pairs for available API services/tools. Defaults to current \code{S7} object prompt, likely interpolating function paths and arguments.} \item{summary_list}{List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list.} diff --git a/man/R6.Rd b/man/R6.Rd index 47c44df..6ee14e9 100644 --- a/man/R6.Rd +++ b/man/R6.Rd @@ -84,6 +84,8 @@ Initializes a new ChatRBox session using the provided parameters. tools_env = NULL, data_env = NULL, object_env = NULL, + tool_docs = FALSE, + include_examples = FALSE, prompt_template = ChatRBox::load_prompt_template(), summary_list = list(), final_summary_prompt = "", @@ -124,6 +126,12 @@ Environment to which data frames are added. Defaults to \code{NULL} (creates a n \item{\code{object_env}}{(`environment()`)\cr Environment in which past API service/tool outputs are stored as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to \code{NULL} (creates a new environment).} +\item{\code{tool_docs}}{(`logical(1)`)\cr +Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{FALSE}.} + +\item{\code{include_examples}}{(`logical(1)`)\cr +Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}.} + \item{\code{prompt_template}}{(`character(1)`)\cr Prompt template for the AI, for parameter extraction and user interaction. Defaults to the ChatRBox conversational template.} diff --git a/man/build_tool_docs.Rd b/man/build_tool_docs.Rd new file mode 100644 index 0000000..579281c --- /dev/null +++ b/man/build_tool_docs.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/documentation_interpolation.R +\name{build_tool_docs} +\alias{build_tool_docs} +\title{Builds Tool Documentation for AI Prompt Interpolation} +\usage{ +build_tool_docs(tools_env = NULL, width = 1000L, include_examples = FALSE) +} +\arguments{ +\item{tools_env}{Environment. Environment containing tool functions, as stored in the \code{tools_env} property of a \code{\link{ChatRBox_obj}} \code{S7} object. Defaults to \code{NULL}, which returns an empty string.} + +\item{width}{Integer. Wrapping width passed to \code{tools::Rd2txt}. Defaults to \code{1000}.} + +\item{include_examples}{Logical. Whether to retain the \code{\\examples} section of each tool's Rd documentation. Defaults to \code{FALSE} to keep the interpolated prompt concise.} +} +\value{ +A brace-escaped character string documenting each tool function, separated by horizontal rules. Returns \code{""} when \code{tools_env} is \code{NULL}, is not an environment, or contains no functions. +} +\description{ +This function renders the help documentation of every tool function contained within a tools environment into a single Markdown-formatted character string. The result is intended for interpolation into the AI prompt via \code{\link{object_generate}}, providing the LLM with detailed reference documentation for each available tool alongside the service paths and arguments already supplied by \code{\link{get_function_path}} and \code{\link{get_function_args}}. +} +\details{ +For each tool function, the package documentation (Rd) is located and rendered to plain text. Where a function has no associated Rd documentation, or is not defined within a package namespace, the function falls back to its argument signature so that the LLM is never left without context. + + +Documentation is extracted from the installed package Rd database using \code{tools::Rd_db} and rendered with \code{tools::Rd2txt}, so it reuses the tool author's own maintained documentation rather than duplicating it. The \code{\\examples} section is dropped by default to keep the interpolated prompt concise, and may be retained via \code{include_examples}. All curly braces in the assembled documentation are escaped exactly once so the returned string is safe to pass through \code{glue::glue()}, consistent with how \code{paths_string}, \code{args_string} and \code{past_outputs} are interpolated into \code{prompt_template} within \code{\link{object_generate}}. This function underpins the optional \code{tool_docs} argument of \code{\link{ChatRBox}}, \code{\link{object_generate}} and \code{\link{ChatRBox_update}}. When \code{tool_docs = TRUE}, the returned string is interpolated into the \code{{tool_docs}} placeholder of the default prompt template. Otherwise, the default prompt remains which only provides LLMs with service names and arguments. Notably, this function locates tool functions by searching the global environment, such that it was intended for package functions supplied to chatbots in R using \code{pkg::fn()} notation. Written functions and API endpoints do not have documentation suitable for rendering via this method. +} +\examples{ +# Defines a tools environment containing a documented package function +tools_env <- new.env() +assign("standard_deviation", stats::sd, envir = tools_env) + +# Renders package documentation for each tool, excluding examples +docs <- build_tool_docs(tools_env) +cat(docs) + +# Retain the examples section for richer prompt grounding +docs_full <- build_tool_docs(tools_env, + include_examples = TRUE) + +# The result is brace-escaped and therefore safe for glue interpolation +prompt <- glue::glue("You have these tools:\n\n{docs}") + +# Enable documentation interpolation via ChatRBox underlying S7 object +add <- function(x, y) x + y + +object <- object_generate(tools_list = list(add_two_numbers = add), + tool_docs = TRUE) + +get_property(object, "tool_docs") +} +\seealso{ +\code{\link{object_generate}}, \code{\link{get_function_path}}, \code{\link{get_function_args}}, \code{\link{load_prompt_template}} +} diff --git a/man/code_extract.Rd b/man/code_extract.Rd index 17cbc0c..e8ebf15 100644 --- a/man/code_extract.Rd +++ b/man/code_extract.Rd @@ -9,7 +9,7 @@ code_extract(string = NULL, language = "json") \arguments{ \item{string}{Character. The input string containing Markdown code block(s). Required.} -\item{language}{Character. The language tag of the code block to extract. Defaults to "json".} +\item{language}{Character. The language tag of the code block. One of the supported tags in \code{.SUPPORTED_CODE_LANGUAGES} (\code{"json"}, \code{"display"}, \code{"yaml"}, \code{"yml"}, \code{"tsv"}, \code{"csv"}, \code{"md"}). This may be extended by adding to the vector assignment. See CONTRIBUTING.md for further detail. Defaults to "json".} } \value{ The contents of the first extracted code block. diff --git a/man/code_remove.Rd b/man/code_remove.Rd index 1c59964..7074993 100644 --- a/man/code_remove.Rd +++ b/man/code_remove.Rd @@ -9,7 +9,7 @@ code_remove(string = NULL, language = "json") \arguments{ \item{string}{Character. The input string containing Markdown code block(s). Required.} -\item{language}{Character. The language tag of the code block to remove. Defaults to "json".} +\item{language}{Character. The language tag of the code block. One of the supported tags in \code{.SUPPORTED_CODE_LANGUAGES} (\code{"json"}, \code{"display"}, \code{"yaml"}, \code{"yml"}, \code{"tsv"}, \code{"csv"}, \code{"md"}). This may be extended by adding to the vector assignment. See CONTRIBUTING.md for further detail. Defaults to "json".} } \value{ A character string with specified code blocks and fences removed. diff --git a/man/examples/examples_tool_docs.R b/man/examples/examples_tool_docs.R new file mode 100644 index 0000000..0ee49f3 --- /dev/null +++ b/man/examples/examples_tool_docs.R @@ -0,0 +1,22 @@ +# Defines a tools environment containing a documented package function +tools_env <- new.env() +assign("standard_deviation", stats::sd, envir = tools_env) + +# Renders package documentation for each tool, excluding examples +docs <- build_tool_docs(tools_env) +cat(docs) + +# Retain the examples section for richer prompt grounding +docs_full <- build_tool_docs(tools_env, + include_examples = TRUE) + +# The result is brace-escaped and therefore safe for glue interpolation +prompt <- glue::glue("You have these tools:\n\n{docs}") + +# Enable documentation interpolation via ChatRBox underlying S7 object +add <- function(x, y) x + y + +object <- object_generate(tools_list = list(add_two_numbers = add), + tool_docs = TRUE) + +get_property(object, "tool_docs") diff --git a/man/object_generate.Rd b/man/object_generate.Rd index 954cf45..61b84e1 100644 --- a/man/object_generate.Rd +++ b/man/object_generate.Rd @@ -12,6 +12,8 @@ object_generate( tools_env = NULL, data_env = NULL, object_env = NULL, + tool_docs = FALSE, + include_examples = FALSE, prompt_template = ChatRBox::load_prompt_template(), summary_list = list(), final_summary_prompt = "", @@ -38,6 +40,10 @@ object_generate( \item{object_env}{Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to NULL which creates a new environment.} +\item{tool_docs}{Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{FALSE}.} + +\item{include_examples}{Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}.} + \item{prompt_template}{Character. Customizable AI prompt to set behavior of AI model. Defaults to empty string.} \item{summary_list}{List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list.} diff --git a/man/object_update.Rd b/man/object_update.Rd index c370c54..9fcacd0 100644 --- a/man/object_update.Rd +++ b/man/object_update.Rd @@ -13,6 +13,8 @@ object_update( tools_env = object@tools_env, data_env = object@data_env, object_env = object@object_env, + tool_docs = isTRUE(nzchar(object@tool_docs)), + include_examples = FALSE, prompt_template = object@prompt_template, summary_list = list(), final_summary_prompt = object@final_summary_prompt, @@ -41,6 +43,10 @@ object_update( \item{object_env}{Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to existing object object environment.} +\item{tool_docs}{Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{S7} object current state to preserve original user intent during update.} + +\item{include_examples}{Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}.} + \item{prompt_template}{Character. Customizable AI prompt to set behavior of AI model. Defaults to empty string.} \item{summary_list}{List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list.} diff --git a/man/property_generate.Rd b/man/property_generate.Rd index 8706c5c..249e919 100644 --- a/man/property_generate.Rd +++ b/man/property_generate.Rd @@ -13,6 +13,8 @@ property_generate( tools_env = NULL, data_env = NULL, object_env = NULL, + tool_docs = FALSE, + include_examples = FALSE, prompt_template = "", summary_list = list(), final_summary_prompt = object@final_summary_prompt, @@ -41,6 +43,10 @@ property_generate( \item{object_env}{Environment. Past API service/tool outputs are stored in this environment as named R objects of any type, used to resolve chained API calls and to inform the AI prompt via \code{past_outputs}. Defaults to NULL which creates a new environment.} +\item{tool_docs}{Logical. Whether to interpolate rendered tool documentation into the prompt via \code{\link{build_tool_docs}}. Defaults to \code{FALSE}.} + +\item{include_examples}{Logical. Whether retained tool documentation includes the \code{\\examples} section. Only relevant when \code{tool_docs = TRUE}. Defaults to \code{FALSE}.} + \item{prompt_template}{Character. Customizable AI prompt to set behavior of AI model. Defaults to empty string.} \item{summary_list}{List. Optional named list of instructions for AI-enabled summaries, assigned to specific services via name matching. Prompting may be provided via character strings or a Markdown file. Defaults to empty list.} diff --git a/tests/testthat/test_generate_ai_response.R b/tests/testthat/test_generate_ai_response.R index c934915..9983fe8 100644 --- a/tests/testthat/test_generate_ai_response.R +++ b/tests/testthat/test_generate_ai_response.R @@ -552,3 +552,38 @@ testthat::test_that("redact_sensitive preserves non-sensitive values", { testthat::expect_equal(out$token_list$api_key, "KEY_REDACTED") testthat::expect_equal(out$token_list$limit, 10) }) + +testthat::test_that("JSON extraction is unchanged after the refactor", { + resp <- 'x```json\n{"a":1}\n```' + testthat::expect_equal(code_extract(resp, language = "json"), '{"a":1}') +}) + +testthat::test_that("New formats extract correctly", { + yml <- "cfg\n```yml\nname: ChatRBox\n```\nend" + testthat::expect_equal(code_extract(yml, language = "yml"), "name: ChatRBox") + + csv <- "t\n```csv\na,b\n1,2\n```\ne" + testthat::expect_equal(code_extract(csv, language = "csv"), "a,b\n1,2") + + md <- "r\n```md\n## H\n- x\n```\ne" + testthat::expect_equal(code_extract(md, language = "md"), "## H\n- x") +}) + +testthat::test_that("Public API stays gated to supported languages", { + testthat::expect_error(code_extract("```toml\nk='v'\n```", language = "toml")) + testthat::expect_error(code_remove("```toml\nk='v'\n```", language = "toml")) +}) + +testthat::test_that("Low-level engine accepts any tag", { + resp <- "intro\n```yaml\nkey: value\n```\noutro" + testthat::expect_equal( + .code_block(resp, language = "yaml", action = "extract"), "key: value") + testthat::expect_equal( + .code_block(resp, language = "yaml", action = "remove"), "intro\n\noutro") +}) + +testthat::test_that("Prefix tags do not collide on the guarded remove", { + resp <- "a\n```markdown\n## real\n```\nb" + testthat::expect_true(is.na(.code_block(resp, language = "md", + action = "extract"))) +}) diff --git a/tests/testthat/test_object_methods.R b/tests/testthat/test_object_methods.R index 173f3d5..996b256 100644 --- a/tests/testthat/test_object_methods.R +++ b/tests/testthat/test_object_methods.R @@ -73,3 +73,37 @@ testthat::expect_equal( "services$subtract_two_numbers(x, y)" ) ) + +# S7/R6 tool documentation interpolation +testthat::test_that("tool_docs defaults empty and off leaves prompt unchanged", { + add <- function(x, y) x + y + object <- object_generate(tools_list = list(add_two_numbers = add)) + testthat::expect_identical(get_property(object, "tool_docs"), "") +}) + +testthat::test_that("tool_docs = TRUE populates the S7 property and prompt", { + add <- function(x, y) x + y + object <- object_generate( + tools_list = list(standard_deviation = stats::sd), + tool_docs = TRUE) + docs <- get_property(object, "tool_docs") + testthat::expect_true(nzchar(docs)) + testthat::expect_match(get_property(object, "prompt"), + "standard_deviation", fixed = TRUE) +}) + +testthat::test_that("tool_docs S7 property is validated as length 1", { + add <- function(x, y) x + y + object <- object_generate(tools_list = list(add_two_numbers = add)) + testthat::expect_error( + { object@tool_docs <- c("a", "b") }, + regexp = "@tool_docs must be length 1" + ) +}) + +testthat::test_that("object_update regenerates tool_docs", { + add <- function(x, y) x + y + object <- object_generate(tools_list = list(add_two_numbers = add)) + updated <- object_update(object, tool_docs = TRUE) + testthat::expect_true(nzchar(get_property(updated, "tool_docs"))) +}) diff --git a/tests/testthat/test_tool_documentation.R b/tests/testthat/test_tool_documentation.R new file mode 100644 index 0000000..7f13ddb --- /dev/null +++ b/tests/testthat/test_tool_documentation.R @@ -0,0 +1,39 @@ +testthat::test_that("build_tool_docs returns empty for invalid tools_env", { + testthat::expect_identical(build_tool_docs(NULL), "") + testthat::expect_identical(build_tool_docs("not_an_env"), "") + testthat::expect_identical(build_tool_docs(new.env()), "") +}) + +testthat::test_that("build_tool_docs falls back to arg signature for bare fns", { + tools_env <- new.env() + assign("add_two_numbers", function(x, y) x + y, envir = tools_env) + docs <- build_tool_docs(tools_env) + testthat::expect_true(nzchar(docs)) + testthat::expect_match(docs, "add_two_numbers", fixed = TRUE) + testthat::expect_match(docs, "Arguments: x, y", fixed = TRUE) +}) + +testthat::test_that("build_tool_docs escapes braces for glue safety", { + tools_env <- new.env() + assign("standard_deviation", stats::sd, envir = tools_env) + docs <- build_tool_docs(tools_env) + testthat::expect_no_error(glue::glue("Tools:\n{docs}")) +}) + +testthat::test_that("include_examples retains the examples section", { + tools_env <- new.env() + assign("standard_deviation", stats::sd, envir = tools_env) + without <- build_tool_docs(tools_env, include_examples = FALSE) + with <- build_tool_docs(tools_env, include_examples = TRUE) + testthat::expect_true(nchar(with) >= nchar(without)) + testthat::expect_no_error(glue::glue("Tools:\n{with}")) +}) + +testthat::test_that("build_tool_docs skips non-function bindings", { + tools_env <- new.env() + assign("a_tool", function(x) x, envir = tools_env) + assign("not_a_tool", 42L, envir = tools_env) + docs <- build_tool_docs(tools_env) + testthat::expect_match(docs, "a_tool", fixed = TRUE) + testthat::expect_false(grepl("not_a_tool", docs, fixed = TRUE)) +}) From b70f86f1741c8fef65b09676f0a7be0a3ee2a5b9 Mon Sep 17 00:00:00 2001 From: abigailbarnett Date: Fri, 2 Oct 2026 20:18:36 +0000 Subject: [PATCH 2/2] Version 0.3.0 --- DESCRIPTION | 2 +- NEWS | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2f7a40e..df78a9d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: ChatRBox Title: Your Chatbot Development Toolkit -Version: 0.2.7 +Version: 0.3.0 Authors@R: c( person("Abigail", "Barnett", email = "Abigail.Barnett@pfizer.com", role = c("aut", "cre"), comment = c(ORCID = "0009-0009-1441-4929")), diff --git a/NEWS b/NEWS index 58374fd..1a5d0f6 100644 --- a/NEWS +++ b/NEWS @@ -1,2 +1,6 @@ Version: 0.2.7 - - First opensource deployment \ No newline at end of file + - First opensource deployment + +Version: 0.3.0 + - Added ability to interpolate tool function package documentation into system prompt + - New user-facing arguments: tool_docs, include_examples \ No newline at end of file