content/docs/06-advanced/07-rendering-ui-with-language-models.mdx
Language models generate text, so at first it may seem like you would only need to render text in your application.
const text = generateText({
model: __MODEL__,
system: 'You are a friendly assistant',
prompt: 'What is the weather in SF?',
tools: {
getWeather: {
description: 'Get the weather for a location',
inputSchema: z.object({
city: z.string().describe('The city to get the weather for'),
unit: z
.enum(['C', 'F'])
.describe('The unit to display the temperature in'),
}),
execute: async ({ city, unit }) => {
const weather = getWeather({ city, unit });
return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`;
},
},
},
});
Above, the language model is passed a tool called getWeather that returns the weather information as text. However, instead of returning text, if you return a JSON object that represents the weather information, you can use it to render a React component instead.
const text = generateText({
model: __MODEL__,
system: 'You are a friendly assistant',
prompt: 'What is the weather in SF?',
tools: {
getWeather: {
description: 'Get the weather for a location',
inputSchema: z.object({
city: z.string().describe('The city to get the weather for'),
unit: z
.enum(['C', 'F'])
.describe('The unit to display the temperature in'),
}),
execute: async ({ city, unit }) => {
const weather = getWeather({ city, unit });
const { temperature, unit, description, forecast } = weather;
return {
temperature,
unit,
description,
forecast,
};
},
},
},
});
Now you can use the object returned by the getWeather function to conditionally render a React component <WeatherCard/> that displays the weather information by passing the object as props.
return (
<div>
{messages.map(message => {
// Check assistant message parts for tool results
if (message.role === 'assistant') {
return message.parts.map(part => {
if (
part.type === 'tool-weather' &&
part.state === 'output-available'
) {
const { temperature, unit, description, forecast } = part.output;
return (
<WeatherCard
weather={{
temperature,
unit,
description,
forecast,
}}
/>
);
}
});
}
})}
</div>
);
Here's a little preview of what that might look like.
<div className="not-prose flex flex-col"> <CardPlayer type="weather" title="Weather" description="An example of an assistant that renders the weather information in a streamed component." /> </div>Rendering interfaces as part of language model generations elevates the user experience of your application, allowing people to interact with language models beyond text.
They also make it easier for you to interpret sequential tool calls that take place in multiple steps and help identify and debug where the model reasoned incorrectly.
To recap, an application has to go through the following steps to render user interfaces as part of model generations:
Most applications have multiple tools that are called by the language model, and each tool can return a different user interface.
For example, a tool that searches for courses can return a list of courses, while a tool that searches for people can return a list of people. As this list grows, the complexity of your application will grow as well and it can become increasingly difficult to manage these user interfaces.
{
message.parts.map(part => {
if (part.state !== 'output-available') return null;
switch (part.type) {
case 'tool-api-search-course':
return <Courses courses={part.output} />;
case 'tool-api-search-profile':
return <People people={part.output} />;
case 'tool-api-meetings':
return <Meetings meetings={part.output} />;
case 'tool-api-search-building':
return <Buildings buildings={part.output} />;
case 'tool-api-events':
return <Events events={part.output} />;
case 'tool-api-meals':
return <Meals meals={part.output} />;
case 'text':
return <div>{part.text}</div>;
default:
return null;
}
});
}
The AI SDK RSC (@ai-sdk/rsc) takes advantage of RSCs to solve the problem of managing all your React components on the client side, allowing you to render React components on the server and stream them to the client.
Rather than conditionally rendering user interfaces on the client based on the data returned by the language model, you can directly stream them from the server during a model generation.
import { createStreamableUI } from '@ai-sdk/rsc'
const uiStream = createStreamableUI();
const text = generateText({
model: __MODEL__,
system: 'you are a friendly assistant'
prompt: 'what is the weather in SF?'
tools: {
getWeather: {
description: 'Get the weather for a location',
inputSchema: z.object({
city: z.string().describe('The city to get the weather for'),
unit: z
.enum(['C', 'F'])
.describe('The unit to display the temperature in')
}),
execute: async ({ city, unit }) => {
const weather = getWeather({ city, unit })
const { temperature, unit, description, forecast } = weather
uiStream.done(
<WeatherCard
weather={{
temperature: 47,
unit: 'F',
description: 'sunny'
forecast,
}}
/>
)
}
}
}
})
return {
display: uiStream.value
}
The createStreamableUI function belongs to the @ai-sdk/rsc module and creates a stream that can send React components to the client.
On the server, you render the <WeatherCard/> component with the props passed to it, and then stream it to the client. On the client side, you only need to render the UI that is streamed from the server.
return (
<div>
{messages.map(message => (
<div>{message.display}</div>
))}
</div>
);
Now the steps involved are simplified:
Note: You can also render text on the server and stream it to the client using React Server Components. This way, all operations from language model generation to UI rendering can be done on the server, while the client only needs to render the UI that is streamed from the server.
Check out this example for a full illustration of how to stream component updates with React Server Components in Next.js App Router.