New features
-
QueryChat()now supports multiple related tables. Register additional tables withadd_table()and the LLM can reason across all of them — joins, cross-table filters, aggregations. Per-table reactive state (df(),sql(),title()) is accessible viaqc_vals.table("name")on the value returned byserver(). For SQLAlchemy engines and Ibis backends,add_tables()registers all tables (or a named subset) in a single call. (#195)qc = QueryChat(orders_df, "orders") qc.add_table(customers_df, "customers") # Or, register all tables from a SQLAlchemy engine or Ibis backend at once: qc = QueryChat() qc.add_tables(engine) # SQLAlchemy engine qc.add_tables(ibis_backend) # Ibis backend qc_vals = qc.server() qc_vals.table("orders").df() qc_vals.table("customers").sql()
-
A new
DataDicttype — integrating with the data-dict spec — lets you annotate tables and columns with plain-English descriptions loaded from a YAML file. This is the preferred way to provide additional context for the data, especially when multiple tables are relevant. The LLM receives these descriptions when it fetches the schema, helping it interpret ambiguous or domain-specific column names without any extra prompting. (#195)QueryChat(data_dict="data_dict.yaml")
-
Conversation history is now enabled by default.
QueryChat/QueryChatExpresskeep a user's chat around across page reloads and browser sessions, backed by shinychat's history support. The defaultrestore_mode="browser"stores the active conversation in the browser's localStorage, but you can passhistory=shinychat.types.HistoryOptions(restore_mode="url")to restore via a plain, shareable URL instead, orrestore_mode="bookmark"to fold the conversation into a full Shiny bookmark. Disable withhistory=False. -
File attachments are now enabled by default in the Shiny chat UI. Users can attach images, PDFs, and text files to their messages and the LLM will receive them. Disable with
allow_attachments=Falseinmod_ui()orQueryChat.ui(). (#253) -
Added
PinSource, a data source for chatting with datasets pinned to a pins board. Works with parquet, CSV, JSON, and Arrow pins, and uses the pin's title, description, and tags as the default data description. Install the optional dependency withpip install querychat[pins]. (#246) -
The SQL panel in
.app()is now an editable code editor. Users can tweak the generated SQL directly and apply it with Ctrl/Cmd+Enter or by clicking away — no extra button required. The editor stays in sync when the LLM updates the query or the active table changes. (#265)
Improvements
-
Chat greetings now use shinychat's greeting API (requires shinychat >= 0.4.0). A provided
greetingrenders instantly when the app loads, and when nogreetingis given one is generated on demand — now schema-aware, so it can describe the data it's about to help you explore — without being added to the conversation history. Generated greetings are preserved across bookmark/restore. Tables passed toQueryChat()are described in the greeting automatically; opt additional tables in withinclude_in_greeting=Trueonadd_table()/add_tables(), or fine-tune which tables and which template the greeting uses viaqc.greeter. (#249, #261) -
The system prompt is now lighter: full schema is no longer embedded upfront. Instead the LLM fetches per-table schema on demand via the new
querychat_get_schematool — and only when it needs to. When aDataDictis provided, the tool skips columns that already have descriptions, so the LLM only pays for what isn't already documented. (#195) -
The query tool result card now starts collapsed by default. Users can still expand it to see the SQL query and results. Set
QUERYCHAT_TOOL_DETAILS=expandedto restore the previous behavior. (#239) -
Fixed
data_descriptionandextra_instructionsbeing HTML-escaped in the system prompt. Special characters like<,>, and&in developer-provided descriptions and instructions are now passed to the LLM verbatim. (#258)
Breaking Changes
-
The
data_sourceproperty has been removed. Useqc.table("name").data_sourceto read a table's data source, andqc.add_table(df, "name", replace=True)to replace it. Thedata_sourceparameter toserver()(Shiny) has also been removed; calladd_table()beforeserver()instead. (#195) -
.app()'sbookmark_storeparameter has been removed. Passhistory=shinychat.types.HistoryOptions(restore_mode="bookmark")to get the same shareable-bookmark behavior; any otherhistoryvalue disables Shiny-level bookmarking for the generated app..app()defaults torestore_mode="bookmark"when nohistoryis set anywhere, so existing.app()callers keep working without changes. Note this default is a storage-mechanism change, not just a rename: the old default (bookmark_store="url") encoded the entire bookmark state in the URL itself, requiring no server storage; the new default requires server-side bookmark storage (bookmark_store="server"), with just a short state ID in the URL. Deployments that relied on.app()being fully stateless should passhistory=Falseor a non-bookmarkHistoryOptions().
Deprecated
.server()'s andQueryChatExpress'senable_bookmarkingparameter is deprecated in favor ofhistory. Passhistory=shinychat.types.HistoryOptions(restore_mode="bookmark")instead ofenable_bookmarking=Truefor the equivalent behavior.