How to implement Generative User Interfaces with LangGraph¶
Prerequisites
Generative user interfaces (Generative UI) allows agents to go beyond text and generate rich user interfaces. This enables creating more interactive and context-aware applications where the UI adapts based on the conversation flow and AI responses.
LangGraph Platform supports colocating your React components with your graph code. This allows you to focus on building specific UI components for your graph while easily plugging into existing chat interfaces such as Agent Chat and loading the code only when actually needed.
LangGraph.js only
Currently only LangGraph.js supports Generative UI. Support for Python is coming soon.
Tutorial¶
1. Define and configure UI components¶
First, create your first UI component. For each component you need to provide an unique identifier that will be used to reference the component in your graph code.
const WeatherComponent = (props: { city: string }) => {
return <div>Weather for {props.city}</div>;
};
export default {
weather: WeatherComponent,
};
Next, define your UI components in your langgraph.json
configuration:
{
"node_version": "20",
"graphs": {
"agent": "./src/agent/index.ts:graph"
},
"ui": {
"agent": "./src/agent/ui.tsx"
}
}
The ui
section points to the UI components that will be used by graphs. By default, we recommend using the same key as the graph name, but you can split out the components however you like, see Customise the namespace of UI components for more details.
LangGraph Platform will automatically bundle your UI components code and styles and serve them as external assets that can be loaded by the LoadExternalComponent
component. Some dependencies such as react
and react-dom
will be automatically excluded from the bundle.
CSS and Tailwind 4.x is also supported out of the box, so you can freely use Tailwind classes as well as shadcn/ui
in your UI components.
2. Send the UI components in your graph¶
Use the typedUi
utility to emit UI elements from your agent nodes:
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import { ChatOpenAI } from "@langchain/openai";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import type ComponentMap from "./ui.js";
import {
Annotation,
MessagesAnnotation,
StateGraph,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
export const graph = new StateGraph(AgentState)
.addNode("weather", async (state, config) => {
// Provide the type of the component map to ensure
// type safety of `ui.push()` calls as well as
// pushing the messages to the `ui` and sending a custom event as well.
const ui = typedUi<typeof ComponentMap>(config);
const weather = await new ChatOpenAI({ model: "gpt-4o-mini" })
.withStructuredOutput(z.object({ city: z.string() }))
.withConfig({ tags: ["langsmith:nostream"] })
.invoke(state.messages);
const response = {
id: uuidv4(),
type: "ai",
content: `Here's the weather for ${weather.city}`,
};
// Emit UI elements with associated AI message
ui.push({ name: "weather", props: weather }, { message: response });
return { messages: [response] };
})
.addEdge("__start__", "weather")
.compile();
3. Handle UI elements in your React application¶
On the client side, you can use useStream()
and LoadExternalComponent
to display the UI elements.
"use client";
import { useStream } from "@langchain/langgraph-sdk/react";
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
export default function Page() {
const { thread, values } = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
});
return (
<div>
{thread.messages.map((message) => (
<div key={message.id}>
{message.content}
{values.ui
?.filter((ui) => ui.metadata?.message_id === message.id)
.map((ui) => (
<LoadExternalComponent key={ui.id} stream={thread} message={ui} />
))}
</div>
))}
</div>
);
}
Behind the scenes, LoadExternalComponent
will fetch the JS and CSS for the UI components from LangGraph Platform and render them in a shadow DOM, thus ensuring style isolation from the rest of your application.
How-to guides¶
Show loading UI when components are loading¶
You can provide a fallback UI to be rendered when the components are loading.
Provide custom components on the client side¶
If you already have the components loaded in your client application, you can provide a map of such components to be rendered directly without fetching the UI code from LangGraph Platform.
const clientComponents = {
weather: WeatherComponent,
};
<LoadExternalComponent
stream={thread}
message={ui}
components={clientComponents}
/>;
Customise the namespace of UI components.¶
By default LoadExternalComponent
will use the assistantId
from useStream()
hook to fetch the code for UI components. You can customise this by providing a namespace
prop to the LoadExternalComponent
component.
Access and interact with the thread state from the UI component¶
You can access the thread state inside the UI component by using the useStreamContext
hook.
import { useStreamContext } from "@langchain/langgraph-sdk/react-ui";
const WeatherComponent = (props: { city: string }) => {
const { thread, submit } = useStreamContext();
return (
<>
<div>Weather for {props.city}</div>
<button
onClick={() => {
const newMessage = {
type: "human",
content: `What's the weather in ${props.city}?`,
};
submit({ messages: [newMessage] });
}}
>
Retry
</button>
</>
);
};
Pass additional context to the client components¶
You can pass additional context to the client components by providing a meta
prop to the LoadExternalComponent
component.
Then, you can access the meta
prop in the UI component by using the useStreamContext
hook.
import { useStreamContext } from "@langchain/langgraph-sdk/react-ui";
const WeatherComponent = (props: { city: string }) => {
const { meta } = useStreamContext<
{ city: string },
{ MetaType: { userId?: string } }
>();
return (
<div>
Weather for {props.city} (user: {meta?.userId})
</div>
);
};
Streaming UI updates before the node execution is finished¶
You can stream UI updates before the node execution is finished by using the onCustomEvent
callback of the useStream()
hook.
import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui";
const { thread, submit } = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
onCustomEvent: (event, options) => {
options.mutate((prev) => {
const ui = uiMessageReducer(prev.ui ?? [], event);
return { ...prev, ui };
});
},
});
Remove UI messages from state¶
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling ui.delete
with the ID of the UI message.
// pushed message
const message = ui.push({ name: "weather", props: { city: "London" } });
// remove said message
ui.delete(message.id);
// return new state to persist changes
return { ui: ui.items };