An LLM can emit a chart in a sentence. The problem isn't generation — it's trust: hand-written chart JSX breaks on first paint, and a plausible-looking chart for the wrong data misleads exactly the reader who can't tell. The generative-UI trust layer is what an AI framework wraps around chart generation. It turns a model's proposal — a component name and props — into a result that is either guaranteed renderable or accompanied by precise reasons and ranked alternatives to retry with. Never a broken chart.
Why this matters
"Generation is cheap; trust is scarce." Every AI-charting story today bets on the model writing correct code — and the model's training data is a monoculture of the same few libraries' defaults, which mass-produces charts that render but don't communicate. Semiotic inverts the bet: the model emits a validatedChartConfig, and a deterministic loop — validate → diagnose → repair → prove — does the trust work the weights can't. Structural validation catches the missing prop. Anti-pattern diagnostics catch the misleading design. A fit check catches the wrong chart for the data. Render evidence proves the scene isn't empty. The agent never ships a chart it hasn't earned.
Watch the loop accept and reject
Pick a proposal a model might emit. Each runs throughprepareChart live; only the ones that pass paint.
ok · safe to renderBarChart
A well-formed proposal: it validates, carries no error diagnostics, and fits the data. The loop returns ok — and only then do we paint.
The result object the agent reads back:
JSON
{ "ok": true, "component": "BarChart", "validation": { "valid": true, "errors": [] }, "diagnostics": [], "repair": { "status": "ok" }, "reasons": [] }
The loop, in code
TS
import { prepareChart } from "semiotic/ai" // Server/SSR only — inject to also prove the scene paints + read evidence: // import { renderChartWithEvidence } from "semiotic/server" const result = prepareChart( { component: "BarChart", props: { data, categoryAccessor: "region", valueAccessor: "revenue" } }, { data, // enables the fit check + ranked alternatives // render: renderChartWithEvidence, // optional: prove non-empty, read mark count/ARIA } ) if (result.ok) { stream(result.jsx) // guaranteed-renderable JSX / result.config } else { retry(result.reasons, result.repair) // precise reasons + ranked alternatives — never paint }
As an agent tool — no vendor SDK required
chartGenerationTool() returns a framework-agnostic JSON-Schema tool definition that mirrors the MCP renderChart contract. The component enum is drawn from the registry, so a model can only propose a real chart:
JSON
{ "name": "render_semiotic_chart", "description": "Render a Semiotic chart from a component name and props. The result is validated, diagnosed for anti-patterns, and (when data is supplied) checked for fit against the data, so it is guaranteed renderable or returns reasons and ranked alternatives to retry with.", "inputSchema": { "type": "object", "additionalProperties": false, "required": [ "component" ], "properties": { "component": { "type": "string", "enum": [ "BarChart", "LineChart", "Scatterplot", "AreaChart", "PieChart" ], "description": "The chart component to render." }, "props": { "type": "object", "description": "Props for the chart (accessors, data, encodings). See the component schema.", "additionalProperties": true } } } }
Shape it for any framework — the library ships no SDK dependency, only the schema and pure transforms:
TS
import { chartGenerationTool, toAnthropicTool, toOpenAITool, createChartToolHandler, } from "semiotic/ai" const def = chartGenerationTool() const handler = createChartToolHandler((input) => ({ data, render })) // Anthropic Messages API const anthropicTools = [toAnthropicTool(def)] // { name, description, input_schema } // OpenAI / function calling const openaiTools = [toOpenAITool(def)] // { type: "function", function: {...} } // Vercel AI SDK — the same JSON Schema, via its jsonSchema() helper: // tool({ description: def.description, parameters: jsonSchema(def.inputSchema), // execute: (input) => handler(input) }) // LangChain — a DynamicStructuredTool over the same schema + handler.
Where this goes
The trust loop is the same wherever a model produces a view: a conversational analytics agent that streams a cross-filtered dashboard, a notebook copilot that turns a dataframe into a chart, an IDE assistant scaffolding a component. In each case the durable value isn't the generation — it's the deterministic gate between the model's confidence and the user's screen. There's also a published Agent Skill packaging this workflow plus the behavior contracts, so any Claude-family agent gets it without bespoke prompting.
Related