Skip to content

Commit c936c79

Browse files
authored
docs: update ClickHouse chat agent example for generative UI (#4251)
Updates the ClickHouse chat agent example page to match the upgraded example (triggerdotdev/examples#124), which is now a fullstack generative-UI chat app rather than an agent-only project. ## What changed - **Overview / tech stack / features** rewritten: Next.js chat app (`useChat` + `useTriggerChatTransport`, no API route), a `renderVisualization` tool taking json-render specs rendered with `@json-render/shadcn` + shadcn charts (Recharts) + mapcn point maps, and a shared catalog that generates both the system-prompt component reference and tool-call validation. - **The agent section** now shows the versioned [AI Prompt](https://trigger.dev/docs/ai/prompts) pattern (`prompts.define()` + `chat.prompt.set()` + `chat.toStreamTextOptions({ registry })`), with a warning that `experimental_telemetry` comes from the stored prompt — the docs previously showed a static `system:` string, which silently ships no LLM observability. - **New sections** for the shared catalog, the `renderVisualization` tool, the Next.js chat UI and registry. - **Relevant code links** updated to the new `src/` layout. - **Learn more** cards now include Frontend and AI Prompts. Note: merge after triggerdotdev/examples#124 lands, so the GitHub file links resolve. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 313fe03 commit c936c79

1 file changed

Lines changed: 183 additions & 31 deletions

File tree

docs/guides/example-projects/clickhouse-chat-agent.mdx

Lines changed: 183 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
---
22
title: "ClickHouse chat agent"
33
sidebarTitle: "ClickHouse chat agent"
4-
description: "Build a chat agent that answers questions about your data by writing and running SQL against ClickHouse Cloud, using chat.agent() and the ClickHouse Node.js client."
4+
description: "Build a chat agent that answers questions about your ClickHouse data with charts, tables and maps instead of text, using chat.agent(), generative UI with json-render, and a Next.js frontend."
55
---
66

77
## Overview
88

9-
This example is a [chat agent](/ai-chat/overview) that answers natural-language questions about the data in a [ClickHouse Cloud](https://clickhouse.com/cloud) database. The agent discovers the schema, writes ClickHouse SQL, runs it through the official [ClickHouse Node.js client](https://clickhouse.com/docs/integrations/javascript), and streams back answers with markdown tables. Trigger.dev handles the chat session, turn loop, streaming, and resumability — the whole agent is one `chat.agent()` call and three tools.
9+
This example is a fullstack [chat agent](/ai-chat/overview) that answers natural-language questions about the data in a [ClickHouse Cloud](https://clickhouse.com/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](https://clickhouse.com/docs/integrations/javascript), then calls a `renderVisualization` tool with a [json-render](https://json-render.dev) spec that a Next.js chat UI renders live with [shadcn/ui](https://ui.shadcn.com) components.
1010

1111
**Tech stack:**
1212

13-
- **[Trigger.dev AI chat](/ai-chat/overview)** for the agent session, turn loop, and streaming
13+
- **[Trigger.dev AI chat](/ai-chat/overview)** for the agent session, turn loop, streaming and resumability
14+
- **[AI Prompts](/ai/prompts)** for a versioned system prompt with dashboard overrides and per-generation LLM observability
1415
- **[ClickHouse Node.js client](https://clickhouse.com/docs/integrations/javascript)** (`@clickhouse/client`) for queries over HTTPS
15-
- **[AI SDK](https://ai-sdk.dev/)** with Anthropic Claude for the model and tool calling
16+
- **[AI SDK](https://ai-sdk.dev/)** with Anthropic Claude for the model and tool calling, and `useChat` on the frontend
17+
- **[json-render](https://json-render.dev)** with the [`@json-render/shadcn`](https://www.npmjs.com/package/@json-render/shadcn) component library for generative UI
18+
- **Next.js** chat app using [`useTriggerChatTransport`](/ai-chat/frontend) — the browser talks directly to Trigger.dev, no API route to maintain
19+
- **shadcn charts** (Recharts) and **[mapcn](https://mapcn.dev)** (MapLibre GL, free CARTO tiles) for the chart and map components
1620

1721
**Features:**
1822

19-
- **Schema discovery tools**: `listTables` reads table names, engines, and row counts from `system.tables`; `describeTable` returns column names and types using a bound `Identifier` query param, so table names are never interpolated into SQL strings
23+
- **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.
24+
- **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
25+
- **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
26+
- **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
2027
- **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
2128
- **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
22-
- **Single environment variable**: the ClickHouse connection is one `CLICKHOUSE_URL` with the credentials embedded, set in the Trigger.dev dashboard
2329

2430
## GitHub repo
2531

@@ -36,25 +42,53 @@ This example is a [chat agent](/ai-chat/overview) that answers natural-language
3642

3743
### The agent
3844

39-
The agent is defined with [`chat.agent()`](/ai-chat/overview). Tools are declared on the config so tool results survive history re-conversion across turns, and the `run` function returns a `streamText()` call:
45+
The agent is defined with [`chat.agent()`](/ai-chat/overview). The system prompt is a versioned [AI Prompt](/ai/prompts): 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:
4046

41-
```ts trigger/clickhouse-agent.ts
47+
```ts src/trigger/clickhouse-agent.ts
48+
import { prompts } from "@trigger.dev/sdk";
4249
import { chat } from "@trigger.dev/sdk/ai";
4350
import { anthropic } from "@ai-sdk/anthropic";
44-
import { stepCountIs, streamText } from "ai";
51+
import { createProviderRegistry, stepCountIs, streamText } from "ai";
52+
import { z } from "zod";
53+
import { catalogPromptSection } from "../lib/catalog";
54+
55+
const registry = createProviderRegistry({ anthropic });
56+
57+
const systemPrompt = prompts.define({
58+
id: "clickhouse-analyst",
59+
model: "anthropic:claude-opus-4-8",
60+
variables: z.object({ componentReference: z.string() }),
61+
content: `You are a ClickHouse data analyst. ...
62+
63+
## renderVisualization spec reference
64+
65+
{{componentReference}}`,
66+
});
4567

4668
export const clickhouseAgent = chat.agent({
4769
id: "clickhouse-agent",
4870
idleTimeoutInSeconds: 300,
49-
tools: { listTables, describeTable, runQuery },
71+
// Declared on the config so tool results survive history re-conversion across turns
72+
tools: { listTables, describeTable, runQuery, renderVisualization },
73+
74+
onChatStart: async () => {
75+
// Latest prompt version (or an active dashboard override), with the
76+
// component reference generated from the catalog so it always matches
77+
// the deployed code.
78+
const resolved = await systemPrompt.resolve({
79+
componentReference: catalogPromptSection(),
80+
});
81+
chat.prompt.set(resolved);
82+
},
83+
5084
run: async ({ messages, tools, signal }) => {
5185
return streamText({
52-
// Spread chat.toStreamTextOptions() FIRST — it wires up
53-
// prepareStep (compaction, steering, background injection),
54-
// the system prompt set via chat.prompt(), and telemetry.
55-
...chat.toStreamTextOptions(),
86+
// Fallback model only — placed BEFORE the spread so the stored
87+
// prompt's model (including dashboard overrides) wins when set.
5688
model: anthropic("claude-opus-4-8"),
57-
system: SYSTEM_PROMPT,
89+
// Wires up prepareStep (compaction, steering, background injection),
90+
// plus the system prompt + model + config + telemetry from chat.prompt().
91+
...chat.toStreamTextOptions({ registry }),
5892
messages,
5993
tools,
6094
stopWhen: stepCountIs(15),
@@ -64,18 +98,127 @@ export const clickhouseAgent = chat.agent({
6498
});
6599
```
66100

67-
The system prompt tells the agent to explore the schema before querying, write ClickHouse SQL (not Postgres dialect), prefer aggregations, and present results as markdown tables.
101+
<Warning>
102+
On AI SDK v5/v6, `experimental_telemetry` comes from the stored prompt via
103+
`chat.toStreamTextOptions()` — without `chat.prompt.set()`, model calls don't appear as spans in
104+
the run trace.
105+
</Warning>
106+
107+
### Generative UI with one shared catalog
108+
109+
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:
110+
111+
```ts src/lib/catalog.ts
112+
import { defineCatalog } from "@json-render/core";
113+
import { schema } from "@json-render/react/schema";
114+
import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
115+
116+
export const catalog = defineCatalog(schema, {
117+
components: {
118+
// Layout & text from the stock shadcn catalog
119+
Card: shadcnComponentDefinitions.Card,
120+
Grid: shadcnComponentDefinitions.Grid,
121+
Table: shadcnComponentDefinitions.Table,
122+
// ...plus custom BarChart, LineChart, AreaChart, PieChart, Stat, PointMap
123+
},
124+
actions: {},
125+
});
126+
127+
// Generates a component reference (props as JSON schema, from the same zod
128+
// definitions) for the system prompt — the prompt can't drift from the code.
129+
export function catalogPromptSection(): string {
130+
/* ... */
131+
}
132+
133+
// Validates a spec against the catalog; errors are phrased for the model
134+
// to correct and retry.
135+
export function validateSpec(spec: VisualizationSpec) {
136+
/* ... */
137+
}
138+
```
139+
140+
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:
141+
142+
```ts src/trigger/clickhouse-agent.ts
143+
const renderVisualization = tool({
144+
description:
145+
"Render charts, tables and stat cards for the user, instead of describing data as text.",
146+
inputSchema: z.object({
147+
spec: z.object({
148+
root: z.string(),
149+
elements: z.record(
150+
z.string(),
151+
z.object({
152+
type: z.string(),
153+
props: z.record(z.string(), z.unknown()),
154+
children: z.array(z.string()).optional(),
155+
})
156+
),
157+
}),
158+
}),
159+
execute: async ({ spec }) => {
160+
const result = validateSpec(spec);
161+
if (!result.ok) {
162+
// The model reads these, fixes the spec, and calls the tool again
163+
return { ok: false, errors: result.errors };
164+
}
165+
return { ok: true, note: "Rendered to the user. Add at most a one-sentence takeaway." };
166+
},
167+
});
168+
```
169+
170+
### The Next.js chat UI
171+
172+
The frontend uses `useChat` with [`useTriggerChatTransport`](/ai-chat/frontend) — 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:
173+
174+
```tsx src/components/chat.tsx
175+
"use client";
176+
177+
import { useChat } from "@ai-sdk/react";
178+
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
179+
import type { clickhouseAgent } from "@/trigger/clickhouse-agent";
180+
import { mintChatAccessToken, startChatSession } from "@/app/actions";
181+
182+
export function Chat() {
183+
const transport = useTriggerChatTransport<typeof clickhouseAgent>({
184+
task: "clickhouse-agent",
185+
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
186+
startSession: ({ chatId, clientData }) => startChatSession({ chatId, clientData }),
187+
});
188+
189+
const { messages, sendMessage, stop, status } = useChat({ transport });
190+
// Render text parts as markdown; render tool-renderVisualization parts
191+
// with json-render's <Renderer spec={...} registry={registry} />
192+
}
193+
```
194+
195+
The registry maps every catalog component to its React implementation — the stock `@json-render/shadcn` components plus the custom charts and map:
196+
197+
```tsx src/lib/registry.tsx
198+
import { defineRegistry } from "@json-render/react";
199+
import { shadcnComponents } from "@json-render/shadcn";
200+
import { catalog } from "./catalog";
201+
202+
export const { registry } = defineRegistry(catalog, {
203+
components: {
204+
Card: shadcnComponents.Card,
205+
Table: shadcnComponents.Table,
206+
// ...
207+
BarChart: ({ props }) => <BarChartView {...props} />,
208+
PointMap: ({ props }) => <PointMapView {...props} />,
209+
},
210+
});
211+
```
68212

69213
### The query tool
70214

71215
`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:
72216

73-
```ts trigger/clickhouse-agent.ts
217+
```ts src/trigger/clickhouse-agent.ts
74218
const READ_ONLY_STATEMENTS = /^\s*(select|with|show|describe|desc|explain|exists)\b/i;
75219

76220
const runQuery = tool({
77-
description:
78-
"Run a read-only SQL query against ClickHouse and get the results as JSON rows.",
221+
description: "Run a read-only SQL query against ClickHouse and get the results as JSON rows.",
79222
inputSchema: z.object({
80223
query: z.string().describe("The ClickHouse SQL query to run"),
81224
}),
@@ -106,35 +249,44 @@ const runQuery = tool({
106249
});
107250
```
108251

109-
### Connecting to ClickHouse
252+
### Running it
110253

111-
The client reads a single `CLICKHOUSE_URL` environment variable — the HTTPS endpoint with credentials embedded — set in the Trigger.dev dashboard on the [Environment Variables page](/deploy-environment-variables):
254+
The example needs `CLICKHOUSE_URL` and `ANTHROPIC_API_KEY` set in the Trigger.dev dashboard on the [Environment Variables page](/deploy-environment-variables), and `TRIGGER_PROJECT_REF` plus `TRIGGER_SECRET_KEY` in the local `.env` for the Next.js server actions:
112255

113-
```bash
114-
CLICKHOUSE_URL=https://default:YOUR_PASSWORD@YOUR_SERVICE.clickhouse.cloud:8443
256+
```bash .env
257+
TRIGGER_PROJECT_REF=proj_xxxxxxxxxxxxxxxxxxxxxxxx
258+
TRIGGER_SECRET_KEY=tr_dev_xxxxxxxxxxxxxxxxxxxxxxxx
115259
```
116260

117-
```ts trigger/clickhouse-agent.ts
118-
import { createClient } from "@clickhouse/client";
261+
Run the agent and the app in two terminals, then open [http://localhost:3000](http://localhost:3000):
119262

120-
const clickhouse = createClient({ url: process.env.CLICKHOUSE_URL });
263+
```bash
264+
pnpm dev:trigger # the agent
265+
pnpm dev # the Next.js app
121266
```
122267

123-
### Chatting with the agent
124-
125-
Run `npx trigger.dev@latest dev`, then open the **AI agents** page in the dashboard and chat with `clickhouse-agent` in the playground. With a dataset like [NYC Taxi](https://clickhouse.com/docs/getting-started/example-datasets/nyc-taxi) loaded, asking "What were the top 5 busiest pickup days?" produces a `listTables` call, a `describeTable` call, a SQL aggregation, and a streamed markdown table of results.
268+
With a dataset like [NYC Taxi](https://clickhouse.com/docs/getting-started/example-datasets/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.
126269

127270
## Relevant code
128271

129-
- **Agent + tools**: [trigger/clickhouse-agent.ts](https://github.com/triggerdotdev/examples/blob/main/clickhouse-chat-agent/trigger/clickhouse-agent.ts): the `chat.agent()` definition, the three tools, the read-only guards, and the ClickHouse client
130-
- **Trigger config**: [trigger.config.ts](https://github.com/triggerdotdev/examples/blob/main/clickhouse-chat-agent/trigger.config.ts): project config pointing at the `trigger/` directory
272+
- **Agent + tools**: [src/trigger/clickhouse-agent.ts](https://github.com/triggerdotdev/examples/blob/main/clickhouse-chat-agent/src/trigger/clickhouse-agent.ts): the `chat.agent()` definition, the versioned prompt, the four tools, the read-only guards, and the ClickHouse client
273+
- **Shared catalog**: [src/lib/catalog.ts](https://github.com/triggerdotdev/examples/blob/main/clickhouse-chat-agent/src/lib/catalog.ts): component definitions, prompt-reference generation, and spec validation
274+
- **Component registry**: [src/lib/registry.tsx](https://github.com/triggerdotdev/examples/blob/main/clickhouse-chat-agent/src/lib/registry.tsx): maps catalog components to shadcn/Recharts/mapcn implementations
275+
- **Chat UI**: [src/components/chat.tsx](https://github.com/triggerdotdev/examples/blob/main/clickhouse-chat-agent/src/components/chat.tsx): `useChat` + `useTriggerChatTransport`, message parts, and visualization rendering
276+
- **Server actions**: [src/app/actions.ts](https://github.com/triggerdotdev/examples/blob/main/clickhouse-chat-agent/src/app/actions.ts): session creation and token minting
131277

132278
## Learn more
133279

134280
<CardGroup cols={2}>
135281
<Card title="AI chat overview" icon="message-bot" href="/ai-chat/overview">
136282
How chat agents, sessions, and the turn loop work.
137283
</Card>
284+
<Card title="Frontend" icon="browser" href="/ai-chat/frontend">
285+
The chat transport, session tokens, and reconnection.
286+
</Card>
287+
<Card title="AI Prompts" icon="file-lines" href="/ai/prompts">
288+
Versioned prompts with dashboard overrides and generation tracking.
289+
</Card>
138290
<Card title="Tools" icon="wrench" href="/ai-chat/tools">
139291
Declaring tools on your agent and how they persist across turns.
140292
</Card>

0 commit comments

Comments
 (0)