logoassistant-ui
Chat History

Chat History for LangGraph Cloud

Overview

assistant-cloud provides thread management and persistent chat history for applications built with LangGraph Cloud. This guide shows you how to integrate cloud persistence into your LangGraph application.

Prerequisites

You need an assistant-cloud account to follow this guide. Sign up here to get started.

Setup Guide

Create a Cloud Project

Create a new project in the assistant-cloud dashboard and from the settings page, copy:

  • Frontend API URL: https://proj-[ID].assistant-api.com
  • API Key: For server-side operations

Configure Environment Variables

Add the following environment variables to your project:

.env.local
# Frontend API URL from your cloud project settings
NEXT_PUBLIC_ASSISTANT_BASE_URL=https://proj-[YOUR-ID].assistant-api.com

# API key for server-side operations
ASSISTANT_API_KEY=your-api-key-here

Install Dependencies

Install the required packages:

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

Create the Runtime Provider

Create a runtime provider that integrates LangGraph with assistant-cloud. Choose between anonymous mode for demos/prototypes or authenticated mode for production:

app/chat/runtime-provider.tsx
"use client";

import {
  AssistantCloud,
  AssistantRuntimeProvider,
  useCloudThreadListRuntime,
  useThreadListItemRuntime,
} from "@assistant-ui/react";
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
import { createThread, getThreadState, sendMessage } from "@/lib/chatApi";
import { LangChainMessage } from "@assistant-ui/react-langgraph";
import { useMemo } from "react";

const useMyLangGraphRuntime = () => {
  const threadListItemRuntime = useThreadListItemRuntime();
  
  const runtime = useLangGraphRuntime({
    stream: async function* (messages) {
      const { externalId } = await threadListItemRuntime.initialize();
      if (!externalId) throw new Error("Thread not found");

      return sendMessage({
        threadId: externalId,
        messages,
      });
    },

    onSwitchToThread: async (externalId) => {
      const state = await getThreadState(externalId);
      return {
        messages:
          (state.values as { messages?: LangChainMessage[] }).messages ?? [],
      };
    },
  });

  return runtime;
};

export function MyRuntimeProvider({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const cloud = useMemo(
    () =>
      new AssistantCloud({
        baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
        anonymous: true, // Creates browser session-based user ID
      }),
    [],
  );

  const runtime = useCloudThreadListRuntime({
    cloud,
    runtimeHook: useMyLangGraphRuntime,
    create: async () => {
      const { thread_id } = await createThread();
      return { externalId: thread_id };
    },
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {children}
    </AssistantRuntimeProvider>
  );
}
app/chat/runtime-provider.tsx
"use client";

import {
  AssistantCloud,
  AssistantRuntimeProvider,
  useCloudThreadListRuntime,
  useThreadListItemRuntime,
} from "@assistant-ui/react";
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
import { createThread, getThreadState, sendMessage } from "@/lib/chatApi";
import { LangChainMessage } from "@assistant-ui/react-langgraph";
import { useAuth } from "@clerk/nextjs";
import { useMemo } from "react";

const useMyLangGraphRuntime = () => {
  const threadListItemRuntime = useThreadListItemRuntime();
  
  const runtime = useLangGraphRuntime({
    stream: async function* (messages) {
      const { externalId } = await threadListItemRuntime.initialize();
      if (!externalId) throw new Error("Thread not found");

      return sendMessage({
        threadId: externalId,
        messages,
      });
    },

    onSwitchToThread: async (externalId) => {
      const state = await getThreadState(externalId);
      return {
        messages:
          (state.values as { messages?: LangChainMessage[] }).messages ?? []
      };
    },
  });

  return runtime;
};

export function MyRuntimeProvider({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const { getToken } = useAuth();

  const cloud = useMemo(
    () =>
      new AssistantCloud({
        baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
        authToken: async () => getToken({ template: "assistant-ui" }),
      }),
    [getToken],
  );

  const runtime = useCloudThreadListRuntime({
    cloud,
    runtimeHook: useMyLangGraphRuntime,
    create: async () => {
      const { thread_id } = await createThread();
      return { externalId: thread_id };
    },
  });

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

For Clerk authentication, configure the "assistant-ui" token template in your Clerk dashboard.

The useMyLangGraphRuntime hook is extracted into a separate function because it will be mounted multiple times, once per active thread.

Add Thread UI Components

Install the thread list component:

npx assistant-ui add thread-list

Then add it to your application layout:

app/chat/page.tsx
import { Thread } from "@/components/assistant-ui/thread";
import { ThreadList } from "@/components/assistant-ui/thread-list";

export default function ChatPage() {
  return (
    <div className="grid h-dvh grid-cols-[250px_1fr] gap-x-2">
      <ThreadList />
      <Thread />
    </div>
  );
}

Authentication

The examples above show two authentication modes:

  • Anonymous: Suitable for demos and prototypes. Creates a browser session-based user ID.
  • Authenticated: For production use with user accounts. The authenticated example uses Clerk, but you can integrate any auth provider.

For other authentication providers or custom implementations, see the Cloud Authorization guide.

Next Steps