Skip to main content

Overview

This example is a fullstack chat agent that answers natural-language questions about the data in a ClickHouse Cloud database — and presents the answers as interactive charts, tables, stat cards and maps instead of walls of text. The agent discovers the schema, writes ClickHouse SQL, runs it through the official ClickHouse Node.js client, then calls a renderVisualization tool with a json-render spec that a Next.js chat UI renders live with shadcn/ui components. Tech stack:
  • Trigger.dev AI chat for the agent session, turn loop, streaming and resumability
  • AI Prompts for a versioned system prompt with dashboard overrides and per-generation LLM observability
  • ClickHouse Node.js client (@clickhouse/client) for queries over HTTPS
  • AI SDK with Anthropic Claude for the model and tool calling, and useChat on the frontend
  • json-render with the @json-render/shadcn component library for generative UI
  • Next.js chat app using useTriggerChatTransport — the browser talks directly to Trigger.dev, no API route to maintain
  • shadcn charts (Recharts) and mapcn (MapLibre GL, free CARTO tiles) for the chart and map components
Features:
  • Generative UI: a renderVisualization tool takes a json-render spec — bar/line/area/pie charts, data tables, stat-card KPI rows and point maps, composed in cards and grids — with the query results inlined. Specs are validated against the component catalog and errors are returned to the model, so it corrects the spec and retries.
  • One shared catalog: the same module generates the system-prompt component reference and validates tool calls, so the prompt and the renderer can’t drift apart
  • Versioned system prompt: defined with prompts.define(), resolvable per-run, overridable from the dashboard without redeploying — and storing it via chat.prompt.set() wires up experimental_telemetry, so every model call appears in the run trace with token, cost and latency metrics
  • Schema discovery tools: listTables reads table names, engines and row counts from system.tables; describeTable returns column names and types using bound Identifier query params, so table names are never interpolated into SQL strings
  • Read-only query tool: runQuery accepts SELECT-style statements only, enforced in code and backed by ClickHouse settings — readonly=2, a 1,000-row result cap, and a 30 second execution timeout
  • Self-correcting SQL: query errors are returned to the model as tool output, so the agent reads the ClickHouse error, fixes its SQL, and retries

GitHub repo

View the ClickHouse chat agent repo

Click here to view the full code for this project in our examples repository on GitHub. You can fork it and use it as a starting point for your own project.

How it works

The agent

The agent is defined with chat.agent(). The system prompt is a versioned AI Prompt: the editable analyst guidance lives in the prompt template, while the json-render component reference is generated from the catalog at run time and injected as a template variable. Storing the resolved prompt with chat.prompt.set() lets chat.toStreamTextOptions() supply the system text, model, config and telemetry:
src/trigger/clickhouse-agent.ts
On AI SDK v5/v6, experimental_telemetry comes from the stored prompt via chat.toStreamTextOptions() — without chat.prompt.set(), model calls don’t appear as spans in the run trace.

Generative UI with one shared catalog

A single module defines which components the model may use: Table, Card, Grid, Badge and friends from @json-render/shadcn, plus custom chart components (shadcn charts on Recharts), a Stat card, and a PointMap built on mapcn. The same catalog produces the system-prompt reference and validates tool calls:
src/lib/catalog.ts
The renderVisualization tool accepts a flat json-render spec with the data rows inlined from earlier runQuery results. Validation failures go back to the model as tool output:
src/trigger/clickhouse-agent.ts

The Next.js chat UI

The frontend uses useChat with useTriggerChatTransport — the browser subscribes to the session’s streams directly, authenticated by two small server actions. renderVisualization tool parts in the message stream render through json-render’s <Renderer> with the shadcn component registry:
src/components/chat.tsx
The registry maps every catalog component to its React implementation — the stock @json-render/shadcn components plus the custom charts and map:
src/lib/registry.tsx

The query tool

runQuery guards against writes twice: a statement allowlist in code, and ClickHouse settings on the request itself. Errors are returned to the model instead of thrown, which is what makes the agent self-correct:
src/trigger/clickhouse-agent.ts

Running it

The example needs CLICKHOUSE_URL and ANTHROPIC_API_KEY set in the Trigger.dev dashboard on the Environment Variables page, and TRIGGER_PROJECT_REF plus TRIGGER_SECRET_KEY in the local .env for the Next.js server actions:
.env
Run the agent and the app in two terminals, then open http://localhost:3000:
With a dataset like NYC Taxi loaded, asking for a dashboard of daily trip volume, hourly demand and revenue by payment type produces a stat-card KPI row, two charts and a pie in one composed card — and asking “Where do trips start and end?” produces two interactive maps with size-scaled markers.

Relevant code

  • Agent + tools: src/trigger/clickhouse-agent.ts: the chat.agent() definition, the versioned prompt, the four tools, the read-only guards, and the ClickHouse client
  • Shared catalog: src/lib/catalog.ts: component definitions, prompt-reference generation, and spec validation
  • Component registry: src/lib/registry.tsx: maps catalog components to shadcn/Recharts/mapcn implementations
  • Chat UI: src/components/chat.tsx: useChat + useTriggerChatTransport, message parts, and visualization rendering
  • Server actions: src/app/actions.ts: session creation and token minting

Learn more

AI chat overview

How chat agents, sessions, and the turn loop work.

Frontend

The chat transport, session tokens, and reconnection.

AI Prompts

Versioned prompts with dashboard overrides and generation tracking.

Tools

Declaring tools on your agent and how they persist across turns.