Components as Tools

Let your agent render rich React components directly in the chat by calling them as tools.


"""PydanticAI agent backing the Tool-Based Generative UI demo.Mirrors showcase/integrations/langgraph-python/src/agents/gen_ui_tool_based.py.The frontend registers `render_bar_chart` and `render_pie_chart` tools via`useComponent`. CopilotKit's runtime injects those tool definitions into theagent request at runtime, so the agent does not need to declare them locally —PydanticAI's AG-UI bridge surfaces frontend-registered tools to the model oneach run, and the model decides when to call them."""from __future__ import annotationsfrom textwrap import dedentfrom pydantic_ai import Agentfrom pydantic_ai.models.openai import OpenAIResponsesModelSYSTEM_PROMPT = dedent(    """    You are a data visualization assistant.    When the user asks for a chart, call `render_bar_chart` or    `render_pie_chart` with a concise title, short description, and a    `data` array of `{label, value}` items. Pick bar for comparisons over    a small set of categories; pick pie for composition / share-of-whole.    Keep chat responses brief — let the chart do the talking.    """).strip()agent = Agent(    model=OpenAIResponsesModel("gpt-4.1-mini"),    system_prompt=SYSTEM_PROMPT,)

What is this?#

Tool-based Generative UI is the simplest form of Generative UI: you register a React component with useComponent, and CopilotKit exposes it to the agent as a tool. When the agent calls the tool, CopilotKit renders your component inline in the chat, passing the tool's arguments straight through as typed props.

Unlike tool rendering, which wraps a real backend tool in a custom UI, tool-based GenUI is the component. There is no handler, no user interaction, no server-side execution. The agent decides when to show it, populates the data, and CopilotKit paints it.

When should I use this?#

Use useComponent when you want to:

  • Display rich UI (cards, charts, tables, dashboards) inline in the chat
  • Show structured data the agent has derived from its reasoning
  • Render previews, status indicators, or visual summaries
  • Let the agent present information beyond plain text

For components that need user interaction, see Human-in-the-loop. For operational transparency around a real backend tool, see Tool rendering.

How it works in code#

Nothing to wire on the agent

PydanticAI's AG-UI bridge surfaces frontend-registered tools to the model on every run, so the agent declares no tools of its own. A component registered with useComponent reaches the model through the AG-UI request payload and the model calls it by name.

src/agents/chart_agent.py
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIResponsesModel

agent = Agent(
    model=OpenAIResponsesModel("gpt-4.1-mini"),
    system_prompt=SYSTEM_PROMPT,
)

Tell the model when to call it

This is the part that is easy to miss. The tool arrives on every run, but a model with no instruction about it will answer in prose and never call it. Name the tool in the system prompt and say what it is for.

src/agents/chart_agent.py
SYSTEM_PROMPT = """
You are a data visualization assistant.

When the user asks for a chart, call `render_bar_chart` with a concise
title and a `data` array of `{label, value}` items.

Keep chat responses brief — let the chart do the talking.
"""

useComponent takes a name, a Zod schema for its props, and the component to render. The runtime registers it as a frontend tool so the agent can discover it, and Zod validates the LLM's arguments before they reach your component.

page.tsx
  useComponent({    name: "render_bar_chart",    description: "Display a bar chart with labeled numeric values.",    parameters: barChartPropsSchema,    render: BarChart,  });

The component itself is ordinary React: it reads only its props and can stream in as the agent fills the payload. The example above uses Recharts for the bar chart; it doesn't know anything about CopilotKit.

The name you pass to useComponent is what the agent sees as the tool name. Make it a verb like render_bar_chart or show_weather so the LLM reliably picks it when the user asks for that visualization.