LangGraph Agent Development Guide
This guide covers building AI agents with LangChain/LangGraph, focusing on agent abstraction.
Core Concepts
State Management
使用 StateSchema 统一定义状态(替代旧的 createState + Annotation 双对象模式):
import { StateSchema, ReducedValue, MessagesValue } from '@langchain/langgraph';
import { z } from 'zod';
// 基本状态:简单字段直接用 Zod schema,无需额外 Annotation
export const MySchema = new StateSchema({
messages: MessagesValue, // 预构建的 messages ReducedValue(含标准 reducer)
my_field: z.string().default('value'),
counter: z.number().default(0),
data: z.object({ nested: z.boolean() }).default(() => ({ nested: true })),
});
// 自定义 reducer 字段(并发更新时合并而非覆盖)
export const ComplexSchema = new StateSchema({
messages: MessagesValue,
task_store: new ReducedValue(
z.record(z.string(), z.any()).default(() => ({})),
{
reducer: (a: Record<string, any>, b: Record<string, any>) => ({ ...a, ...b }),
},
),
my_field: z.string().default('value'),
});
export type MyStateType = typeof MySchema.State;
规则:
- 普通字段(最后写入胜出)→ 直接用 Zod schema
- 需要合并/累积的字段 → 用
new ReducedValue(schema, { reducer }) messages字段 → 直接用预构建的MessagesValue
Agent Creation
Basic Agent
import { createAgent } from 'langchain';
import { tool } from '@langchain/core/tools';
const model = await initChatModel('gpt-4');
const agent = createAgent({
name: 'MyAgent',
model,
systemPrompt: 'You are a helpful assistant',
tools: [myTool1, myTool2],
stateSchema: MySchema,
});
Model Initialization
import { ChatOpenAI } from '@langgraph-js/pro';
import { ChatAnthropic } from '@langchain/anthropic';
interface InitModelOptions {
streamUsage?: boolean;
enableThinking?: boolean;
}
export async function initChatModel(model: string, options: InitModelOptions = {}) {
const { streamUsage = true, enableThinking = true } = options;
if (model.includes('claude')) {
return new ChatAnthropic({
model,
streamUsage,
streaming: true,
thinking: enableThinking
? {
budget_tokens: 1024,
type: 'enabled',
}
: undefined,
});
} else {
return new ChatOpenAI({
model,
streamUsage,
modelKwargs: enableThinking
? {
thinking: { type: 'enabled' },
}
: undefined,
});
}
}
Tool Development
Basic Tool
import { tool } from 'langchain';
import { z } from 'zod';
export const myTool = tool(
async ({ param1, param2 }) => {
// Tool implementation
return 'result';
},
{
name: 'my_tool',
description: 'Tool description',
schema: z.object({
param1: z.string(),
param2: z.number().optional(),
}),
},
);
Tool with Error Handling
export const fetchDataTool = tool(
async ({ url }) => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
return { error: error.message };
}
},
{
name: 'fetch_data',
description: 'Fetch data from URL',
schema: z.object({
url: z.string().url(),
}),
},
);
Middleware
For comprehensive middleware patterns including request/response interception, prompt enhancement, Anthropic prompt caching, and human-in-the-loop, see:
Never use LangChain middleware directly, use standard-agent middleware instead!
Agent Invocation
Basic Invocation
const result = await agent.invoke({
messages: [new HumanMessage('Hello, how are you?')],
});
Streaming
When using @langgraph-js/pure-graph with registerGraph(), the framework automatically exposes streaming endpoints
via the HTTP API (/api/langgraph). No additional streaming code is required—the Hono adapter handles SSE (Server-Sent
Events) for real-time responses.
With Config
const result = await agent.invoke(
{ messages: [new HumanMessage('Hello')] },
{
configurable: {
runtime_context: {
userId: '123',
},
},
},
);
With Recursion Limit
const result = await agent.invoke({ messages: [new HumanMessage('Hello')] }, { recursionLimit: 100 });
Complete Server Setup
Full LangGraph Server with Hono
import { registerGraph } from '@langgraph-js/pure-graph';
import LGApp from '@langgraph-js/pure-graph/dist/adapter/hono';
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { serve } from 'bun';
// 1. Create your graph
import { graph } from './graphBuilder.js';
// 2. Register the graph
registerGraph('code', graph);
// 3. Create Hono app
const app = new Hono();
// Add middleware
app.use(logger());
// 4. Mount LangGraph API routes
app.route('/api/langgraph', LGApp);
// 5. Start server
const port = 8123;
console.log(`🚀 Server running on http://127.0.0.1:${port}`);
serve({
fetch: app.fetch,
port,
});
Graph Builder with StateGraph
import { Runtime } from 'langchain';
import { MySchema, MyStateType } from './state.js';
import { START, StateGraph } from '@langchain/langgraph';
import { createAgent } from 'langchain';
// Create agent
const model = await initChatModel('claude-3-5-sonnet');
const agent = createAgent({
name: 'MyAgent',
model,
systemPrompt: 'You are a helpful assistant',
tools: [myTool1, myTool2],
stateSchema: MySchema,
});
// Build graph
export function createMyGraph() {
return new StateGraph(MySchema)
.addNode('agent', async (state: MyStateType, runtime: Runtime) => {
const response = await agent.invoke(state, {
recursionLimit: 100,
configurable: runtime.configurable,
});
return response;
})
.addEdge(START, 'agent')
.compile();
}
export const graph = createMyGraph();
Testing
Testing Agents
import { describe, it, expect } from 'vitest';
import { FakeListChatModel } from '@langchain/core/utils/testing';
describe('MyAgent', () => {
it('should respond to messages', async () => {
const fakeModel = new FakeListChatModel({
responses: ['Hello! How can I help you?'],
});
const agent = createAgent({
name: 'TestAgent',
model: fakeModel,
tools: [myTool],
stateSchema: MySchema,
});
const result = await agent.invoke({ messages: [] });
expect(result.messages.length).toBeGreaterThan(0);
});
});
Testing Tools
describe('myTool', () => {
it('should return correct result', async () => {
const result = await myTool.invoke({
param1: 'test',
param2: 42,
});
expect(result).toBe('result');
});
});
Common Patterns
Multi-Agent System
// Create specialized agents
const coderAgent = createAgent({
name: 'Coder',
model,
systemPrompt: 'You are an expert software engineer',
tools: [searchTool, codeEditTool],
stateSchema: MySchema,
});
const reviewerAgent = createAgent({
name: 'Reviewer',
model,
systemPrompt: 'You review code for bugs and best practices',
tools: [readTool, analysisTool],
stateSchema: MySchema,
});
// Use based on task
const result = await coderAgent.invoke({
messages: [new HumanMessage('Write code for X')],
});
Tool Selection Based on State
const tools = state.is_in_task ? [commitTaskTool] : [addTaskTool, searchTool, analysisTool];
const agent = createAgent({
name: 'DynamicAgent',
model,
systemPrompt: 'You are a helpful assistant',
tools,
stateSchema: MySchema,
});
Error Recovery
const result = await agent
.invoke(
{ messages: [new HumanMessage('Do something')] },
{
signal: AbortSignal.timeout(30000), // 30s timeout
},
)
.catch((error) => {
console.error('Agent failed:', error);
return { messages: [new AIMessage('Sorry, something went wrong.')] };
});
File Organization
Recommended Structure
src/
├── agents/
│ ├── index.ts # Agent factory functions
│ ├── coder.ts # Coding agent
│ └── reviewer.ts # Review agent
├── tools/
│ ├── index.ts # Export all tools
│ ├── search.ts
│ └── code.ts
├── middlewares/
│ ├── index.ts
│ ├── logging.ts
│ └── cache.ts
├── state.ts # State definitions
└── models.ts # Model initialization
Best Practices
- 使用
StateSchema:单一对象同时承担类型定义和运行时行为,不再需要 Zod schema + Annotation 双对象 - 普通字段用 Zod schema:
z.string().default('...')即可,无需createDefaultAnnotation - 并发更新字段用
ReducedValue:fanout/并行节点写同一 key 时必须定义 reducer,否则报INVALID_CONCURRENT_GRAPH_UPDATE messages直接用MessagesValue:预构建,无需手写 reducer- Async Initialization:始终 await 模型初始化
- Error Handling:agent 调用包裹在 try/catch 中
- Tool Focus:每个 tool 单一职责,命名清晰
- Testing:agent 和 tool 独立测试
- Streaming:支持流式输出以改善 UX
- Configuration:复杂系统使用 AgentPackage
Common Issues
并发更新报错 INVALID_CONCURRENT_GRAPH_UPDATE
fanout 或并行节点同时写入同一字段时,需要用 ReducedValue 定义合并逻辑:
// 错误:并行节点都写 results,会报错
export const BadSchema = new StateSchema({
results: z.array(z.string()).default(() => []),
});
// 正确:使用 ReducedValue 定义 reducer
export const GoodSchema = new StateSchema({
results: new ReducedValue(
z.array(z.string()).default(() => []),
{
reducer: (existing, update: string[]) => existing.concat(update),
},
),
});
Type errors with tools
// Use type assertion
const tools = [myTool1 as any, myTool2 as any];
State not updating
// 确保 StateSchema 传入 createAgent 和 StateGraph 是同一个实例
export const MySchema = new StateSchema({ ... });
const agent = createAgent({ stateSchema: MySchema, ... });
const graph = new StateGraph(MySchema);
Installation
Core Dependencies
npm install langchain @langchain/core @langchain/langgraph @langchain/openai @langchain/anthropic zod
Development Dependencies
npm install --save-dev typescript vitest
Frontend Development
For building chat interfaces and UI components that connect to LangGraph backend, see:
The @langgraph-js/sdk package provides:
ChatProviderReact component for wrapping your appuseChat()hook for accessing chat state and mutations- Message rendering components (human, AI, tool messages)
- Agent selection and dynamic switching
- Streaming support via Server-Sent Events
When to use: Building web UIs, chat interfaces, or dashboards for LangGraph agents.
Configuration-Driven Agents (Optional)
For production systems requiring dynamic configuration, tool registries, and multi-agent management from storage, see:
The @langgraph-js/standard-agent package provides an abstraction layer with:
- AgentPackage for centralized configuration
- Tool and middleware registries
- Pluggable storage backends (memory, file system, database)
- Multi-agent management from configuration
When to use: Database-driven configuration, multi-agent routing, dynamic tool/middleware loading. For simple agents, use the patterns above directly.
Resources
- LangChain TypeScript
- @langgraph-js
- Middleware Guide - Comprehensive middleware patterns
- Standard Agent System - Configuration-driven agents
- Model Metadata - Model configuration and metadata management
Converted and distributed by TomeVault — claim your Tome and manage your conversions.