Laurent Knauss Software Engineer

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

  1. LangGraph Server (src/agents) - Backend AI agents
  2. UI Components (src/agent-uis) - Frontend React components
  3. 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 thread

Reminder: useStream & useStreamContext hooks

  • useStream: Parent component that owns the connection and stream state
  • useStreamContext: Child components that need to read/interact with that stream without prop drilling

This is particularly useful for Generative UI where LangGraph generates custom React components dynamically—those generated components use useStreamContext to access the parent thread's state without needing props passed down through multiple layers.

In summary: useStream sets up the stream, useStreamContext accesses 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 server
    • thread.messages - Full conversation history
    • thread.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

  1. Install dependencies: pnpm install
  2. Configure environment variables: Create .env file with your OpenAI API key & GOOGLE_API_KEY
  3. Start the LangGraph server: pnpm agent (server runs on localhost:2024)
  4. Use with agent chat UI:

What Makes This Special

  1. No custom protocols: Use standard React components
  2. Full TypeScript safety: Type-checked components & props
  3. Bidirectional communication: Components can talk back to agents
  4. State persistence: UI survives page refreshes
  5. 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.

    LangGraph Generative UI | Laurent