observability
Adds tracing, telemetry, and observability to an assistant-ui backend. Use when wiring an AI SDK route handler (streamText/generateText, toUIMessageStreamResponse) to a tracing backend: Langfuse via OpenTelemetry (LangfuseSpanProcessor and NodeSDK in instrumentation.ts, experimental_telemetry isEnabled, propagateAttributes with traceName/userId/sessionId, langfuseSpanProcessor.forceFlush on serverless), LangSmith via wrapAISDK(ai) from langsmith/experimental/vercel (createLangSmithProviderOptions, awaitPendingTraceBatches), or Helicone via createOpenAI baseURL https://oai.helicone.ai/v1 with the Helicone-Auth header. Also covers rendering collected spans with @assistant-ui/react-o11y headless primitives (SpanResource, SpanPrimitive Root/Indent/CollapseToggle/StatusIndicator/TypeBadge/Name/Children, SpanByIndexProvider, SpanData/SpanState) mounted via useAui/AuiProvider from @assistant-ui/store. Use for missing or empty traces, edge vs nodejs runtime telemetry, serverless flush issues, or trace waterfalls.
NPX Install
npx skill4agent add assistant-ui/skills observabilityTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →assistant-ui Observability
@assistant-ui/react-o11yContents
- References
- Where it plugs in
- Provider routing
- AI SDK telemetry (shared)
- Helicone (proxy, no OTel)
- Visualizing spans with react-o11y
- Common Gotchas
- Related Skills
References
- ./references/langfuse.md -- Langfuse tracing
- ./references/langsmith.md -- LangSmith tracing
- ./references/helicone.md -- Helicone proxy
- ./references/react-o11y.md -- @assistant-ui/react-o11y client primitives
Where it plugs in
streamTextgenerateTextuseChatRuntimeThreadreact-o11yThread (frontend) ──> /api/chat (streamText) ──> tracing backend
└─> react-o11y (optional UI)Provider routing
Langfuse → OTel span processor + experimental_telemetry, propagateAttributes
LangSmith → wrapAISDK(ai) wrapper, no OTel setup
Helicone → proxy baseURL on the provider, no telemetry flag
react-o11y → client primitives to render spans you collectedAI SDK telemetry (shared)
experimental_telemetryimport { openai } from "@ai-sdk/openai";
import { streamText, convertToModelMessages } from "ai";
import type { UIMessage } from "ai";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai("gpt-5.4-nano"),
messages: await convertToModelMessages(messages),
experimental_telemetry: { isEnabled: true },
});
return result.toUIMessageStreamResponse();
}instrumentation.tsuserIdsessionIdimport { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
export const langfuseSpanProcessor = new LangfuseSpanProcessor();
export async function register() {
if (process.env.NEXT_RUNTIME !== "nodejs") return;
const sdk = new NodeSDK({ spanProcessors: [langfuseSpanProcessor] });
sdk.start();
}import { propagateAttributes } from "@langfuse/tracing";
const result = await propagateAttributes(
{ traceName: "chat-completion", userId, sessionId },
async () =>
streamText({
model: openai("gpt-5.4-nano"),
messages: await convertToModelMessages(messages),
experimental_telemetry: { isEnabled: true },
}),
);aiconvertToModelMessagesaiimport * as ai from "ai";
import { wrapAISDK } from "langsmith/experimental/vercel";
import { openai } from "@ai-sdk/openai";
const { streamText } = wrapAISDK(ai);
const result = streamText({
model: openai("gpt-5.4-nano"),
messages: await ai.convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();Helicone (proxy, no OTel)
baseURLimport { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({
baseURL: "https://oai.helicone.ai/v1",
headers: { "Helicone-Auth": `Bearer ${process.env.HELICONE_API_KEY}` },
});openaistreamTextVisualizing spans with react-o11y
@assistant-ui/react-o11ySpanData[]SpanResourceuseAuiSpanPrimitivenpm install @assistant-ui/react-o11yimport {
SpanResource,
SpanPrimitive,
type SpanData,
} from "@assistant-ui/react-o11y";
import { AuiProvider, useAui } from "@assistant-ui/store";
function SpanRow() {
return (
<SpanPrimitive.Root>
<SpanPrimitive.Indent />
<SpanPrimitive.CollapseToggle />
<SpanPrimitive.StatusIndicator />
<SpanPrimitive.TypeBadge />
<SpanPrimitive.Name />
</SpanPrimitive.Root>
);
}
export function TraceView({ spans }: { spans: SpanData[] }) {
const aui = useAui({ resource: SpanResource({ spans }) });
return (
<AuiProvider value={aui}>
<SpanPrimitive.Children components={{ Span: SpanRow }} />
</AuiProvider>
);
}SpanPrimitive.ChildrenSpanByIndexProviderRootdata-span-statusdata-span-typedata-span-depthdata-collapsedSpanStateCommon Gotchas
- The function exits before OTel flushes its buffer. Langfuse: before responding. LangSmith:
await langfuseSpanProcessor.forceFlush().await new Client().awaitPendingTraceBatches()
- must be set on each
experimental_telemetry: { isEnabled: true }/streamTextcall.generateText - The span processor only registers when ; OTel does not run on the edge runtime.
process.env.NEXT_RUNTIME === "nodejs"
- Use the destructured methods from , not the originals from
wrapAISDK(ai).aimust be set.LANGSMITH_TRACING=true
- Confirm requests go to , not
oai.helicone.ai, and carry bothapi.openai.comandHelicone-Authheaders.Authorization
- Primitives must render inside ; the resource mounts through
AuiProvider.useAui({ resource: SpanResource({ spans }) })
Related Skills
- - The route handler and stream response telemetry attaches to
/streaming - - Backend wiring (
/setup,ai-sdk) where the route livescustom-backend - - Persistence; pair
/cloud/userId/sessionIdwith trace attributesthreadId