QueryChat is an R6 class built on Shiny, shinychat, and ellmer to enable
interactive querying of data using natural language. It leverages large
language models (LLMs) to translate user questions into SQL queries, execute
them against a data source (data frame or database), and various ways of
accessing/displaying the results.
The QueryChat class takes your data (a data frame or database connection)
as input and provides methods to:
Generate a chat UI for natural language queries (e.g.,
$app(),$sidebar())Initialize server logic that returns session-specific reactive values (via
$server())Access reactive data, SQL queries, and titles through the returned server values (use
qc_vals$table("name")for multi-table access)
Usage in Shiny Apps
library(querychat)
# Create a QueryChat object
qc <- QueryChat$new(mtcars)
# Quick start: run a complete app
qc$app()
# Or build a custom Shiny app
ui <- page_sidebar(
qc$sidebar(),
verbatimTextOutput("sql"),
dataTableOutput("data")
)
server <- function(input, output, session) {
qc_vals <- qc$server()
output$sql <- renderText(qc_vals$sql())
output$data <- renderDataTable(qc_vals$df())
}
shinyApp(ui, server)Public fields
greetingThe greeting message displayed to users.
historyConversation history configuration.
idID for the QueryChat instance.
id_overrideWhether the ID was explicitly set by the user.
toolsThe allowed tools for the chat client.
Active bindings
greeterThe QueryChatGreeter controlling greeting generation; access its
$tablesand$prompt.system_promptGet the system prompt.
data_sourceRemoved. Use
$add_table()and$remove_table()to manage tables.
Methods
QueryChat$new()
Create a new QueryChat object.
Arguments
data_sourceEither a data.frame, a database connection (e.g., DBI connection), or
NULLto defer setting the data source until later. WhenNULL, the data source must be added via$add_table()or passed to$server()before calling methods that require data access.table_nameA string specifying the table name to use in SQL queries. If
data_sourceis a data.frame, this is the name to refer to it by in queries (typically the variable name). If not provided, will be inferred from the variable name for data.frame inputs. Required for database connections. Optional whendata_sourceisNULL: if omitted,$idfalls back to a generic default, and a table name must be supplied later via$add_table()or$server(data_source =, table_name = )....Additional arguments (currently unused).
idOptional module ID for the QueryChat instance. If not provided, will be auto-generated from
table_name(or a generic default whendata_sourceisNULLandtable_nameis also omitted). The ID is used to namespace the Shiny module.greetingOptional initial message to display to users. Can be a character string (in Markdown format) or a file path. If not provided, a greeting will be generated at the start of each conversation using the LLM, which adds latency and cost. Use
$generate_greeting()to create a greeting to save and reuse.historyConversation history configuration:
NULL(default; resolves toTRUEwhen$server()/$app()is called and nothing else was set),TRUE/FALSE, or ashinychat::history_options()object. Passed straight through toshinychat::chat_server(history = ).clientOptional chat client. Can be:
An ellmer::Chat object
A string to pass to
ellmer::chat()(e.g.,"openai/gpt-4o")NULL(default): Uses thequerychat.clientoption, theQUERYCHAT_CLIENTenvironment variable, or defaults toellmer::chat_openai()
toolsWhich querychat tools to include in the chat client, by default.
"filter"includes the tools for filtering and resetting the dashboard,"query"includes the tool for executing SQL queries, and"visualize"includes the tool for rendering visualizations (requires the ggsql package; if it is not installed, the tool is dropped with a warning). The default isc("filter", "query", "visualize"). Usetools = "filter"when you only want the dashboard filtering tools, or when you want to disable the querying tool entirely to prevent the LLM from seeing any of the data in your dataset. The legacy name"update"is still accepted as an alias for"filter".data_descriptionOptional description of the data in plain text or Markdown. Can be a string or a file path. This provides context to the LLM about what the data represents.
categorical_thresholdFor text columns, the maximum number of unique values to consider as a categorical variable. Default is 20.
extra_instructionsOptional additional instructions for the chat model in plain text or Markdown. Can be a string or a file path.
prompt_templateOptional path to or string of a custom prompt template file. If not provided, the default querychat template will be used. See the package prompts directory for the default template format.
data_dictOptional data dictionary. A path to a YAML file, or a list of YAML file paths. See
read_data_dict()for the expected format.cleanupWhether or not to automatically run
$cleanup(). By default, cleanup only occurs ifQueryChatgets created while a Shiny app is running: when created inside a session (e.g., in the server function), cleanup runs when that session ends; when created outside a session (e.g., at the top level ofapp.R), it runs when the app stops. Set toTRUEto always clean up, orFALSEto never clean up automatically.
QueryChat$add_table()
Add a table to this QueryChat instance.
Replacing or removing an existing table after a session has started is an error; adding a new one warns.
Arguments
data_sourceA data frame, database connection, or DataSource object.
table_nameThe SQL table name for this data source.
replaceWhether to replace an existing table with this name. Default is
FALSE.include_in_greetingWhether to include this table in the greeting context. Default is
FALSE.
QueryChat$add_tables()
Add multiple tables from a DBI connection in a single call.
Unlike calling $add_table() repeatedly, this method builds the
system prompt exactly once after all tables have been staged, avoiding
N-1 spurious intermediate rebuilds.
Replacing or removing an existing table after a session has started is an error; adding a new one warns.
Arguments
connA DBI connection. Only DBI connections are supported; pass individual data frames or other sources via
$add_table().tablesTable names to register. When
NULL, all tables returned byDBI::dbListTables(conn)are used.replaceWhether to replace existing tables with the same name. Default is
FALSE.include_in_greetingWhether to include added tables in the greeting context.
TRUEincludes all tables;FALSE(default) includes none; a character vector includes only those named tables (intersected with the tables being added). Any other type raises an error.
QueryChat$remove_table()
Remove a table from this QueryChat instance.
Removing an existing table after a session has started is an error.
QueryChat$client()
Create a chat client, complete with registered tools, for the current data source.
Usage
QueryChat$client(
tools = NA,
update_dashboard = function(query, title, table) {
},
reset_dashboard = function(table) {
},
visualize = function(data) {
},
session = NULL
)Arguments
toolsWhich querychat tools to include in the chat client.
"filter"includes the tools for filtering and resetting the dashboard and"query"includes the tool for executing SQL queries. By default, whentools = NA, the values provided at initialization are used. The legacy name"update"is still accepted as an alias for"filter".update_dashboardOptional function to call with the
query,title, andtablegenerated by the LLM for theupdate_dashboardtool.reset_dashboardOptional function to call when the
reset_dashboardtool is called. Takes atableargument.visualizeOptional function to call with a list containing
ggsql,title, andwidget_idwhen a visualization succeeds.sessionA Shiny session object. Required when
"visualize"is intoolsand you want interactive chart rendering. WhenNULL(the default), visualizations still execute but are not rendered as Shiny outputs.
QueryChat$console()
Launch a console-based chat interface with the data source.
Arguments
newWhether to create a new chat client instance or continue the conversation from the last console chat session (the default).
...Additional arguments passed to the
$client()method.toolsWhich querychat tools to include in the chat client. See
$client()for details. Ignored when not creating a new chat client. By default, only the"query"tool is included, regardless of thetoolsset at initialization.
QueryChat$app()
Create and run a Shiny gadget for chatting with data
Arguments
...Arguments passed to
$app_obj().historyConversation history configuration for the generated app. Defaults to
shinychat::history_options(restore_mode = "bookmark")when neither this nor$new()'shistorywas set, since$app()'s whole purpose is a single, shareable demo. When the resolved value hasrestore_mode = "bookmark", the generated app automatically enables Shiny's own server-side bookmarking.
QueryChat$app_obj()
A streamlined Shiny app for chatting with data
Arguments
...Additional arguments (currently unused).
historyConversation history configuration for the generated app. See
$app().
Returns
A Shiny app object that can be run with shiny::runApp().
QueryChat$sidebar()
Create a sidebar containing the querychat UI.
Arguments
...Additional arguments passed to
bslib::sidebar().widthWidth of the sidebar in pixels. Default is 400.
heightHeight of the sidebar. Default is "100%".
fillableWhether the sidebar should be fillable. Default is
TRUE.idOptional ID for the QueryChat instance.
Returns
A bslib::sidebar() UI component.
QueryChat$ui()
Create the UI for the querychat chat interface.
Arguments
...Additional arguments passed to
shinychat::chat_ui().idOptional ID for the QueryChat instance.
QueryChat$page()
Create a full-window page containing the querychat UI.
This wraps shinychat::page_chat(), making the chat the primary
surface of the app, with optional navigation pages, sidebars, and a
drawer. Use this instead of $sidebar() or $ui() when the chat
should own the full browser window.
Arguments
titlePage title displayed in the header. When it is a string and
window_titleis omitted, it is also used as the document title....Additional arguments passed to
shinychat::page_chat().idOptional ID for the QueryChat instance.
QueryChat$server()
Initialize the querychat server logic.
Usage
QueryChat$server(
data_source = NULL,
client = NULL,
history = NULL,
enable_bookmarking = NULL,
...,
table_name = NULL,
id = NULL,
session = shiny::getDefaultReactiveDomain()
)Arguments
data_sourceOptional data source to register for this session only, for the deferred pattern where the source can't be created until the server function runs (for example a per-user database connection). The instance's own tables are not modified; a same-named instance table is shadowed for this session; any connection querychat created for it is cleaned up when the session ends.
clientOptional chat client override for this session.
historyConversation history configuration for this call. Overrides the value set on
$new(). Resolves toTRUEwhen neither this nor the constructor'shistorywas set.enable_bookmarkingUse
history = shinychat::history_options(restore_mode = "bookmark")instead (set on$new(), or passed here)....Ignored.
table_nameTable name to register
data_sourceunder. Only used whendata_sourceis provided. Named-only (placed after...) so it can't shift the meaning of existing positional calls.idOptional module ID override.
sessionThe Shiny session object.
Returns
A list containing session-specific reactive values and the chat
client. For single-table usage, includes df, sql, title directly.
For multi-table, use qc_vals$table("name") to get a TableAccessor
with per-table reactive state. Also includes table_names() to list tables.
current_table() returns the name of the most recently queried table,
or NULL before any query.
QueryChat$generate_greeting()
Generate a welcome greeting for the chat.
Usage
QueryChat$generate_greeting(echo = c("none", "output"))QueryChat$cleanup()
Clean up resources this object created.
Closes the query executors and data-source connections querychat opened
(in-memory DuckDB), including those of table sets superseded by a late
$add_table(). Connections you passed in are never closed.
Examples
# Basic usage with a data frame
qc <- QueryChat$new(mtcars)
#> duckdb keeps downloaded extensions and secrets in a temporary directory:
#> ℹ /tmp/RtmpZdCyHx/duckdb
#> This is removed when the R session ends.
#> • Extensions are re-downloaded each session.
#> • Secrets are lost.
#> ℹ Run duckdb(shared_home = TRUE) (or create ~/.duckdb) to keep them (suitable for most users).
#> ℹ Run duckdb(shared_home = FALSE) to accept the temporary directory (and silence this message).
#> ℹ See ?duckdb_storage for details and alternatives.
if (FALSE) { # \dontrun{
app <- qc$app()
} # }
# With a custom greeting
greeting <- "Welcome! Ask me about the mtcars dataset."
qc <- QueryChat$new(mtcars, greeting = greeting)
#> duckdb keeps downloaded extensions and secrets in a temporary directory:
#> ℹ /tmp/RtmpZdCyHx/duckdb
#> This is removed when the R session ends.
#> • Extensions are re-downloaded each session.
#> • Secrets are lost.
#> ℹ Run duckdb(shared_home = TRUE) (or create ~/.duckdb) to keep them (suitable for most users).
#> ℹ Run duckdb(shared_home = FALSE) to accept the temporary directory (and silence this message).
#> ℹ See ?duckdb_storage for details and alternatives.
# With a specific LLM provider
qc <- QueryChat$new(mtcars, client = "anthropic/claude-sonnet-4-5")
#> duckdb keeps downloaded extensions and secrets in a temporary directory:
#> ℹ /tmp/RtmpZdCyHx/duckdb
#> This is removed when the R session ends.
#> • Extensions are re-downloaded each session.
#> • Secrets are lost.
#> ℹ Run duckdb(shared_home = TRUE) (or create ~/.duckdb) to keep them (suitable for most users).
#> ℹ Run duckdb(shared_home = FALSE) to accept the temporary directory (and silence this message).
#> ℹ See ?duckdb_storage for details and alternatives.
# Generate a greeting for reuse (requires internet/API access)
if (FALSE) { # \dontrun{
qc <- QueryChat$new(mtcars)
greeting <- qc$generate_greeting(echo = "text")
# Save greeting for next time
writeLines(greeting, "mtcars_greeting.md")
} # }
# Or specify greeting and additional options at initialization
qc <- QueryChat$new(
mtcars,
greeting = "Welcome to the mtcars explorer!",
client = "openai/gpt-4o",
data_description = "Motor Trend car road tests dataset"
)
#> duckdb keeps downloaded extensions and secrets in a temporary directory:
#> ℹ /tmp/RtmpZdCyHx/duckdb
#> This is removed when the R session ends.
#> • Extensions are re-downloaded each session.
#> • Secrets are lost.
#> ℹ Run duckdb(shared_home = TRUE) (or create ~/.duckdb) to keep them (suitable for most users).
#> ℹ Run duckdb(shared_home = FALSE) to accept the temporary directory (and silence this message).
#> ℹ See ?duckdb_storage for details and alternatives.
# Create a QueryChat object from a database connection
# 1. Set up the database connection
con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
# 2. (For this demo) Create a table in the database
DBI::dbWriteTable(con, "mtcars", mtcars)
# 3. Pass the connection and table name to `QueryChat`
qc <- QueryChat$new(con, "mtcars")
