Shiny is a web framework for building interactive data applications. It provides a reactive programming model that’s a natural fit for querychat.
In this guide, you’ll learn how to build Shiny apps with querychat to enable rich data exploration experiences where data views update based on natural language filters.
Initialize QueryChat
The “main” QueryChat class is available directly from the top-level module, and it designed to work with Shiny (Core):
from querychat import QueryChat
For Shiny Express, import from querychat.express instead (similar to how you import from shiny.express):
from querychat.express import QueryChat
Once imported, initialize it with your data source:
from querychat.data import titanicqc = QueryChat(titanic(), "titanic")
Quick start with .app()
Remember, the simplest way to get started is with .app(), which gives a “pre-baked” Shiny app:
After initializing QueryChat, use .sidebar() or .ui() to place the chat interface in your app. As users interact with the chat, .df(), .sql(), and .title() automatically update to reflect the current query. Any Shiny outputs that depend on these reactive values will re-render automatically.
Method
Description
.sidebar()
Place the chat interface in a sidebar
.ui()
Returns just the chat component for custom placement
.df()
Current filtered/sorted DataFrame
.sql()
Current SQL query (str | None)
.title()
Short description of current filter (str | None)
Note
Shiny has two modes: Express (simple, script-based) and Core (explicit UI/server separation). With Core, call qc.server() in your server function and access reactives via the returned object (e.g., qc_vals.df()). With Express, access them directly on the QueryChat instance (e.g., qc.df()).
Basic sidebar
The most common pattern places chat in the sidebar with your custom filtered views in the main area:
from pathlib import Pathfrom shiny.express import render, uifrom querychat.express import QueryChatfrom querychat.data import titanicgreeting = Path(__file__).parent /"greeting.md"# 1. Provide data source to QueryChatqc = QueryChat(titanic(), "titanic", greeting=greeting)# 2. Add sidebar chat controlqc.sidebar()# 3. Add a card with reactive title and data framewith ui.card():with ui.card_header():@render.textdef title():return qc.title() or"Titanic Dataset"@render.data_framedef data_table():return qc.df()# 4. Set some page options (optional)ui.page_opts( fillable=True, title="Titanic Dataset Explorer")
Deferred data sources
Some data sources, like database connections or reactive calculations, may need to be created within an active Shiny session. To help support this, QueryChat allows you to initialize without a data source and provide it later, like this:
deferred-app.py
from shiny.express import render, session, uifrom querychat.express import QueryChat# Don't create connection until we have an actual sessionif session.is_stub_session(): conn =Noneelse: conn = get_user_connection(session)qc = QueryChat(conn, "users")qc.sidebar()@render.data_framedef table():return qc.df()
from pathlib import Pathfrom shiny import App, render, uifrom querychat import QueryChatfrom querychat.data import titanicgreeting = Path(__file__).parent /"greeting.md"# 1. Provide data source to QueryChatqc = QueryChat(titanic(), "titanic", greeting=greeting)app_ui = ui.page_sidebar(# 2. Create sidebar chat control qc.sidebar(), ui.card( ui.card_header(ui.output_text("title")), ui.output_data_frame("data_table"), fill=True, ), fillable=True)def server(input, output, session):# 3. Add server logic (to get reactive data frame and title) qc_vals = qc.server()# 4. Use the filtered/sorted data frame reactively@render.data_framedef data_table():return qc_vals.df()@render.textdef title():return qc_vals.title() or"Titanic Dataset"app = App(app_ui, server)
Deferred data sources
Some data sources, like database connections or reactive calculations, may need to be created within an active Shiny session. To help support this, QueryChat allows you to initialize without a data source and provide it later, like this:
deferred-app.py
from shiny import App, uifrom querychat import QueryChat# Global scope - create QueryChat without data sourceqc = QueryChat(None, "users")app_ui = ui.page_sidebar( qc.sidebar(), ui.output_data_frame("table"),)def server(input, output, session):# Server scope - create connection with session credentials conn = get_user_connection(session) qc_vals = qc.server(data_source=conn)@render.data_framedef table():return qc_vals.df()app = App(app_ui, server)
Custom chat UI
Use .ui() to place the chat anywhere in your layout. Here we use it simply to place custom content in the sidebar alongside the chat (like a reset button):
Users can also ask the LLM to “reset” or “show all data” to clear filters through the chat interface.
Advanced patterns
Programmatic updates
You can update the query state programmatically using .sql() and .title() as setters. This is useful for adding preset filter buttons or linking filters to other UI controls.
Multiple datasets
To explore multiple datasets, use separate QueryChat instances (i.e., separate chat interfaces).
Multiple tables in one chat?
In some cases, you might be able to “pre-join” datasets into a single table and use one QueryChat instance to explore them together. In the future, we may support multiple filtered tables in one chat interface, but this is not currently available. Please upvote the relevant issue if this is a feature you’d like to see!