LangChain React Runtime

Use LangChain's useStream hook with a React chat UI through assistant-ui — a lighter LangGraph adapter that delegates streaming to @langchain/react.

@assistant-ui/react-langchain wraps useStream from @langchain/react and exposes it as an assistant-ui runtime. It targets the same backend as @assistant-ui/react-langgraph (LangGraph Cloud) but at a higher level, delegating stream plumbing to the upstream hook.

When to use it

Pick react-langchain over react-langgraph when:

  • Your app already depends on @langchain/react and uses useStream elsewhere.
  • You want to read custom state keys (todos, files, plans) reactively with useLangChainState<T>(key).
  • You prefer a thin wrapper that stays pinned to upstream behavior.

Pick react-langgraph instead when:

  • You are scaffolding via npx create-assistant-ui -t langgraph (the template uses it).
  • You need subgraph events, generative UI messages, message metadata, or end-to-end cancellation today.

Both adapters are first-class. react-langchain is newer and thinner; some features have not been ported yet (the comparison below has the full table).

Architecture

@assistant-ui/react-langchain is layered on ExternalStoreRuntime (see architecture). Graph state is the source of truth; the runtime renders messages from state.values.messages and submits user input back to the graph.

Shared adapters (attachments, speech, feedback) work the same way described in adapters. Cloud thread persistence is built in.

Requirements

  • A LangGraph Cloud API server (locally via LangGraph Studio or hosted via LangSmith).
  • The graph state must include a messages key with LangChain-alike messages, or pass a custom messagesKey.

Quickstart

Install dependencies

npm install @assistant-ui/react @assistant-ui/react-langchain @langchain/react @langchain/langgraph-sdk

Define the assistant component

@/components/MyAssistant.tsx
"use client";

import { Thread } from "@/components/assistant-ui/thread";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useStreamRuntime } from "@assistant-ui/react-langchain";

export function MyAssistant() {
  const runtime = useStreamRuntime({
    assistantId: process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!,
    apiUrl: process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"],
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Thread />
    </AssistantRuntimeProvider>
  );
}

Mount the component

@/app/page.tsx
import { MyAssistant } from "@/components/MyAssistant";

export default function Home() {
  return (
    <main className="h-dvh">
      <MyAssistant />
    </main>
  );
}

Set environment variables

.env.local
NEXT_PUBLIC_LANGGRAPH_API_URL=http://localhost:2024
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_id

Set up UI components

useStreamRuntime options

useStreamRuntime accepts every option upstream useStream does, plus three assistant-ui-specific fields:

OptionTypeDescription
cloudAssistantCloudOptional. Persists threads via assistant-cloud.
adapters{ attachments?, speech?, feedback? }Optional. Attachment, speech, and feedback adapters.
messagesKeystringThe state key that holds messages. Defaults to "messages".

Reading custom state keys

LangGraph agents often expose structured state beyond messages (plans, todos, scratch files, generative-UI artifacts). Read them directly with useLangChainState. It mirrors useStream().values[key] upstream and updates when the stream emits new state.

import { useLangChainState } from "@assistant-ui/react-langchain";

type Todo = { id: string; title: string; done: boolean };

function TodoList() {
  const todos = useLangChainState<Todo[]>("todos", []);
  return (
    <ul>
      {todos.map((t) => (
        <li key={t.id}>
          {t.done ? "✓" : "○"} {t.title}
        </li>
      ))}
    </ul>
  );
}

Signatures:

useLangChainState<T>(key: string): T | undefined;
useLangChainState<T>(key: string, defaultValue: T): T;

Useful with the deepagents middleware, whose write_todos step updates state.todos alongside the tool-call stream. Reading the state key directly avoids reconstructing the list from partial tool-call args.

Added in v0.0.2 — see issue #3862 for motivation.

Interrupts

LangGraph interrupts pause the graph and wait for client input. useLangChainInterruptState exposes the current interrupt; useLangChainSubmit resumes the graph with a raw state update.

import {
  useLangChainInterruptState,
  useLangChainSubmit,
} from "@assistant-ui/react-langchain";
import { Command } from "@langchain/langgraph-sdk";

function InterruptPrompt() {
  const interrupt = useLangChainInterruptState();
  const submit = useLangChainSubmit();
  if (!interrupt) return null;
  return (
    <div>
      <pre>{JSON.stringify(interrupt.value, null, 2)}</pre>
      <button
        onClick={() =>
          submit(null, { command: new Command({ resume: "approved" }) })
        }
      >
        Approve
      </button>
    </div>
  );
}

Message conversion

convertLangChainBaseMessage transforms a LangChain BaseMessage into an assistant-ui message. Use it when building a custom ExternalStoreAdapter that consumes LangChain messages outside useStreamRuntime.

import { convertLangChainBaseMessage } from "@assistant-ui/react-langchain";

Cloud persistence

Pass an AssistantCloud instance to persist threads across sessions. The runtime automatically wires thread list management and resumes state from the cloud.

// see "AssistantCloud" in /docs/runtimes/concepts/threads for cloud setup
const runtime = useStreamRuntime({
  cloud,
  assistantId: "agent",
  apiUrl: "http://localhost:2024",
});

Custom messagesKey

If your graph stores messages under a non-default key, pass messagesKey so the runtime submits tool results and human turns to the correct state slot:

const runtime = useStreamRuntime({
  assistantId: "agent",
  apiUrl: "http://localhost:2024",
  messagesKey: "chat_messages",
});

Comparison with react-langgraph

Both packages connect assistant-ui to LangGraph backends. They are independent adapters for different upstream libraries; one is not a successor to the other.

Aspectreact-langgraphreact-langchain
Wraps@langchain/langgraph-sdk (raw SDK)@langchain/react (useStream hook)
AgeSept 2024 onwardApril 2026 onward
Version0.13.x0.0.x
Lines of source~7,500~600
Built onuseExternalStoreRuntimeuseExternalStoreRuntime
create-assistant-ui template-t langgraphNo template yet

Feature coverage

Featurereact-langgraphreact-langchain
Stream messagesYes (useLangGraphRuntime)Yes (useStreamRuntime)
Interrupt stateYesYes
Send raw state update / resume commandYesYes (useLangChainSubmit)
Read arbitrary custom state keyNoYes (useLangChainState<T>(key))
Per-message metadata (messages-tuple)YesNot exposed
Generative UI messages (LangSmith)YesNot exposed
Subgraph / namespaced stream eventsYes (via eventHandlers)Not exposed
End-to-end cancellation primitiveYes (unstable_createLangGraphStream)Not exposed
Message accumulator utilityYes (LangGraphMessageAccumulator)Not exposed
Cloud thread persistenceYesYes

react-langchain is the newer, thinner wrapper. It delegates to upstream useStream rather than re-implementing the stream plumbing, which is why its footprint is smaller. Features absent today have not been ported, not deprecated.

Hook name mapping

react-langgraphreact-langchainNotes
useLangGraphRuntimeuseStreamRuntimeOptions extend upstream UseStreamOptions; no stream / create / load to write.
useLangGraphInterruptStateuseLangChainInterruptStateSame return shape.
useLangGraphSendCommanduseLangChainSubmitsubmit(values, { command }) replaces the dedicated hook.
useLangGraphSend(use runtime.thread.append)No direct equivalent; send turns through the runtime.
useLangGraphMessageMetadata(not available)Open an issue if you rely on this.
useLangGraphUIMessages(not available)Open an issue if you rely on this.
(none)useLangChainState<T>(key)New — reads any custom state key reactively.