LangGraph's Generative UI feature allows AI agents on the server to dynamically render interactive React components on the client. It bridges the gap between backend AI logic and frontend user interfaces.
Architecture Overview
3 Key Pieces
- LangGraph Server (src/agents) - Backend AI agents
- UI Components (src/agent-uis) - Frontend React components
- Component Registry (langgraph.json + src/agent-uis/index.tsx) - The bridge connecting them
How It Works: The Complete Flow
1. Component Registration (The Bridge)
File: langgraph.json
{
"graphs": {
"agent": "./src/agent/supervisor/index.ts:graph"
},
"ui": {
"agent": "./src/agent-uis/index.tsx"
}
}File: src/agent-uis/index.tsx
const componentMap = {
"accommodations-list": AccommodationsList,
"restaurant-list": RestaurantsList,
"stock-price": StockPrice,
// ...
}This creates a mapping of component names to React components that the server can reference.
2. Server Side: Pushing UI Components
File: src/trip-planner/nodes/tools.ts:30-92
Here's what happens when a user asks “Show me places to stay in New York”:
export async function callTools(state, config) {
// 1. Create a typed UI instance with access to your component map
const ui = typedUI<typeof componentMap>(config);
// 2. Call LLM to determine which tool to use
const llm = new ChatOpenAI({ model: "gpt-4" }).bindTools(ACCOMMODATIONS_TOOLS);
const response = await llm.invoke(state.messages);
// 3. If LLM calls the list-accommodations tool...
if (listAccommodationsToolCall) {
// 4. Push the UI component to state
ui.push(
{
name: "accommodations-list", // References ComponentMap key
props: {
toolCallId: listAccommodationsToolCall.id,
tripDetails: state.tripDetails,
accommodations: getAccommodations() // mock data
}
},
{ message: response }
);
}
// 5. Return state update with UI items
return {
messages: [response],
ui: ui.items, // <- This goes to the client
};
}Key Insight: We are essentially returning components from your graph nodes, and they get added to state.
3. Client Side: Rendering UI Components
File: src/agent-uis/trip-planner/accommodations-list/index.tsx:230-354
This is a regular React component but with special powers:
export default function AccommodationsList({
toolCallId,
tripDetails,
accommodations
}) {
// 1. useStreamContext gives you access to the LangGraph threadReminder:
useStream&useStreamContexthooks
useStream: Parent component that owns the connection and stream stateuseStreamContext: Child components that need to read/interact with that stream without prop drillingThis is particularly useful for Generative UI where LangGraph generates custom React components dynamically—those generated components use
useStreamContextto access the parent thread's state without needing props passed down through multiple layers.In summary:
useStreamsets up the stream,useStreamContextaccesses it from deeply nested components.
const thread = useStreamContext();
const [selectedAccommodation, setSelectedAccommodation] = useState();
const [accommodationsBooked, setAccommodationsBooked] = useState(false);
// 2. Regular React UI with shadcn components
return (
<Carousel>
{accommodations.map(accommodation => (
<AccommodationCard
accommodation={accommodation}
onClick={() => setSelectedAccommodation(accommodation)}
/>
))}
</Carousel>
);
}4. Interactive Components: Client → Server Communication
File: src/agent-uis/trip-planner/accommodations-list/index.tsx
When a user clicks “Book” on an accommodation:
function handleBookAccommodation(accommodation) {
const orderDetails = {
accommodation,
tripDetails,
};
// Send new messages back to the LangGraph server
thread.submit(
{},
{
command: {
update: {
messages: [
{
type: "tool",
tool_call_id: toolCallId, // <- Links back to original tool call
name: "book-accommodation",
content: JSON.stringify(orderDetails),
},
{
type: "human",
content: `Booked ${accommodation.name}.`,
}
]
},
goto: "generalInput" // <- Resume graph at specific node
}
}
);
setAccommodationBooked(true);
}Project Structure Explained
Key Concepts
1. Typed UI Functions
const ui = typedUI<typeof componentMap>(config);- Gives you full TypeScript safety
- Type-checks component name & props
- Ensures server references only registered components
2. UI Push Method
ui.push({name: "component-name", props: {...}});- Adds component to state
- Gets rendered on client automatically
- Persists across page refreshes (stored in checkpoint)
3. useStreamContext Hook
const thread = useStreamContext();- Gives components access to:
thread.submit()- Send messages back to the serverthread.messages- Full conversation historythread.ui- All UI components in state
4. Tool Call Linking
props: { toolCallId: toolCallId.id }- Links UI component to specific tool call
- Allows sending tool responses back
- Creates bi-directional communication
Persistence: LangGraph uses a checkpoint system to save state to memory/database. UI components are part of the state, so they persist.
Custom Styling
Each component has an index.css for custom styles.
In short, this is standard React state management + useStreamContext for server communication.
Running the Project
- Install dependencies:
pnpm install - Configure environment variables: Create
.envfile with your OpenAI API key & GOOGLE_API_KEY - Start the LangGraph server:
pnpm agent(server runs on localhost:2024) - Use with agent chat UI:
- Visit: agentchat.vercel.app
- Connect to
http://localhost:2024 - Graph ID:
agent
What Makes This Special
- No custom protocols: Use standard React components
- Full TypeScript safety: Type-checked components & props
- Bidirectional communication: Components can talk back to agents
- State persistence: UI survives page refreshes
- Reusable components: Same UI library as the rest of your app
🙏 This article is based on the official LangGraph Generative UI Examples repository by the LangChain team. All code examples and patterns are derived from the trip-planner implementation in this repository.