Introduction
LangGraph is a powerful library from the LangChain ecosystem designed for building stateful, multi-actor applications powered by large language models (LLMs). It excels at orchestrating complex workflows where multiple agents collaborate, maintain context across interactions, and handle interruptions or human-in-the-loop scenarios.
When paired with the Anthropic Claude SDK, LangGraph leverages Claude's superior reasoning, tool-calling capabilities, and safety features—particularly Claude 3.5 Sonnet—to create robust AI agents. This tutorial walks you through integrating the Claude SDK via LangChain's Anthropic wrapper, constructing a multi-agent research workflow, implementing tool calling and persistent memory, and deploying to Vercel as a serverless API.
By the end, you'll have a production-ready system for tasks like automated research reports, where a supervisor routes queries to specialized researcher and writer agents.
Prerequisites
Before starting, ensure you have:
- Node.js 18+ installed
- An Anthropic API key (get one from console.anthropic.com)
- A Vercel account for deployment
- Basic familiarity with TypeScript, async/await, and npm
Project Setup
Create a new directory and initialize the project:
mkdir claude-langgraph-app
cd claude-langgraph-app
npm init -y
npm install @langchain/anthropic @langchain/core langgraph zod
npm install -D typescript @types/node tsx
Note: We're using @langchain/anthropic, which wraps the official @anthropic-ai/sdk for seamless LangGraph integration, handling messages, tools, and streaming natively.
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Set your API key in a .env file:
ANTHROPIC_API_KEY=your-api-key-here
Defining Tools
Agents need tools for real-world actions. We'll define two: a web search simulator (using a mock for demo) and a file writer.
Create tools.ts:
import { z } from 'zod';
import { Tool } from '@langchain/core/tools';
// Mock web search tool
export class WebSearchTool extends Tool {
name = 'web_search';
description = 'Search the web for current information.';
protected _call(input: string) {
// In production, integrate with Tavily, SerpAPI, etc.
return `Mock results for "${input}": Claude 3.5 Sonnet excels in agentic workflows. LangGraph.js v0.1+ supports checkpoints.`;
}
schema = z.object({
query: z.string().describe('Search query'),
});
}
export const tools = [new WebSearchTool()];
Building Single Agents
Define agent nodes: a researcher using tools and a writer for summarization.
Create agents.ts:
import { ChatAnthropic } from '@langchain/anthropic';
import { createReactAgent } from '@langchain/core/agents';
import { pull } from 'langgraph';
import { tools } from './tools';
import { STATE } from './graph';
const llm = new ChatAnthropic({
model: 'claude-3-5-sonnet-20240620',
temperature: 0,
apiKey: process.env.ANTHROPIC_API_KEY,
}).bindTools(tools);
export const researcher = pull('researcher', createReactAgent({
llm,
tools,
}));
export const writer = pull('writer', createReactAgent({
llm: new ChatAnthropic({
model: 'claude-3-5-sonnet-20240620',
temperature: 0.2,
}),
tools: [],
}));
Here, createReactAgent uses ReAct reasoning (Reason + Act), ideal for Claude's tool-use strengths.
Orchestrating Multi-Agent Graph
The core is a StateGraph with a supervisor routing tasks.
First, define the state schema in graph.ts:
import { Annotation, BaseMessage } from '@langchain/core/messages';
import { z } from 'zod';
export const AgentState = z.object({
messages: z.array(Annotation<BaseMessage>()),
next: z.string().optional(),
});
export type STATE = z.infer<typeof AgentState>;
Now, the full graph in graph.ts:
import { StateGraph, END } from '@langchain/langgraph';
import { Annotation, HumanMessage } from '@langchain/core/messages';
import { researcher, writer } from './agents';
import { STATE } from './state';
import { ToolMessage } from '@langchain/core/messages';
// Supervisor node
function supervisor(state: STATE) {
const llm = /* same Claude config as above */;
const prompt = `You are a supervisor. Route to: researcher, writer, or END. Current: ${state.messages.slice(-1)[0].content}`;
// Implement routing logic with Claude
const response = llm.invoke([{ role: 'user', content: prompt }]);
return { next: 'researcher' }; // Simplified; parse response
}
const workflow = new StateGraph(STATE)
.addNode('supervisor', supervisor)
.addNode('researcher', researcher)
.addNode('writer', writer)
.addEdge('__start__', 'supervisor')
.addConditionalEdges('supervisor', (s) => s.next ?? END, {
researcher: 'researcher',
writer: 'writer',
})
.addEdge('researcher', 'supervisor')
.addEdge('writer', END);
export const graph = workflow.compile();
This creates cycles: supervisor → researcher → supervisor → writer → END.
Memory Management
LangGraph supports checkpoints for state persistence. Use in-memory for dev:
const memoryGraph = workflow.compile({ checkpointer: new InMemorySaver() });
// Invoke with thread ID for persistence
const config = { configurable: { thread_id: 'abc123' } };
const result = await memoryGraph.invoke({ messages: [new HumanMessage('Research LangGraph with Claude')] }, config);
For production, swap to PostgresSaver or RedisSaver (via @langchain/langgraph-checkpoint-redis).
Claude's context window (200K tokens) shines here, retaining long histories without truncation issues common in other models.
Handling Tool Calling
Claude's native tool use is invoked via bindTools(). In ReAct agents, it automatically decides when to call tools, parses outputs, and continues reasoning.
Extend with custom tools:
// Add to tools.ts
class CalculatorTool extends Tool {
name = 'calculator';
description = 'Perform math calculations.';
schema = z.object({ expression: z.string() });
async _call({ expression }: { expression: string }) {
return eval(expression).toString(); // Safe in prod with safe-eval
}
}
Claude 3.5 Sonnet reliably handles parallel tool calls and error recovery.
Running the App Locally
Create index.ts:
import { graph } from './graph';
import dotenv from 'dotenv';
dotenv.config();
const input = { messages: [{ role: 'user', content: 'Generate a report on Claude SDK best practices.' }] };
const result = await graph.invoke(input);
console.log(result);
Run with npx tsx index.ts.
Deployment to Vercel
LangGraph apps deploy easily as serverless functions.
- Add
vercel.json:
{
"functions": {
api/graph.ts": { "runtime": "nodejs18.x" }
}
}
- Create
api/graph.ts:
import { graph } from '../../graph';
export default async function handler(req: Request) {
const { input, threadId } = await req.json();
const config = { configurable: { thread_id: threadId } };
const result = await graph.invoke(input, config);
return Response.json(result);
}
vercel deploy --prod
Vercel handles cold starts; use Upstash Redis for shared memory across invocations.
Best Practices
- Model Selection: Use Claude 3.5 Sonnet for agents; Haiku for simple routing.
- Prompt Engineering: Prefix with "You are a helpful agent." Claude follows XML-like tool formats precisely.
- Error Handling: Wrap invokes in try-catch; Claude's safety reduces hallucinations.
- Cost Optimization: Stream responses with
graph.stream(). - Scaling: Shard threads by user ID; monitor via Anthropic console.
Common Pitfalls and Solutions
| Issue | Solution |
|---|---|
| Tool parsing fails | Ensure Zod schemas match tool descriptions |
| State loss on deploy | Always use checkpointer |
| High latency | Parallel edges with addParallelEdges |
| Context overflow | Summarize history with a compressor node |
Conclusion
Integrating the Claude SDK with LangGraph unlocks sophisticated multi-agent systems tailored for production. This setup handles research-to-report workflows scalably, with Claude's edge in reliability. Experiment with your use cases—add Slack integrations or n8n triggers next!
Source code: [GitHub repo link placeholder]
(Word count: ~1450)
Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.