Skip to content

feat(pkg-py): add ggsql visualization tool#201

Open
cpsievert wants to merge 33 commits intomainfrom
feat/ggsql-integration
Open

feat(pkg-py): add ggsql visualization tool#201
cpsievert wants to merge 33 commits intomainfrom
feat/ggsql-integration

Conversation

@cpsievert
Copy link
Copy Markdown
Contributor

@cpsievert cpsievert commented Jan 21, 2026

Summary

Adds an opt-in visualize tool to querychat's Python package, enabling LLM-generated data visualizations via ggsql. When enabled, the LLM can write ggsql queries (SQL + VISUALISE clause) that are executed and rendered as interactive Altair charts inline in the chat.

Usage

Install

pip install querychat[viz]

This installs the optional dependencies: ggsql, altair, shinywidgets, and vl-convert-python.

Example app

from querychat import QueryChat
from querychat.data import titanic

qc = QueryChat(
    titanic(),
    "titanic",
    tools=("query", "visualize"),
)

app = qc.app()

The visualize tool is opt-in — include it in the tools tuple alongside "query" and/or "update" to enable it.

What it looks like

Charts render inline in the chat with a collapsible footer showing the ggsql query (with syntax highlighting), a copy button, and save options:

Screenshot 2026-03-20 at 7 04 42 PM

Key changes

  • visualize tool (opt-in via tools=("query", "visualize")): The LLM writes a full ggsql query, which is executed against the data source and rendered as an Altair chart.
  • Two-stage ggsql pipeline (_viz_ggsql.py): Uses DataSource for SQL execution (preserving database pushdown), then feeds results into ggsql for VISUALISE processing → Altair chart.
  • Inline chart rendering (_viz_altair_widget.py): Custom AltairWidget for rendering charts directly in the chat stream, with JS/CSS assets for interactive display.
  • Prompt restructuring: System and tool prompts were reorganized into a unified structure. The LLM is guided to favor visualization over redundant table output, and preparatory queries are collapsed. A comprehensive ggsql syntax reference is included for the LLM.
  • Viz state is internal only: No public accessor methods or reactive values for viz state — it's managed entirely by the module/app internals.
    • This is somewhat intentional for now given that there will likely be a "second pass" at ggsql support. At that point, Maybe it will become more clear what sort of state accessor methods might be useful beyond "give me the latest chart".
  • Documentation: Added a visualization guide (docs/visualize.qmd) with examples and screenshots.

