Skip to content

feat(pkg-py): Add ggsql visualization integration#201

Draft
cpsievert wants to merge 17 commits intomainfrom
feat/ggsql-integration
Draft

feat(pkg-py): Add ggsql visualization integration#201
cpsievert wants to merge 17 commits intomainfrom
feat/ggsql-integration

Conversation

@cpsievert
Copy link
Contributor

@cpsievert cpsievert commented Jan 21, 2026

Summary

Integrates ggsql with querychat's Shiny integration to enable LLM-generated data visualizations via a new visualize_query tool that extends the existing tool system.

Uses ggsql's two-stage API (reader.execute()writer.render()) to gain access to the writer-independent Spec object, enabling structured metadata access (row/column counts, VISUALISE clause, etc.) rather than regex-parsing raw strings.

New Features

  • visualize_query tool (opt-in): Creates charts from SQL queries with a VISUALISE clause. The LLM writes a full ggsql query (SQL + VISUALISE), which is executed and rendered as an Altair chart.

  • Two-stage ggsql pipeline: A hybrid execute_ggsql() helper uses DataSource for SQL execution (preserving database pushdown for SQLAlchemy/ibis backends), then feeds the result into a ggsql.DuckDBReader for VISUALISE processing → SpecVegaLiteWriter → Altair chart.

  • Accessor methods on QueryChatExpress:

    • ggvis() — Returns rendered Altair chart object
    • ggsql() — Returns ggsql specification string
    • ggtitle() — Returns visualization title
  • Tabbed UI: Shiny app() displays Data / Query Plot tabs when viz tool is enabled

  • System prompt: ggsql syntax reference included when the visualization tool is enabled

Installation

The visualization tool requires the viz extra:

pip install querychat[viz]

This installs ggsql, altair, and shinywidgets as optional dependencies.

Usage

The visualization tool is opt-in via the tools parameter:

from querychat import QueryChat

qc = QueryChat(
    df, "my_data",
    tools=("update", "query", "visualize_query"),
)

The default remains ("update", "query") — no visualization unless explicitly requested.

Key Files

  • pkg-py/src/querychat/_ggsql.pyexecute_ggsql(), spec_to_altair(), to_polars(), extract_title()
  • pkg-py/src/querychat/tools.pytool_visualize_query implementation
  • pkg-py/src/querychat/prompts/tool-visualize-query.md — Tool prompt template
  • pkg-py/src/querychat/prompts/prompt.md — System prompt with ggsql syntax section
  • pkg-py/src/querychat/_shiny.py — Shiny UI (Query Plot tab, Express accessors)
  • pkg-py/src/querychat/_shiny_module.py — Reactive viz state and chart rendering
  • pyproject.tomlviz optional extra

Test Coverage

  • New test files:
    • test_ggsql.pyexecute_ggsql pipeline, spec_to_altair, extract_title, ggsql.validate() tests
    • test_viz_tools.py — Visualization tool unit tests (dependency checks, tool creation, rendering, error handling)
    • test_visualization_tabs.py — Playwright tests for Shiny viz tabs

Before Merging

  • Test Shiny app with real LLM interactions
  • Verify visualizations render correctly
  • Update user-facing docs with visualization examples
  • Update changelog

🤖 Generated with Claude Code

cpsievert and others added 3 commits March 3, 2026 14:33
Add core support for LLM-generated visualizations using ggsql syntax:

- Add ggsql and altair dependencies to pyproject.toml
- Create _ggsql.py with helpers for parsing and rendering visualizations
- Extend AppState with visualization state fields (filter_viz_*, query_viz_*)
- Implement visualize_dashboard and visualize_query tools in tools.py
- Add prompt templates for visualization tools with ggsql syntax reference
- Update system prompt with ggsql grammar documentation
- Add visualization accessor methods to QueryChatBase
- Export visualization data types (VisualizeDashboardData, VisualizeQueryData)

The ggsql DSL allows the LLM to generate chart specifications that are
rendered to Altair/Vega-Lite charts, supporting bar, line, point, area,
and boxplot marks with various encodings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Integrate ggsql visualization capabilities into all supported frameworks:

Shiny:
- Add visualization state to ServerValues dataclass
- Implement ggvis(), ggsql(), ggtitle() reactive accessors
- Add filter visualization re-rendering on data change
- Create tabbed UI with Data/Filter Plot/Query Plot tabs

Streamlit:
- Add ggvis(), ggsql(), ggtitle() methods reading from session state
- Create tabbed app layout with visualization tabs
- Render Altair charts with expandable ggsql specs

Gradio:
- Add ggvis(), ggsql(), ggtitle() methods taking state dict
- Create tabbed Blocks layout with visualization displays
- Wire state changes to update all visualization outputs

Dash:
- Add visualization callbacks and state management
- Create tabbed layout with dcc.Graph for Altair charts
- Add ggsql spec display in collapsible sections

All frameworks enable visualization tools by default and support
both filter (dashboard) and query visualizations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive test coverage for ggsql visualization features:

- test_ggsql.py: Unit tests for ggsql parsing and rendering
- test_ggsql_integration.py: Integration tests for end-to-end visualization
- test_viz_tools.py: Tests for visualize_dashboard and visualize_query tools
- test_visualization_tabs.py: Playwright tests for UI tab interactions
- Update test_state.py with visualization state field tests
- Update test_tools.py and test_base.py for new tool configurations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ge ggsql API

Remove the visualize_dashboard tool to focus on visualize_query. Switch
from the one-shot ggsql.render_altair() API to the two-stage API
(reader.execute → writer.render), gaining access to ggsql's writer-
independent Spec object for structured metadata access.

The new execute_ggsql() helper uses a hybrid approach: DataSource handles
SQL execution (preserving database pushdown), then a ggsql DuckDBReader
processes the VISUALISE portion to produce a Spec. Title extraction now
reads from the rendered Vega-Lite JSON rather than regex-parsing the
VISUALISE string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cpsievert and others added 4 commits March 4, 2026 15:59
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a footer bar to inline chart tool results with a Show/Hide Query
toggle (backed by a read-only bslib code editor), a Save dropdown for
PNG/SVG export via the Vega view API, and a clipboard copy button.
Includes a ggsql Prism grammar extension (ggsql-grammar.js) that adds
VISUALISE/DRAW/LABEL/FACET/SCALE/THEME keyword highlighting at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- htmltools doesn't have `tags.path`, use `Tag("path", ...)` instead
- Extend SQL grammar in-place rather than creating separate ggsql
  language, since input_code_editor uses language="sql"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cpsievert cpsievert force-pushed the feat/ggsql-integration branch from f1aef08 to 1c3c8e3 Compare March 5, 2026 23:42
cpsievert and others added 8 commits March 5, 2026 17:43
Replace hardcoded 300px chart height with CSS aspect-ratio (4/3) on the
widget container, so charts scale proportionally to chat width. Uses
height="container" + width="container" in Altair so Vega-Lite fills the
aspect-ratio box. Full-screen mode unsets the aspect-ratio to allow
flexbox-based filling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hide Vega's built-in save/export dropdown since the viz card already
has its own save button. Also change chart aspect ratio from 4/3 to 4/2
to reduce chart height.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Translate the TextMate grammar from posit-dev/ggsql into Prism format.
Fix import bug (was importing languageMap, not languages registry).
Add token types: geom, aesthetic, scale-type, theme, project, functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Translate the TextMate grammar from posit-dev/ggsql into Prism format.
Fix import bug (was importing languageMap from index.js, not languages
from the internal grammar registry). Add token types: geom, aesthetic,
scale-type, theme, project, functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
input_code_editor has a built-in copy button extension, so the custom
one is redundant. Removes the button HTML, its JS click handler, and
its CSS rules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Custom token names (ggsql-keyword, ggsql-geom, etc.) have no CSS in
the Prism theme. Use Prism's alias feature to map each to a standard
token class (keyword, builtin, attr-name, class-name, operator).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous polling approach extended the grammar AFTER the code
editor had already tokenized, so ggsql tokens never rendered. Now
uses Object.defineProperty to intercept the moment sql.js assigns
languages.sql, extending it before the editor ever reads it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.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.

1 participant