Elements

Elements · Messages

Confidence

Which claims came from a source, which were inferred, and which are guesses.

fig. 01

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 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.

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

components/assistant-ui/elements/thread.aui.tsx
"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.

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),
  }));

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 / fieldTypeDescription
s.message.contentreadonly 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.resultTResult | undefinedThe 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.