Streaming Foundations
Purpose and Scope
Streaming is the AI SDK pattern for returning model output as it becomes available instead of waiting for a complete response. The foundations documentation frames this as a user-experience problem: long LLM generations can make a blocking interface feel stalled for several seconds, while a streaming interface can render partial output immediately. In practice, this page helps you choose between blocking and streaming flows, understand the stream shapes exposed by the SDK, and decide when to send plain text versus richer UI message events.
Sources: content/docs/02-foundations/05-streaming.mdx
The core distinction is not only performance. A blocking response is simpler because the server returns one completed value, but it hides progress and delays user feedback. A streaming response introduces a long-lived readable stream, so the server and client must agree on framing, headers, and message semantics. The SDK reduces that complexity by exposing high-level generation APIs such as streamText, stream transformers such as smoothStream, and response helpers that encode streams correctly for HTTP consumers.
Sources: content/docs/02-foundations/05-streaming.mdx, packages/ai/src/generate-text/smooth-stream.test.ts, packages/ai/src/text-stream/create-text-stream-response.test.ts, packages/ai/src/ui-message-stream/create-ui-message-stream-response.test.ts
Relevant Source Files
content/docs/02-foundations/05-streaming.mdxexplains why streaming improves perceived latency, contrasts blocking and streaming UIs, and introduces thestreamTextloop overtextStream.packages/ai/src/generate-text/smooth-stream.test.tsverifies the behavior of thesmoothStreamtransform, including invalid options, word chunking, chunk splitting, and delayed emission.packages/ai/src/text-stream/create-text-stream-response.test.tsverifiescreateTextStreamResponse, its plain text content type, custom headers, and interoperability withtoTextStream.packages/ai/src/ui-message-stream/create-ui-message-stream-response.test.tsverifiescreateUIMessageStreamResponse, Server-Sent Events framing, stream completion markers, error parts, and conversion fromtoUIMessageStream.
Core Streaming Primitives
The simplest foundation primitive is streamText. The documentation example imports streamText, passes a model and prompt, then iterates for await over textStream to receive each text part. That loop is the mental model for server-side scripts, route handlers, and adapters: the model call produces an asynchronous stream, and your application consumes or forwards each chunk. The same idea can power a console logger, a plain HTTP text response, or a framework-specific chat UI.
Sources: content/docs/02-foundations/05-streaming.mdx
A text stream is appropriate when the client only needs generated text chunks. The response test shows createTextStreamResponse returning a standard Response, preserving status, status text, and custom headers while setting Content-Type to text/plain; charset=utf-8. When paired with toTextStream, SDK stream parts such as text-delta become raw text chunks like Hello and , world!. This is intentionally minimal: the client appends decoded text and does not need to understand message IDs, tool events, or metadata.
Sources: packages/ai/src/text-stream/create-text-stream-response.test.ts
A UI message stream is appropriate when the frontend needs structured events rather than only text. The UI response test shows createUIMessageStreamResponse encoding events as text/event-stream, setting cache and buffering headers for streaming delivery, adding x-vercel-ai-ui-message-stream: v1, and ending with data: [DONE]. When toUIMessageStream receives TextStreamPart events, it can emit a start event with a generated message ID, followed by text-start, text-delta, and text-end events for the client to reconcile into a message.
Sources: packages/ai/src/ui-message-stream/create-ui-message-stream-response.test.ts
Stream Transformations
Streaming output is not always pleasant exactly as a provider emits it. Providers may produce very small chunks, partial words, or larger text spans that feel uneven in a UI. The smoothStream tests demonstrate a transform stage that sits between a model stream and the consumer. With word chunking, partial pieces such as Hello, , , and world! can be combined into user-friendly deltas. A larger chunk such as Hello, World! This is an example text. can be split into word-sized deltas with configured delays between emissions.
Sources: packages/ai/src/generate-text/smooth-stream.test.ts
The tests also show that stream transformations should validate configuration early. Passing an invalid chunking strategy or null chunking option throws immediately, which prevents an application from starting a response stream with ambiguous behavior. That matters because stream errors after headers have been sent are harder for HTTP clients to recover from. Treat transforms as part of the server-side pipeline: configure them before returning the response, then pipe the model stream through them only when the chosen chunking and delay behavior matches the user experience you want.
Sources: packages/ai/src/generate-text/smooth-stream.test.ts
Execution Flow
A typical text-only route starts with a model call, extracts the stream, optionally transforms it, and returns an HTTP response. The documentation demonstrates the consumption side with for await (const textPart of textStream), while the response tests demonstrate the forwarding side with createTextStreamResponse. For local scripts, iterating and logging may be enough. For web applications, returning a Response with an encoded stream allows the browser or SDK UI hook to process chunks incrementally without waiting for the final model message.
Sources: content/docs/02-foundations/05-streaming.mdx, packages/ai/src/text-stream/create-text-stream-response.test.ts
A chat-oriented route follows the same server-side shape but chooses a richer wire protocol. The test for createUIMessageStreamResponse shows that UI message events are serialized as Server-Sent Events data frames and include a completion marker. It also verifies that an error event can be sent as { type: 'error', errorText: 'Custom error message' }, allowing the client to handle a stream-level failure as part of the protocol. Use this form when the UI needs message lifecycle events, structured parts, transient data, or metadata-aware rendering.
Sources: packages/ai/src/ui-message-stream/create-ui-message-stream-response.test.ts
Compact API Reference
| Primitive | Use it when | Source-backed behavior |
|---|---|---|
streamText | You want generated text as it arrives | Returns a textStream that can be consumed with for await in the foundations example. |
smoothStream | Provider chunks should be reshaped for display | Validates chunking options, can combine partial words, split large text chunks, and insert delays. |
createTextStreamResponse | The HTTP client only needs plain text | Creates a Response with text/plain; charset=utf-8 and encoded text chunks. |
toTextStream | You have SDK text stream parts but need raw text | Converts text-delta parts into plain text chunks. |
createUIMessageStreamResponse | The client needs structured UI stream events | Creates an SSE response with no-cache, keep-alive, no-buffering, version header, event frames, and [DONE]. |
toUIMessageStream | You have SDK text stream parts but need UI messages | Converts start and text lifecycle parts into UI message stream events and can generate message IDs. |
Choosing the Right Stream Shape
Choose the smallest protocol that carries the information your client needs. If the frontend is only rendering a single assistant text span, a text stream is easier to inspect, proxy, and consume. If the frontend is a chatbot with message IDs, tool activity, loading states, custom data parts, or message-level metadata, use UI message streams. Official UI docs distinguish metadata, which describes a message as a whole, from data parts, which are streamed as message parts and can represent dynamic state such as loading indicators or interactive components.
Sources: packages/ai/src/text-stream/create-text-stream-response.test.ts, packages/ai/src/ui-message-stream/create-ui-message-stream-response.test.ts
The important implementation boundary is the stream contract, not the framework. The same generated model stream can be consumed in a loop, transformed for smoother display, encoded as plain text, or converted into UI message events. When building a new endpoint, start from the client requirement: raw text, structured UI events, or custom data. Then select the corresponding response helper and keep transformations close to the model stream so that downstream clients receive a stable, predictable protocol.
Sources: content/docs/02-foundations/05-streaming.mdx, packages/ai/src/generate-text/smooth-stream.test.ts, packages/ai/src/text-stream/create-text-stream-response.test.ts, packages/ai/src/ui-message-stream/create-ui-message-stream-response.test.ts
Testing Signals and Next Steps
The tests provide practical acceptance criteria for streaming code. A plain text stream response should preserve HTTP status fields, merge custom headers, set the text content type, and emit decoded text chunks in order. A UI message stream response should use SSE framing, include stream-friendly cache and buffering headers, emit protocol events in order, and terminate with [DONE]. A smoothing transform should not silently accept invalid configuration and should produce predictable deltas under word chunking.
Sources: packages/ai/src/generate-text/smooth-stream.test.ts, packages/ai/src/text-stream/create-text-stream-response.test.ts, packages/ai/src/ui-message-stream/create-ui-message-stream-response.test.ts
Next, read the text generation and UI pages that sit on top of these foundations. Use Generating Text and Streaming when you need model-call options, callbacks, and response object details. Use Stream Protocol, Transport, and Metadata when you are building a custom frontend or backend transport. Use Chatbot Tools, Persistence, and Resume when the stream must carry tool events, durable message IDs, or resumable chat state across requests.