querychat

A self-service analytics platform

Give users a natural language interface to your data — in 3 lines.

The 3-line analytics app

app.py
from querychat import QueryChat
from querychat.data import titanic

qc = QueryChat(titanic(), "titanic")
app = qc.app()
shiny run app.py

A real dashboard

app.py
from shiny.express import render, ui
from querychat.express import QueryChat
from querychat.data import titanic

qc = QueryChat(titanic(), "titanic")
qc.sidebar()

with ui.card():
    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(fillable=True, title="Titanic Explorer")

Built-in tools

Tool What it does User sees
filter SELECT * WHERE ... — updates the dashboard Data table + title update
query Arbitrary analytics SQL — returns results SQL + result table in chat
visualize SQL + chart spec — renders inline Altair chart in chat
# Default: filter + query
qc = QueryChat(df, "data")

# All three
qc = QueryChat(df, "data", tools=("filter", "query", "visualize"))

All tools are read-only — no INSERT, UPDATE, or DELETE.

Tools in action

SCREENSHOT: querychat Titanic app showing a conversation —
1. User asks “show me first-class passengers who survived” → dashboard filters
2. User asks “what was the survival rate by class?” → query result table in chat
(capture a real session to show the natural flow)

Context management

querychat auto-generates context from your data:

Auto-generated

  • Column names and SQL types
  • Numeric ranges (min/max)
  • Categorical values (≤20 unique)

The LLM knows your schema without seeing your data.

User-supplied

QueryChat(
    df, "titanic",
    data_description=(
        "survived: 1 = survived, "
        "0 = did not survive"
    ),
    extra_instructions=(
        "Use British spelling. "
        "Refuse off-topic questions."
    ),
)

The security model

❌ The naive approach

Send your CSV to the LLM.

  • Data leaves your server
  • Doesn’t scale past ~100K rows
  • No reproducibility
  • No verification

✅ The querychat approach

LLM writes SQL. You execute it locally.

  • Data stays on your server
  • Scales via DuckDB
  • SQL is reproducible
  • SQL is verifiable and read-only

The LLM is a translator (language → SQL), not a database.

Works everywhere

Shiny ✨

Best experience — native reactivity. qc.df() drives the whole dashboard.

Streamlit

from querychat.streamlit \
    import QueryChat

Dash

from querychat.dash \
    import QueryChat

Gradio

from querychat.gradio \
    import QueryChat

Same QueryChat API — swap the import, keep your code.

Data sources

querychat works with data you already have:

import pandas as pd
from sqlalchemy import create_engine

# In-memory DataFrames (pandas, polars, pyarrow)
qc = QueryChat(pd.read_csv("sales.csv"), "sales")

# Databases (via SQLAlchemy)
engine = create_engine("duckdb:///warehouse.duckdb")
qc = QueryChat(engine, "transactions")

# Pins boards (shared data)
qc = QueryChat(board, "monthly_metrics")

🛠️ Hands-on: build your own analytics app

~15 minutes — work through as many as you can:

1️⃣ Your own data

Swap the Titanic dataset for your own CSV (or one we provide).

Add a data_description to help the LLM.

Register tool_web_search() so the assistant can answer questions beyond the dataset.

3️⃣ Add a custom tool

Write a tool that does something the built-in tools can’t — e.g., export to CSV, send a summary email, etc.

Exercise file: exercises/03-querychat-starter.py