Back to Copilotkit

Tool Rendering

showcase/shell-docs/src/content/docs/generative-ui/tool-rendering.mdx

1.57.03.5 KB
Original Source

What is this?

Tools are how an LLM invokes predefined, typically-deterministic functions. Tool rendering lets you decide how each of those tool calls appears in the chat. Instead of showing raw JSON, you register a React component that draws a branded card for the call (arguments, live status, and the eventual result). This is the Generative UI variant CopilotKit calls tool rendering.

<InlineDemo demo="tool-rendering" />

When should I use this?

Render tool calls when you want to:

  • Show users exactly what tools the agent is invoking and with what arguments
  • Display live progress indicators while a tool executes
  • Render rich, polished results once a tool completes
  • Give tool-heavy agents a transparent, on-brand chat experience

Default tool rendering (zero-config)

The simplest entry point: call useDefaultRenderTool() with no arguments. CopilotKit registers its built-in DefaultToolCallRenderer as the * wildcard: every tool call renders as a tidy status card (tool name, live Running → Done pill, collapsible arguments/result) without you writing any UI.

Without this hook the runtime has no * renderer and tool calls are invisible; the user only sees the assistant's final text summary.

<Snippet cell="tool-rendering-default-catchall" region="default-catchall-zero-config" title="frontend/src/app/page.tsx — useDefaultRenderTool()" />

Here's what the built-in status card looks like for each tool call:

<InlineDemo demo="tool-rendering-default-catchall" />

Custom catch-all

Once you want on-brand chrome, pass a render function to useDefaultRenderTool. It's a convenience wrapper around useRenderTool({ name: "*", ... }): one wildcard renderer handles every tool call, named or not:

<Snippet cell="tool-rendering-custom-catchall" region="use-default-render-tool-wildcard" title="frontend/src/app/page.tsx — custom wildcard renderer" />

Here's the branded catch-all in action, where every tool call gets the same on-brand card:

<InlineDemo demo="tool-rendering-custom-catchall" />

Per-tool renderers

The most expressive path is one renderer per tool name. The primary tool-rendering cell wires two: get_weather draws a branded WeatherCard, search_flights draws a FlightListCard. Each renderer receives the tool's parsed arguments, a live status, and (once the agent returns) the result:

<Snippet region="render-weather-tool" title="frontend/src/app/page.tsx — weather renderer" />

The flight renderer follows the same pattern with a different component and schema:

<Snippet region="render-flight-tool" title="frontend/src/app/page.tsx — flight renderer" /> <Callout type="info"> The `name` you pass to `useRenderTool` must match the tool name the agent exposes; that's how the runtime routes the call to your component. </Callout>

Per-tool renderers compose with a catch-all: named renderers claim the "interesting" tools and a wildcard handles everything else. In the primary cell, the same CustomCatchallRenderer from above catches get_stock_price and roll_dice:

<Snippet region="catchall-renderer" title="frontend/src/app/page.tsx — per-tool + catch-all" />

The backend tool definition

The frontend renderer only sees what the agent sends down. Here's the matching Python definition for get_weather, a standard LangChain tool, no CopilotKit-specific plumbing required:

<Snippet region="weather-tool-backend" title="backend/agent.py — weather tool" /> <FeatureIntegrations feature="tool-rendering" /> <IntegrationGrid path="generative-ui/tool-rendering" />