Elements

Elements · Knowledge

Retrieval chunks

The passages a retrieval answer stands on, scored, before the answer itself arrives.

how does draft restore work
Retrieving
fig. 01 · plays once, replay from the corner

Installation

npx shadcn@latest add "@assistant-ui/elements-retrieval-chunks"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

Retrieval chunks show a query, a status line, and the passages a retrieval call found, each with its source, a locator, a relevance score, and a score bar. With a runtime the query and passages come from a tool call the model made; standalone you drive query, chunks, and how many of them are visible.

Getting started

A retrieval tool renders through that tool's own toolkit entry: the model's query arrives as args, the retrieved passages as result, and the call's lifecycle as status.

Define the retrieval toolkit

app/toolkit.tsx
"use generative";

import { defineToolkit, externalTool } from "@assistant-ui/react";
import { RetrievalChunks } from "@/components/assistant-ui/elements/retrieval-chunks";

export default defineToolkit({
  search_docs: {
    execute: externalTool(),
    render: ({ args, result, status }) => (
      <RetrievalChunks
        query={args.query ?? ""}
        chunks={result?.chunks ?? []}
        visibleCount={result?.chunks.length ?? 0}
        searching={status.type === "running"}
      />
    ),
  },
});

execute: externalTool() marks a tool a backend route or vector store runs, not the browser: the compiler drops it from the client bundle and keeps only render.

Register the toolkit

app/MyRuntimeProvider.tsx
"use client";

import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import toolkit from "./toolkit";

export function App() {
  const runtime = useChatRuntime();
  const config = AuiConfig({ tools: Tools({ toolkit }) });

  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      <Thread />
    </AssistantRuntimeProvider>
  );
}

Every search_docs tool call in the thread now renders as this element, wherever MessagePrimitive.Parts places it in the assistant's reply.

Anatomy

<div data-slot="retrieval-chunks">
  <span>{/* query, in a rounded field with a database icon */}</span>
  <div>{/* "Retrieving" (shimmer) while searching, else "N passages above threshold" */}</div>
  <div>
    {/* the first `visibleCount` chunks: source, locator, score, snippet, named score meter */}
  </div>
</div>

visibleCount clamps to 0…chunks.length, so a value out of range never throws: negative or NaN shows nothing, one past the end shows everything. The status line, unlike web search's, does read chunks.length once retrieval finishes. A chunk's score bar fills to score as a percentage of 1, and its track is a named meter exposing that 0…100 value with value text reading the same score the row prints. The score text turns emerald at 0.8 and above; anything below reads in the muted foreground color. text is clamped to two lines and does not expand on its own.

Examples

Filtering by score before rendering

Nothing in the element enforces a relevance floor: pass a shorter chunks array to drop passages below your own threshold, rather than trying to hide them with visibleCount.

<RetrievalChunks
  query={query}
  chunks={chunks.filter((c) => c.score >= 0.5)}
  visibleCount={chunks.length}
  searching={searching}
/>

Restyle the chunks

Both lanes take className on the root. The query pill uses the shared field surface, the chunk cards use paper, and the locator and score use mono, all from surfaces.tsx.

<RetrievalChunks className="max-w-none" /* ... */ />

API reference

Tool render props

PropTypeDescription
args{ query?: string }Model-supplied arguments. Partial while streaming.
result{ chunks: RetrievalChunk[] } | undefinedSet once the tool call resolves.
status.type"running" | "complete" | "incomplete" | "requires-action"Lifecycle of the call. "running" is the only state that should show the retrieving shimmer.

RetrievalChunk is { id: string; source: string; locator: string; score: number; text: string }. See Tool UI for the full tool-call part shape.