# LangGraph Integration CopilotKit supports LangGraph in three configurations: Python with self-hosted FastAPI, Python with LangGraph Platform, and JavaScript/TypeScript. All use the AG-UI protocol. ## Python (Self-Hosted FastAPI) This is the `langgraph-fastapi` example pattern. You run the LangGraph agent as a standalone FastAPI server and connect via `LangGraphHttpAgent`. ### Prerequisites - Python 3.10+ - Node.js 18+ - OpenAI API key - `poetry` or `uv` for Python dependency management ### Python Dependencies ```toml # pyproject.toml [project] dependencies = [ "copilotkit==0.1.74", "langchain==1.0.1", "langchain-openai==1.0.1", "langgraph==1.0.1", "fastapi==0.115.12", "uvicorn>=0.38.0", "python-dotenv>=1.0.0", "ag-ui-langgraph==0.0.22", "pydantic>=2.0.0,<3.0.0", ] ``` ### Agent Definition (agent/src/agent.py) The agent extends `CopilotKitState` for shared state and uses the standard ReAct pattern: ```python from copilotkit import CopilotKitState from langchain.tools import tool from langchain_core.messages import SystemMessage from langchain_core.runnables import RunnableConfig from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import StateGraph from langgraph.prebuilt import ToolNode from langgraph.types import Command from typing_extensions import Literal from src.util import should_route_to_tool_node class AgentState(CopilotKitState): proverbs: list[str] @tool def get_weather(location: str): """Get the weather for a given location.""" return f"The weather for {location} is 70 degrees." tools = [get_weather] async def chat_node( state: AgentState, config: RunnableConfig ) -> Command[Literal["tool_node", "__end__"]]: model = ChatOpenAI(model="gpt-4o") # Bind both frontend (CopilotKit) actions and backend tools fe_tools = state.get("copilotkit", {}).get("actions", []) model_with_tools = model.bind_tools([*fe_tools, *tools]) system_message = SystemMessage( content=f"You are a helpful assistant. The current proverbs are {state.get('proverbs', [])}." ) response = await model_with_tools.ainvoke( [system_message, *state["messages"]], config, ) tool_calls = response.tool_calls if tool_calls and should_route_to_tool_node(tool_calls, fe_tools): return Command(goto="tool_node", update={"messages": response}) return Command(goto="__end__", update={"messages": response}) workflow = StateGraph(AgentState) workflow.add_node("chat_node", chat_node) workflow.add_node("tool_node", ToolNode(tools=tools)) workflow.add_edge("tool_node", "chat_node") workflow.set_entry_point("chat_node") graph = workflow.compile(checkpointer=MemorySaver()) ``` Key pattern: `CopilotKitState` provides the `copilotkit` field containing `actions` (frontend tools). You must bind both frontend actions and backend tools to the model, then route frontend tool calls back to CopilotKit (not the ToolNode). ### FastAPI Server (agent/main.py) ```python from fastapi import FastAPI from copilotkit import LangGraphAGUIAgent from ag_ui_langgraph import add_langgraph_fastapi_endpoint from src.agent import graph app = FastAPI() add_langgraph_fastapi_endpoint( app=app, agent=LangGraphAGUIAgent( name="sample_agent", description="An example agent.", graph=graph, ), path="/", ) ``` ### Next.js Route (src/app/api/copilotkit/[[...slug]]/route.ts) ```typescript import { CopilotRuntime, createCopilotHonoHandler, InMemoryAgentRunner, } from "@copilotkit/runtime/v2"; import { LangGraphHttpAgent } from "@copilotkit/runtime/langgraph"; import { handle } from "hono/vercel"; const runtime = new CopilotRuntime({ agents: { default: new LangGraphHttpAgent({ url: `${process.env.AGENT_URL || "http://localhost:8123"}/`, }), }, runner: new InMemoryAgentRunner(), }); const app = createCopilotHonoHandler({ runtime, basePath: "/api/copilotkit", }); export const GET = handle(app); export const POST = handle(app); export const PATCH = handle(app); export const DELETE = handle(app); ``` Use `LangGraphHttpAgent` (from `@copilotkit/runtime/langgraph`) for self-hosted agents -- the FastAPI server runs under `ag-ui-langgraph`, which speaks AG-UI directly. The default port is 8123 (note the trailing slash on the URL). --- ## Python (LangGraph Platform / Monorepo) This is the `langgraph-python` example pattern. Uses `LangGraphAgent` which connects to a LangGraph deployment (local or cloud). ### Next.js Route (src/app/api/copilotkit/[[...slug]]/route.ts) ```typescript import { CopilotRuntime, createCopilotHonoHandler, InMemoryAgentRunner, } from "@copilotkit/runtime/v2"; import { LangGraphAgent } from "@copilotkit/runtime/langgraph"; import { handle } from "hono/vercel"; const defaultAgent = new LangGraphAgent({ deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123", graphId: "sample_agent", langsmithApiKey: process.env.LANGSMITH_API_KEY || "", }); const runtime = new CopilotRuntime({ agents: { default: defaultAgent }, runner: new InMemoryAgentRunner(), }); const app = createCopilotHonoHandler({ runtime, basePath: "/api/copilotkit", }); export const GET = handle(app); export const POST = handle(app); export const PATCH = handle(app); export const DELETE = handle(app); ``` Key difference from self-hosted: `LangGraphAgent` uses `deploymentUrl` and `graphId` (and optionally `langsmithApiKey`) to target the LangGraph Platform / `langgraph-cli dev` surface, while `LangGraphHttpAgent` uses a plain `url` for a self-hosted AG-UI server. --- ## JavaScript / TypeScript This is the `langgraph-js` example pattern. The agent is a TypeScript LangGraph graph running in a separate Node.js process. ### Agent Definition (apps/agent/src/agent.ts) ```typescript import { z } from "zod"; import { tool } from "@langchain/core/tools"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { AIMessage, SystemMessage } from "@langchain/core/messages"; import { MemorySaver, START, StateGraph } from "@langchain/langgraph"; import { ChatOpenAI } from "@langchain/openai"; import { convertActionsToDynamicStructuredTools, CopilotKitStateAnnotation, } from "@copilotkit/sdk-js/langgraph"; import { Annotation } from "@langchain/langgraph"; const AgentStateAnnotation = Annotation.Root({ ...CopilotKitStateAnnotation.spec, proverbs: Annotation, }); export type AgentState = typeof AgentStateAnnotation.State; const getWeather = tool( (args) => `The weather for ${args.location} is 70 degrees.`, { name: "getWeather", description: "Get the weather for a given location.", schema: z.object({ location: z.string() }), }, ); const tools = [getWeather]; async function chat_node(state: AgentState, config) { const model = new ChatOpenAI({ temperature: 0, model: "gpt-4o" }); const modelWithTools = model.bindTools!([ ...convertActionsToDynamicStructuredTools(state.copilotkit?.actions ?? []), ...tools, ]); const systemMessage = new SystemMessage({ content: `You are a helpful assistant. The current proverbs are ${JSON.stringify(state.proverbs)}.`, }); const response = await modelWithTools.invoke( [systemMessage, ...state.messages], config, ); return { messages: response }; } function shouldContinue({ messages, copilotkit }: AgentState) { const lastMessage = messages[messages.length - 1] as AIMessage; if (lastMessage.tool_calls?.length) { const actions = copilotkit?.actions; const toolCallName = lastMessage.tool_calls![0].name; if (!actions || actions.every((action) => action.name !== toolCallName)) { return "tool_node"; } } return "__end__"; } const workflow = new StateGraph(AgentStateAnnotation) .addNode("chat_node", chat_node) .addNode("tool_node", new ToolNode(tools)) .addEdge(START, "chat_node") .addEdge("tool_node", "chat_node") .addConditionalEdges("chat_node", shouldContinue); export const graph = workflow.compile({ checkpointer: new MemorySaver() }); ``` Key JS-specific patterns: - Use `CopilotKitStateAnnotation` from `@copilotkit/sdk-js/langgraph` to include CopilotKit state - Use `convertActionsToDynamicStructuredTools()` to convert frontend actions to LangChain tools - Check `copilotkit.actions` to determine whether a tool call should route to `tool_node` (backend) or `__end__` (frontend) ### Serving the JS graph `LangGraphAgent` with `deploymentUrl`/`graphId` targets the LangGraph **server** surface, not the bare compiled `graph` export. Serve the graph with the LangGraph JS CLI (`@langchain/langgraph-cli`) so that surface exists. Add a `langgraph.json` next to the agent: ```json { "node_version": "20", "dependencies": ["."], "graphs": { "sample_agent": "./src/agent.ts:graph" }, "env": "../.env" } ``` Run it with `langgraphjs dev --port 8123` (the agent app's `dev` script). The `graphId` you pass to `LangGraphAgent` must match a key under `graphs` (here `"sample_agent"`), and `deploymentUrl` points at the CLI server (`http://localhost:8123`). ### Next.js Route The catch-all `src/app/api/copilotkit/[[...slug]]/route.ts` uses `LangGraphAgent` (from `@copilotkit/runtime/langgraph`) with `deploymentUrl` (the `langgraphjs dev` URL, e.g. `http://localhost:8123`) and `graphId` (`"sample_agent"`), mounted via `createCopilotHonoHandler`. ## Monorepo Structure (JS) The JS variant uses a Turborepo monorepo: ``` apps/ web/ # Next.js frontend agent/ # LangGraph agent, served via `langgraphjs dev` (langgraph.json) pnpm-workspace.yaml turbo.json ``` Run `pnpm dev` to start both apps via Turborepo (the agent app runs `langgraphjs dev --port 8123`).