Skip to content
Docs
Generative UI
render

render()

The render function is a powerful helper function to create a streamable UIs from an LLM response.

Note: this function only supports OpenAI SDK-compatible models/providers.

Streaming Text

By default, render will stream the text content of the LLM response wrapped with a React Fragment <> tag.

app/actions.tsx
import { OpenAI } from "openai";
import { render } from "ai/rsc";
 
const openai = new OpenAI();
 
async function submitUserMessage(content: string) {
  "use server";
 
  const aiState = getMutableAIState();
 
  // Update AI state with new message.
  aiState.update([
    ...aiState.get(),
    {
      role: "user",
      content,
    },
  ]);
 
  // render() returns a stream of UI components
  const ui = render({
    model: 'gpt-4-turbo',
    provider: openai,
    // You may want to construct messages from your AI state
    messages: [
      { role: 'system', content: 'You are a flight assistant' },
      { role: 'user', content: userInput }
    ],
  })
 
  return {
    id: Date.now(),
    // You can render ui on the client with something like `{message.display}`
    display: ui
  };
}

You can also customize the React component streamed for text responses by using the text key.

app/actions.tsx
async function submitUserMessage(content: string) {
  "use server";
  
  // ... same as above
 
  const ui = render({
 
    // ... same as above
 
    // `text` is called when an AI returns a text response (as opposed to a tool call)
    text: ({ content, done }) => {
      // text can be streamed from the LLM, but we only want to close the stream with .done() when its completed.
      // done() marks the state as available for the client to access
      if (done) {
        aiState.done([
          ...aiState.get(),
          {
            role: "assistant",
            content
          }
        ])
      }
 
      return <div>{content}</div>
    }
  })
 
  return {
    id: Date.now(),
    // You can render UI on the client with something like `{message.display}` and the
    // result yielded in`text` will be displayed on the client and streamed
    // in as it is returned from the model.
    display: ui
  };
}

Tools and Function Calls with React Server Components

The render function allows you to map OpenAI-compatible model/providers with Function Calls and Assistants Tools (opens in a new tab) to React Server Components (opens in a new tab) using the tools key. Note that both text and render can be specified at the same time, with text being the fallback for when no function is called by the model.

If you use other models, you can prompt engineer them into returning structured data and manually handle the streaming UI with createStreamableUI and createStreamableValue.

Tool/Function Schema definitions use Zod (opens in a new tab) schemas to specify the function parameters and return values. render will automatically validate the schemas and throw an error if the function call is invalid.

To install Zod, run:

pnpm install zod

Each tool specified also accepts a nested render function for returning React components. There are a few different signatures you can use:

  • () => ReactNode - a function that returns a React Node
  • async () => ReactNode - an async function that returns a React Node
  • function* () {} - an generator function that returns a React Node.
  • async function* () {} - an async generator function that returns a React Node.

If you use a generator signature, you can yield React Nodes and they will be sent as distinct updates to the client. This is very powerful for loading states and agentic, multi-step behaviors.

app/actions.tsx
import { OpenAI } from "openai";
import { render } from "ai/rsc";
import { z } from "zod";
 
const openai = new OpenAI();
 
async function submitUserMessage(content: string) {
  "use server";
 
  const aiState = getMutableAIState();
 
  // Update AI state with new message.
  aiState.update([
    ...aiState.get(),
    {
      role: "user",
      content,
    },
  ]);
 
  // render() returns a stream of UI components
  const ui = render({
    model: 'gpt-4-turbo',
    provider: openai,
    // You may want to construct messages from your AI state
    messages: [
      { role: 'system', content: 'You are a flight assistant' },
      { role: 'user', content: userInput }
    ],
    // `text` is called when an AI returns a text response (as opposed to a tool call)
    text: ({ content, done }) => {
      // text can be streamed from the LLM, but we only want to close the stream with .done() when its completed.
      // done() marks the state as available for the client to access
      if (done) {
        aiState.done([
          ...aiState.get(),
          {
            role: "assistant",
            content
          }
        ])
      }
 
      return <div>{content}</div>
    }
    tools: {
      get_flight_info: {
        description: 'Get the information for a flight',
        parameters: z.object({
          flightNumber: z.string().describe('the number of the flight')
        }).required(),
        // flightNumber is inferred from the parameters passed above
        render: async function* ({ flightNumber }) {
          yield <Spinner/>
          const flightInfo = await getFlightInfo(flightNumber)
 
          aiState.done([
            ...aiState.get(),
            {
              role: "function",
              name: "get_flight_info",
              // Content can be any string to provide context to the LLM in the rest of the conversation
              content: JSON.stringify(flightInfo),
            }
          ]);
 
          return <FlightCard flightInfo={flightInfo} />
        }
      }
    }
  })
 
  return {
    id: Date.now(),
    // You can render UI on the client with something like `{message.display}` and the
    // result yielded in `render` or `text` will be displayed on the client and streamed
    // in as it is returned from the model.
    display: ui
  };
}

© 2023 Vercel Inc.