Stream Text Multi-Step
You may want to have different steps in your stream where each step has different settings, e.g. models, tools, or system prompts.
With createDataStreamResponse
and sendFinish
/ sendStart
options when merging
into the data stream, 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.
Server
app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';import { createDataStreamResponse, streamText, tool } from 'ai';import { z } from 'zod';
export async function POST(req: Request) { const { messages } = await req.json();
return createDataStreamResponse({ execute: async dataStream => { // step 1 example: forced tool call const result1 = streamText({ model: openai('gpt-4o-mini', { structuredOutputs: true }), system: 'Extract the user goal from the conversation.', messages, toolChoice: 'required', // force the model to call a tool tools: { extractGoal: tool({ parameters: z.object({ goal: z.string() }), execute: async ({ goal }) => goal, // no-op extract tool }), }, });
// forward the initial result to the client without the finish event: result1.mergeIntoDataStream(dataStream, { experimental_sendFinish: false, // omit the finish event });
// 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: [...messages, ...(await result1.response).messages], });
// forward the 2nd result to the client (incl. the finish event): result2.mergeIntoDataStream(dataStream, { experimental_sendStart: false, // omit the start event }); }, });}
Client
app/page.tsx
'use client';
import { useChat } from '@ai-sdk/react';
export default function Chat() { const { messages, input, handleInputChange, handleSubmit } = 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-invocation': { return ( <pre key={index}> {JSON.stringify(part.toolInvocation, null, 2)} </pre> ); } } })} </div> ))} <form onSubmit={handleSubmit}> <input value={input} onChange={handleInputChange} /> </form> </div> );}