Confidence
Which claims came from a source, which were inferred, and which are guesses.
Installation
npx shadcn@latest add "@assistant-ui/elements-confidence-marker"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 initThen 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.
npx shadcn@latest add "@assistant-ui/elements-confidence-marker"Props-driven: no runtime or provider required.
ConfidenceMarker underlines each claim in an answer by how sure it is: grounded, inferred, or uncertain, with the basis shown on hover or focus. With a runtime you produce the claim list from a structured tool result; standalone you already hold it.
Getting started
assistant-ui's message content has no built-in per-claim confidence concept: text and citations are separate content parts, and neither carries a confidence grade. The closest real building block is a tool call. Ask the model to return its answer as graded claims, and read the result back off the tool-call part.
Read the tool result off the message
"use client";
import { useState } from "react";
import { useAuiState } from "@assistant-ui/react";
import type { ToolCallMessagePart } from "@assistant-ui/react";
import {
ConfidenceMarker,
type ConfidenceClaim,
} from "@/components/assistant-ui/elements/confidence-marker";
type CiteClaims = ToolCallMessagePart<unknown, { claims: ConfidenceClaim[] }>;
function ClaimsFromMessage() {
const [hoveredId, setHoveredId] = useState("");
const claims = useAuiState((s) =>
s.message.role === "assistant"
? s.message.content.find(
(part): part is CiteClaims =>
part.type === "tool-call" && part.toolName === "cite_claims",
)?.result?.claims
: undefined,
);
if (!claims) return null;
return <ConfidenceMarker claims={claims} hoveredId={hoveredId} onHover={setHoveredId} />;
}cite_claims and the { claims: [...] } result shape are your own tool contract; part.result is the real, typed plumbing that carries whatever you defined back to the client.
Use a real citation for the grounded tier alone
When all you need is "this claim came from a fetched source," not a full three-tier grade, SourceMessagePart is a first-class runtime concept you can render directly instead of routing it through a custom tool. See A grounded-only version below.
Standalone, you already hold the claim list; the component only tracks which one is hovered or focused.
Hold the claims and the hover state
"use client";
import { useState } from "react";
import {
ConfidenceMarker,
type ConfidenceClaim,
} from "@/components/assistant-ui/elements/confidence-marker";
const CLAIMS: ConfidenceClaim[] = [
{ id: "1", text: "The composer owns its draft from 0.14 onward.", confidence: "grounded", basis: "migration-0.14.md" },
{ id: "2", text: "Most apps will not need the hydrate effect.", confidence: "inferred", basis: "from the changed API" },
];
export function Answer() {
const [hoveredId, setHoveredId] = useState("");
return <ConfidenceMarker claims={CLAIMS} hoveredId={hoveredId} onHover={setHoveredId} />;
}Basis is plain text
basis is a plain string; nothing parses it. Citing a document path, a search result title, or "not measured" are all equally valid values.
Anatomy
<div data-slot="confidence-marker">
<p>{/* each claim as an inline button, underlined by confidence tier */}</p>
<div>{/* fixed-height slot: the hovered/focused claim's basis pill, or nothing */}</div>
</div>Hover and focus both reveal the basis (onMouseEnter/onFocus share the same onHover call), so it is reachable from the keyboard, and aria-describedby points at the basis pill only while it belongs to the focused claim. The basis slot has a fixed height whether or not anything is shown, so revealing it never shifts the paragraph above. The tier is marked by underline style, not color alone: solid for grounded and inferred, dotted for uncertain, so the distinction survives without color.
Examples
A grounded-only version
Mapping real citations straight into the same shape skips the custom tool entirely when every claim you show is source-backed:
const claims: ConfidenceClaim[] = message.content
.filter((part): part is SourceMessagePart => part.type === "source")
.map((part) => ({
id: part.id,
text: part.title ?? part.url ?? part.id,
confidence: "grounded",
basis: part.sourceType === "url" ? part.url : (part.filename ?? part.title),
}));The same narrowing works on any list you already hold: keep only the claims whose confidence is "grounded" when a caller only wants the sourced ones.
<ConfidenceMarker claims={claims.filter((c) => c.confidence === "grounded")} hoveredId={hoveredId} onHover={setHoveredId} />Restyle the underline and basis pill
Both lanes take className on the root. The basis pill reads the shared floating and mono surfaces from surfaces.tsx.
<ConfidenceMarker className="gap-3" /* ... */ />API reference
Message state
| Selector / field | Type | Description |
|---|---|---|
s.message.content | readonly ThreadAssistantMessagePart[] | Search this for a tool-call part whose result you shape as a claims list; assistant-ui has no built-in confidence field. |
ToolCallMessagePart.result | TResult | undefined | The tool's return value once it has run, typed by the generic you give ToolCallMessagePart<TArgs, TResult>. |
SourceMessagePart | { type: "source"; sourceType: "url" | "document"; id: string; url?: string; title?: string; filename?: string } | A real citation part, useful when "grounded" is the only tier you need. |
ConfidenceMarker
| Prop | Type | Default | Description |
|---|---|---|---|
claims | readonly ConfidenceClaim[] | required | The full, ordered list to render. |
hoveredId | string | required | Id of the claim whose basis is shown; an empty string shows none. |
onHover | (id: string) => void | Called with the claim id on hover or focus, and with "" on leave or blur. | |
className | string | Merged onto the root. |
ConfidenceClaim
| Field | Type | Description |
|---|---|---|
id | string | |
text | string | |
confidence | "grounded" | "inferred" | "uncertain" | Drives the underline color and style. |
basis | string | Shown in the hover/focus pill. |
All other div props are forwarded to the root.