Comment thread pyproject.toml Outdated
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from a21712c to 05687d5 Compare March 3, 2026 20:34
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from 05687d5 to 8f33d70 Compare March 3, 2026 20:38
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from 8f33d70 to a3bdcbd Compare March 3, 2026 20:40
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from a3bdcbd to 3e15610 Compare March 3, 2026 20:52
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from 3e15610 to ac299be Compare March 4, 2026 00:04
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from 1450755 to a711326 Compare March 4, 2026 01:10
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from a711326 to 8696fca Compare March 4, 2026 01:15
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from 8696fca to db264dd Compare March 4, 2026 01:16
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from db264dd to 5a7e9de Compare March 4, 2026 01:19
@cpsievert cpsievert requested a review from gadenbuie April 9, 2026 14:53
@cpsievert cpsievert marked this pull request as ready for review April 13, 2026 19:06
cpsievert and others added 8 commits April 16, 2026 13:43
Integrate main's deferred client initialization (#207) and narwhals
version constraint (#218) with the ggsql visualization feature branch.
Extended _create_session_client with visualize_query parameter and
updated stub session test to match eager client construction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
chatlas 0.16.0 added stream_content as a new abstract method on Provider,
causing DummyProvider instantiation to fail in CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
shallow copy + nested attribute mutation was modifying the original
chart's sub-specs through shared references, violating the function's
documented contract.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
narwhals.stable.v1.from_native wraps ibis Tables as DataFrame (not
LazyFrame), so the isinstance(nw_df, nw.LazyFrame) check was False and
collect() was never called, causing 'IbisLazyFrame has no attribute
to_polars'. Delegate to as_narwhals() which already handles ibis
correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…identifiers

Snowflake uppercases unquoted identifiers, causing case mismatches when
ggsql validates VISUALISE column references against DuckDB results.
Replace the generic casing note with explicit wrong/correct examples
instructing the LLM to alias uppercase columns to lowercase in SELECT.

Borrowed from ggsqlbot's proven prompt pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Snowflake uppercases all unquoted identifiers, so columns come back as
AVG_INTEREST_RATE even when the LLM writes AS avg_interest_rate. Since
ggsql validates VISUALISE column references case-sensitively and DuckDB
is case-insensitive, lowercasing the DataFrame columns before registering
ensures the LLM's lowercase references always match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@gadenbuie gadenbuie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! I have a few high-level comments at the design and integration layers; I did not review the Python code in detail but I scanned the changes and nothing jumped out at me in that area.

Comment thread pkg-py/docs/visualize.qmd
Comment thread pkg-py/examples/10-viz-app.py Outdated
Comment on lines +13 to +23
#def app_ui(request):
# return ui.page_fillable(
# qc.ui(),
# )
#
#
#def server(input, output, session):
# qc.server(enable_bookmarking=True)
#
#
#app = App(app_ui, server, bookmark_store="url")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this left here intentionally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I had been using this for testing and forgot to make it more user facing. I've just kept it basic for now and using Shiny Express.

Comment thread pkg-py/src/querychat/prompts/prompt.md Outdated
Comment thread pkg-py/src/querychat/prompts/prompt.md Outdated
Comment thread pkg-py/src/querychat/prompts/tool-query.md
Comment thread pkg-py/src/querychat/prompts/tool-update-dashboard.md
Comment thread pkg-py/src/querychat/prompts/tool-visualize.md Outdated
Comment thread pkg-py/src/querychat/static/js/viz-preload.js Outdated
cpsievert and others added 9 commits April 17, 2026 22:26
Adds truncate_error() in _utils to cap error strings sent to the LLM,
stripping schema dumps and applying a hard character limit. Wired into
all tool error catch blocks in tools.py and _viz_tools.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updates syntax guide for ggsql v0.2.4 breaking changes: rect→tile,
linear merged into rule, array syntax to parentheses, updated text
aesthetics. Fixes pre-existing errors (invalid position fill, nonexistent
density stacking). Bumps ggsql dependency to >=0.2.4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses PR feedback that "visualize_query" sounds like it might
provide a visual representation of a query rather than visualizing data.

Renames across source, prompts, docs, examples, and tests:
- Tool name: querychat_visualize_query → querychat_visualize
- Functions: tool_visualize_query → tool_visualize
- Types: VisualizeQueryData → VisualizeData, VisualizeQueryResult → VisualizeResult
- Template var: has_tool_visualize_query → has_tool_visualize
- Prompt file: tool-visualize-query.md → tool-visualize.md
- User-facing string: "visualize_query" → "visualize"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LLM was generating trailing commas in LABEL clauses causing parse errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restores the title + description + usage guidance convention in
tool-query.md and tool-update-dashboard.md, keeping new additions
like the collapsed parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from de20a94 to 231e3ef Compare April 20, 2026 16:38
cpsievert and others added 3 commits April 20, 2026 11:48
Adds title/description/when-to-use/constraints to tool-visualize.md
following the same convention as the other tool prompts. Removes
duplicated routing and error recovery guidance from the system prompt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests now check for "Avoid redundant expanded results" instead of
removed headings/content that moved to tool-visualize.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds null mapping, rect/errorbar geoms, subtitle/caption labels,
lollipop/ridgeline examples, scale oob setting, and facet panel
filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

This comment was marked as resolved.

cpsievert and others added 2 commits April 20, 2026 12:13
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants