Skip to main content

smoothStream()

smoothStream() creates an experimental transform stream that buffers text and reasoning deltas before emitting them in consistent chunks. Use it to make streamed responses appear at a steadier pace when a model emits uneven deltas.

Non-text chunks pass through unchanged. Any buffered content is emitted before a tool, control, or completion chunk.

Usage example
Direct link to Usage example

Pipe an agent's fullStream through the transform:

src/stream-agent.ts
import { smoothStream } from '@mastra/core/stream'

const result = await agent.stream('Explain how rainbows form')

const stream = result.fullStream.pipeThrough(
smoothStream({
delayInMs: 20,
chunking: 'word',
}),
)

for await (const chunk of stream) {
if (chunk.type === 'text-delta') {
process.stdout.write(chunk.payload.text)
}
}

The transform changes only the piped stream. Promise properties and callbacks on the original MastraModelOutput, such as result.text and onChunk, keep the model's original chunk timing.

AI SDK routes
Direct link to AI SDK routes

Import smoothStream() from @mastra/ai-sdk to smooth an agent before handleChatStream() converts its output to AI SDK UI chunks:

app/api/chat/route.ts
import { handleChatStream, smoothStream } from '@mastra/ai-sdk'
import { createUIMessageStreamResponse } from 'ai'
import { mastra } from '@/src/mastra'

export async function POST(req: Request) {
const params = await req.json()
const stream = await handleChatStream({
mastra,
agentId: 'weatherAgent',
params,
experimentalTransform: smoothStream({
delayInMs: 20,
chunking: 'word',
}),
})

return createUIMessageStreamResponse({ stream })
}

The @mastra/ai-sdk export returns a reusable transform factory so route configuration creates a fresh TransformStream for every request. The @mastra/core/stream export returns a TransformStream for direct use with pipeThrough().

The reusable factory can also be passed to Agent.stream():

import { smoothStream } from '@mastra/ai-sdk'

const result = await agent.stream('Explain how rainbows form', {
experimentalTransform: smoothStream({ delayInMs: 20 }),
})

for await (const chunk of result.fullStream) {
// Consume the transformed Mastra chunks.
}

Parameters
Direct link to Parameters

options?:

SmoothStreamOptions
Controls the delay and chunk boundaries for the transformed stream.
SmoothStreamOptions

delayInMs?:

number | null
Delay in milliseconds after each emitted chunk. Set this value to null to disable the delay.

chunking?:

'word' | 'line' | RegExp | SmoothStreamChunkDetector | Intl.Segmenter
Controls how buffered text and reasoning are divided into chunks.

The chunking option accepts:

  • 'word': Emits complete words, including trailing whitespace.
  • 'line': Emits content through each newline.
  • RegExp: Emits content through the first match.
  • Intl.Segmenter: Uses locale-aware segmentation, which is useful for languages without spaces between words.
  • SmoothStreamChunkDetector: Calls a function with the current buffer. The function returns a non-empty prefix to emit, or null or undefined to wait for more content.

Custom chunking
Direct link to Custom chunking

Use a regular expression to define a chunk boundary:

const stream = result.fullStream.pipeThrough(
smoothStream({
chunking: /[^,]*,\s*/,
}),
)

Use Intl.Segmenter for locale-aware segmentation:

const stream = result.fullStream.pipeThrough(
smoothStream({
chunking: new Intl.Segmenter('ja', { granularity: 'word' }),
}),
)

Use a detector function when chunk boundaries depend on custom logic. The returned value must be a prefix of the buffer:

const stream = result.fullStream.pipeThrough(
smoothStream({
chunking: buffer => {
const boundary = buffer.indexOf('. ')
return boundary === -1 ? null : buffer.slice(0, boundary + 2)
},
}),
)

Returns
Direct link to Returns

TransformStream<ChunkType<OUTPUT>, ChunkType<OUTPUT>>

The transform emits smoothed text-delta and reasoning-delta chunks. It preserves chunk identifiers, run identifiers, sources, and metadata.

On this page