File: stream-text-multistep.md | Updated: 11/15/2025
Menu
Google Gemini Image Generation
Get started with Claude 3.7 Sonnet
Get started with OpenAI o3-mini
Generate Text with Chat Prompt
Generate Image with Chat Prompt
streamText Multi-Step Cookbook
Markdown Chatbot with Memoization
Generate Object with File Prompt through Form Submission
Model Context Protocol (MCP) Tools
Share useChat State Across Components
Human-in-the-Loop Agent with Next.js
Render Visual Interface in Chat
Generate Text with Chat Prompt
Generate Text with Image Prompt
Generate Object with a Reasoning Model
Stream Object with Image Prompt
Record Token Usage After Streaming Object
Record Final Object after Streaming Object
Model Context Protocol (MCP) Tools
Retrieval Augmented Generation
Copy markdown
==================================================================================================================
You may want to have different steps in your stream where each step has different settings, e.g. models, tools, or system prompts.
With createUIMessageStream and sendFinish / sendStart options when merging into the UIMessageStream, you can control when the finish and start events are sent to the client, allowing you to have different steps in a single assistant UI message.
app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';import { convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse, streamText, tool,} from 'ai';import { z } from 'zod';
export async function POST(req: Request) { const { messages } = await req.json();
const stream = createUIMessageStream({ execute: async ({ writer }) => { // step 1 example: forced tool call const result1 = streamText({ model: openai('gpt-4o-mini'), system: 'Extract the user goal from the conversation.', messages, toolChoice: 'required', // force the model to call a tool tools: { extractGoal: tool({ inputSchema: z.object({ goal: z.string() }), execute: async ({ goal }) => goal, // no-op extract tool }), }, });
// forward the initial result to the client without the finish event: writer.merge(result1.toUIMessageStream({ sendFinish: false }));
// note: you can use any programming construct here, e.g. if-else, loops, etc. // workflow programming is normal programming with this approach.
// example: continue stream with forced tool call from previous step const result2 = streamText({ // different system prompt, different model, no tools: model: openai('gpt-4o'), system: 'You are a helpful assistant with a different system prompt. Repeat the extract user goal in your answer.', // continue the workflow stream with the messages from the previous step: messages: [ ...convertToModelMessages(messages), ...(await result1.response).messages, ], });
// forward the 2nd result to the client (incl. the finish event): writer.merge(result2.toUIMessageStream({ sendStart: false })); }, });
return createUIMessageStreamResponse({ stream });}
app/page.tsx
'use client';
import { useChat } from '@ai-sdk/react';import { useState } from 'react';
export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat();
return ( <div> {messages?.map(message => ( <div key={message.id}> <strong>{`${message.role}: `}</strong> {message.parts.map((part, index) => { switch (part.type) { case 'text': return <span key={index}>{part.text}</span>; case 'tool-extractGoal': { return <pre key={index}>{JSON.stringify(part, null, 2)}</pre>; } } })} </div> ))} <form onSubmit={e => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > <input value={input} onChange={e => setInput(e.currentTarget.value)} /> </form> </div> );}
On this page
Deploy and Scale AI Apps with Vercel.
Vercel delivers the infrastructure and developer experience you need to ship reliable AI-powered applications at scale.
Trusted by industry leaders: