This guide demonstrates how to use React Server Components and the AI SDK to stream structured data (like tool calls) from a server action to a client.
Workflow
- Define a Schema: Use
zod to define the structure of the tool's input. - Create a Server Action: Use the
"use server" directive and createStreamableValue from ai/rsc to manage the stream. - Bind Tools to LLM: Use
llm.bind to attach tool definitions (converted to JSON schema via zodToJsonSchema) to the model. - Construct an LCEL Chain: Pipe the prompt, the model with tools, and a
JsonOutputKeyToolsParser together. - Stream Results: Iterate over the chain's
.stream() output and update the streamable value using stream.update().
"use server";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { createStreamableValue } from "ai/rsc";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools";
const Weather = z
.object({
city: z.string().describe("City to search for weather"),
state: z.string().describe("State abbreviation to search for weather"),
})
.describe("Weather search parameters");
export async function executeTool(
input: string,
) {
"use server";
const stream = createStreamableValue();
(async () => {
const prompt = ChatPromptTemplate.fromMessages([
[
"system",
`You are a helpful assistant. Use the tools provided to best assist the user.`,
],
["human", "{input}"],
]);
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
});
const modelWithTools = llm.bind({
tools: [
{
type: "function" as const,
function: {
name: "get_weather",
description: Weather.description,
parameters: zodToJsonSchema(Weather),
},
},
],
});
const chain = prompt.pipe(modelWithTools).pipe(
new JsonOutputKeyToolsParser<z.infer<typeof Weather>>({
keyName: "get_weather",
zodSchema: Weather,
}),
);
const streamResult = await chain.stream({
input,
});
for await (const item of streamResult) {
stream.update(JSON.parse(JSON.stringify(item, null, 2)));
}
stream.done();
})();
return { streamData: stream.value };
}