express.QueryChat
express.QueryChat(
data_source=None,
table_name=None,
*,
id=None,
greeting=None,
client=None,
tools=DEFAULT_TOOLS,
data_dict=None,
extra_instructions=None,
prompt_template=None,
categorical_threshold=20,
data_description=None,
history=None,
enable_bookmarking='auto',
)Use QueryChat with Shiny Express.
This class makes it easy to use querychat within Shiny Express apps – it automatically calls .server() during initialization, so you don’t have to do it manually.
Examples
from querychat.express import QueryChat
from seaborn import load_dataset
from shiny.express import render, ui
from shinychat.types import HistoryOptions
titanic = load_dataset("titanic")
qc = QueryChat(titanic, "titanic", history=HistoryOptions(restore_mode="bookmark"))
qc.sidebar()
with ui.card(fill=True):
with ui.card_header():
@render.text
def title():
return qc.title() or "Titanic Dataset"
@render.data_frame
def data_table():
return qc.df()
ui.page_opts(
title="Titanic QueryChat App",
fillable=True,
)Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| data_source | IntoFrame | sqlalchemy.Engine | ibis.Table | None |
Either a Narwhals-compatible data frame (e.g., Polars or Pandas) or a SQLAlchemy engine containing the table to query against. Can be None for deferred binding (set via the data_source property before the real session starts). |
None |
| table_name | str | None | If a data_source is a data frame, a name to use to refer to the table in SQL queries (usually the variable name of the data frame, but it doesn’t have to be). If a data_source is a SQLAlchemy engine, the table_name is the name of the table in the database to query against. | None |
| id | Optional[str] | An optional ID for the QueryChat module. If not provided, an ID will be generated based on the table_name. | None |
| greeting | Optional[str | Path] | A string in Markdown format, containing the initial message. If a pathlib.Path object is passed, querychat will read the contents of the path into a string with .read_text(). You can use querychat.greeting() to help generate a greeting from a querychat configuration. If no greeting is provided, one will be generated at the start of every new conversation. |
None |
| client | Optional[str | chatlas.Chat] |
A chatlas.Chat object or a string to be passed to chatlas.ChatAuto()’s provider_model parameter, describing the provider and model combination to use (e.g. "openai/gpt-4.1", “anthropic/claude-sonnet-4-5”, “google/gemini-2.5-flash”. etc). If client is not provided, querychat consults the QUERYCHAT_CLIENT environment variable. If that is not set, it defaults to "openai". |
None |
| data_dict | DataDict | str | Path | None |
A :class:~querychat.DataDict instance, or a path (str or pathlib.Path) to a YAML file, that provides rich per-table and per-column metadata. When set, documented columns use the dict’s values, range, and description fields instead of querying the data source for statistics, which speeds up schema generation and improves LLM context. Supersedes data_description. |
None |
| extra_instructions | Optional[str | Path] | Additional instructions for the chat model. If a pathlib.Path object is passed, querychat will read the contents of the path into a string with .read_text(). |
None |
| prompt_template | Optional[str | Path] | Path to or a string of a custom prompt file. If not provided, the default querychat template will be used. This should be a Markdown file that contains the system prompt template. The mustache template can use the following variables: - {db_engine}: The database engine used (e.g., “DuckDB”) - {schema}: The schema of the data source, generated by data_source.get_schema() - {data_description}: The optional data description provided - {extra_instructions}: Any additional instructions provided |
None |
| categorical_threshold | int | Threshold for determining if a column is categorical based on number of unique values. | 20 |
| data_description | Optional[str | Path] | Optional plain-text or Markdown description of the data, as a string or file path. Superseded by data_dict for new code. |
None |
Attributes
| Name | Description |
|---|---|
| data_source | Removed. Use add_table() and remove_table() to manage tables. |
| greeter | Greeting configuration and generator for this QueryChat instance. |
| system_prompt | Get the system prompt. |
Methods
| Name | Description |
|---|---|
| add_table | Add or replace a table in the QueryChat instance. |
| add_tables | Add multiple tables from a SQLAlchemy engine or Ibis backend in a single call. |
| cleanup | Clean up resources this object created. |
| client | Create a chat client with registered tools. |
| console | Launch an interactive console chat with the data. |
| current_table | Reactively read the name of the most recently queried table. |
| df | Reactively read the current filtered data frame that is in effect. |
| generate_greeting | Generate a welcome greeting for the chat. |
| page | Create a full-window Express page containing the querychat UI. |
| remove_table | Remove a table from the QueryChat instance. |
| sidebar | Create a sidebar containing the querychat UI. |
| sql | Reactively read (or set) the current SQL query that is in effect. |
| table | Get a per-table accessor with reactive state. |
| table_names | Return the names of all registered tables. |
| title | Reactively read (or set) the current title that is in effect. |
| ui | Create the UI for the querychat component. |
add_table
express.QueryChat.add_table(
data_source,
table_name,
*,
replace=False,
include_in_greeting=False,
)Add or replace a table in the QueryChat instance.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| data_source | IntoFrame | sqlalchemy.Engine | BaseBoard |
The data source (DataFrame, LazyFrame, database connection, or pins board). | required |
| table_name | str | Name for the table. | required |
| replace | bool | If True, replace an existing table with the same name. If False (default), raise ValueError if the table already exists. | False |
| include_in_greeting | bool | If True, include this table’s schema in the greeting system prompt. | False |
Raises
| Name | Type | Description |
|---|---|---|
| TypeError | If include_in_greeting is not a bool. | |
| ValueError | If table_name already exists (and replace=False) or is invalid. | |
| RuntimeError | If called to replace or remove an existing table after a session has started. |
add_tables
express.QueryChat.add_tables(
data_source,
tables=None,
*,
replace=False,
include_in_greeting=False,
)Add multiple tables from a SQLAlchemy engine or Ibis backend in a single call.
Unlike calling :meth:add_table repeatedly, this method builds the system prompt exactly once after all tables have been staged, avoiding N-1 spurious intermediate rebuilds.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| data_source | sqlalchemy.Engine | SQLBackend |
A SQLAlchemy engine or Ibis SQL backend. Pass individual DataFrames or other sources via :meth:add_table. |
required |
| tables | list[str] | None | Table names to register. When None, all tables returned by the backend’s table-discovery method are used. |
None |
| replace | bool | If True, replace any existing table whose name appears in tables. If False (default), raise ValueError if any name already exists. |
False |
| include_in_greeting | bool | list[str] | True to include all added tables in the greeting, False (default) for none, or a list of table names to include. Any other type raises TypeError. |
False |
Raises
| Name | Type | Description |
|---|---|---|
| TypeError | If data_source is not a sqlalchemy.Engine or Ibis SQL backend. |
|
| ValueError | If the resolved table list is empty, any name is invalid, or any name already exists (and replace=False). |
|
| RuntimeError | If called to replace or remove an existing table after a session has started. |
Examples
Register all tables from a SQLAlchemy engine:
>>> qc = QueryChat()
>>> qc.add_tables(engine)Register a specific subset:
>>> qc.add_tables(engine, ["orders", "customers"])Register all tables from an Ibis backend:
>>> import ibis
>>> backend = ibis.duckdb.connect("mydb.duckdb")
>>> qc.add_tables(backend)cleanup
express.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, engines, and backends you passed in are never closed. Also closes the chatlas client, but only if querychat created it from a spec (client=None or a string such as "openai/gpt-4o"); a chatlas.Chat you supplied is left open.
Resources a session registers via .server(data_source=...) are released when that session ends, not here.
Safe to call multiple times. In long-lived applications, call this when the app shuts down (e.g., via atexit).
client
express.QueryChat.client(
tools=MISSING,
update_dashboard=None,
reset_dashboard=None,
visualize=None,
)Create a chat client with registered tools.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| tools | TOOL_GROUPS | tuple[TOOL_GROUPS, …] | MISSING_TYPE | None |
Which tools to include: "filter", "query", "visualize", or a combination. The legacy name "update" is still accepted as an alias for "filter". |
MISSING |
| update_dashboard | Callable[[UpdateDashboardData], None] | None |
Callback when update_dashboard tool succeeds. | None |
| reset_dashboard | ResetDashboardCallback | None |
Callback when reset_dashboard tool is invoked. | None |
| visualize | Callable[[VisualizeData], None] | None |
Callback when visualize tool succeeds. | None |
Returns
| Name | Type | Description |
|---|---|---|
chatlas.Chat |
A configured chat client. |
console
express.QueryChat.console(new=False, tools='query', **kwargs)Launch an interactive console chat with the data.
current_table
express.QueryChat.current_table()Reactively read the name of the most recently queried table.
Returns None if no query has run yet in this session. Useful for auto-switching a tabbed UI to the active table.
Returns
| Name | Type | Description |
|---|---|---|
| str or None | Table name, or None. |
df
express.QueryChat.df()Reactively read the current filtered data frame that is in effect.
Returns
| Name | Type | Description |
|---|---|---|
IntoFrameT |
The current filtered data frame, in the same format as the original data source (e.g., polars DataFrame, Polars LazyFrame, Ibis Table). If no query has been set, returns the unfiltered data from the data source. |
generate_greeting
express.QueryChat.generate_greeting(echo='none')Generate a welcome greeting for the chat.
page
express.QueryChat.page(title, *, id=None, **kwargs)Create a full-window Express page containing the querychat UI.
This wraps shinychat.express.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.
Since page_chat() owns the entire page layout, this must be the only top-level UI item in the Express app.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| title | Page title displayed in the header. When it is a string and window_title is omitted, it is also used as the document title. |
required | |
| id | Optional[str] | Optional ID for the QueryChat instance. If not provided, will use the ID provided at initialization. | None |
| **kwargs | Additional arguments passed to shinychat.express.page_chat(). |
{} |
Returns
| Name | Type | Description |
|---|---|---|
| The page’s chat root, returned so Express can display it. It must remain the sole top-level UI item: do not assign it to a variable or wrap it in other UI. |
remove_table
express.QueryChat.remove_table(table_name)Remove a table from the QueryChat instance.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| table_name | str | Name of the table to remove. | required |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If table doesn’t exist or is the last remaining table. | |
| RuntimeError | If called to replace or remove an existing table after a session has started. |
sql
express.QueryChat.sql(query=None)Reactively read (or set) the current SQL query that is in effect.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| query | Optional[str] | If provided, sets the current SQL query to this value. | None |
Returns
| Name | Type | Description |
|---|---|---|
| str | bool | None | If no query is provided, returns the current SQL query as a string (or None if no query has been set). If a query is provided, returns True if the query was changed to a new value, or False if it was the same as the current value. |
table
express.QueryChat.table(name)Get a per-table accessor with reactive state.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| name | str | Table name (must match a name passed to add_table()). |
required |
Returns
| Name | Type | Description |
|---|---|---|
TableAccessor |
Accessor with df(), sql(), and title() backed by per-session reactive state. |
Examples
from querychat.express import QueryChat
from shiny.express import render
qc = QueryChat(orders, "orders")
qc.add_table(customers, "customers")
qc.sidebar()
@render.data_frame
def orders_table():
return qc.table("orders").df()
@render.data_frame
def customers_table():
return qc.table("customers").df()table_names
express.QueryChat.table_names()Return the names of all registered tables.
Returns
| Name | Type | Description |
|---|---|---|
| list[str] | List of table names in the order they were added. |
title
express.QueryChat.title(value=None)Reactively read (or set) the current title that is in effect.
The title is a short description of the current query that the LLM provides to us whenever it generates a new SQL query. It can be used as a status string for the data dashboard.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| value | Optional[str] | If provided, sets the current title to this value. | None |
Returns
| Name | Type | Description |
|---|---|---|
| str | bool | None | If no value is provided, returns the current title as a string, or None if no title has been set due to no SQL query being set. If a value is provided, sets the current title to this value and returns True if the title was changed to a new value, or False if it was the same as the current value. |
ui
express.QueryChat.ui(id=None, **kwargs)Create the UI for the querychat component.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| id | Optional[str] | Optional ID for the QueryChat instance. If not provided, will use the ID provided at initialization. | None |
| **kwargs | Additional arguments to pass to shinychat.chat_ui(). |
{} |
Returns
| Name | Type | Description |
|---|---|---|
| A UI component. |