本指南演示 LangGraph 的 Graph API 基础。它会介绍状态 ,以及如何组合常见的图结构,例如序列 、分支 和循环 。它还涵盖 LangGraph 的控制功能,包括用于 map-reduce 工作流的 Send API ,以及用于将状态更新与跨节点“跳转”结合起来的 Command API 。
安装 langgraph:
npm install @langchain/langgraph
设置 LangSmith 以便更好地调试 注册 LangSmith ,可以快速发现问题并提升 LangGraph 项目的性能。LangSmith 允许你使用 trace 数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用——更多入门信息请阅读文档 。
定义和更新状态
这里我们展示如何在 LangGraph 中定义和更新状态 。我们将演示:
如何使用状态来定义图的 schema
如何使用 reducers 控制状态更新的处理方式。
定义状态
LangGraph 中的状态 使用 StateSchema 类定义。它提供了统一的 API,可以接受单个字段的标准 schema (例如 Zod ),以及 ReducedValue、MessagesValue 和 UntrackedValue 等特殊值类型。
默认情况下,图会具有相同的输入和输出 schema,而状态决定了该 schema。有关如何定义不同的输入和输出 schema,请参阅定义输入和输出 schema 。
我们来看一个使用 messages 的简单示例。对于许多 LLM 应用来说,这是一种通用的状态表达方式。更多细节请参阅我们的概念页面 。
import { StateSchema , MessagesValue } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
messages : MessagesValue ,
extraField : z . number () ,
} ) ;
这个状态会跟踪一个消息 对象列表,以及一个额外的整数字段。
更新状态
我们来构建一个只有单个节点的示例图。我们的节点 只是一个 TypeScript 函数,它读取图的状态并对其进行更新。这个函数的第一个参数始终是状态:
import { AIMessage } from "@langchain/core/messages" ;
import { GraphNode } from "@langchain/langgraph" ;
const node : GraphNode < typeof State > = ( state ) => {
const messages = state . messages ;
const newMessage = new AIMessage ( "你好!" ) ;
return { messages : [newMessage] , extraField : 10 };
};
这个节点只是向消息列表追加一条消息(reducer 会处理拼接),并填充一个额外字段。
节点应该直接返回对状态的更新,而不是修改状态本身。
接下来我们定义一个包含此节点的简单图。我们使用 StateGraph 来定义一个对该状态进行操作的图。然后使用 addNode 填充图。
import { StateGraph } from "@langchain/langgraph" ;
const graph = new StateGraph (State)
. addNode ( "node" , node)
. addEdge ( "__start__" , "node" )
. compile () ;
LangGraph 提供了内置工具来可视化你的图。我们来查看这个图。有关可视化的详细信息,请参阅可视化你的图 。
import * as fs from "node:fs/promises" ;
const drawableGraph = await graph . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;
在这个例子中,我们的图只执行单个节点。我们继续做一次简单调用:
import { HumanMessage } from "@langchain/core/messages" ;
const result = await graph . invoke ( { messages : [ new HumanMessage ( "你好" )] , extraField : 0 } ) ;
console . log (result) ;
{ messages: [HumanMessage { content: '你好' }, AIMessage { content: '你好!' }], extraField: 10 }
请注意:
我们通过更新状态中的单个 key 来启动调用。
我们会在调用结果中收到完整状态。
为方便起见,我们经常通过日志查看消息对象 的内容:
for ( const message of result . messages) {
console . log ( ` ${ message . getType () } : ${ message . content } ` ) ;
}
使用 reducer 处理状态更新
状态中的每个 key 都可以拥有自己独立的 reducer 函数,用于控制如何应用来自节点的更新。如果没有显式指定 reducer 函数,则默认认为对该 key 的所有更新都会覆盖原值。
在前面的示例中,我们使用了 MessagesValue,它已经内置了 reducer。对于自定义字段,你可以使用 ReducedValue 定义如何应用更新。
在前面的示例中,我们的节点通过追加一条消息来更新状态中的 "messages" key。MessagesValue reducer 会自动处理这一点:
import { StateSchema , MessagesValue , ReducedValue } from "@langchain/langgraph" ;
import * as z from "zod" ;
// MessagesValue 已经内置了 reducer
const State = new StateSchema ( {
messages : MessagesValue ,
extraField : z . number () ,
} ) ;
我们的节点可以只返回新消息(reducer 会处理拼接):
import { GraphNode } from "@langchain/langgraph" ;
const node : GraphNode < typeof State > = ( state ) => {
const newMessage = new AIMessage ( "你好!" ) ;
return { messages : [newMessage] , extraField : 10 };
};
import { START } from "@langchain/langgraph" ;
const graph = new StateGraph (State)
. addNode ( "node" , node)
. addEdge (START , "node" )
. compile () ;
const result = await graph . invoke ( { messages : [ new HumanMessage ( "你好" )] } ) ;
for ( const message of result . messages) {
console . log ( ` ${ message . getType () } : ${ message . content } ` ) ;
}
MessagesValue
在实践中,更新消息列表时还有一些额外注意事项:
LangGraph 包含内置的 MessagesValue,可以处理这些情况:
import { StateSchema , StateGraph , MessagesValue , GraphNode , START } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
messages : MessagesValue ,
extraField : z . number () ,
} ) ;
const node : GraphNode < typeof State > = ( state ) => {
const newMessage = new AIMessage ( "你好!" ) ;
return { messages : [newMessage] , extraField : 10 };
};
const graph = new StateGraph (State)
. addNode ( "node" , node)
. addEdge (START , "node" )
. compile () ;
const inputMessage = { role : "user" , content : "你好" };
const result = await graph . invoke ( { messages : [inputMessage] } ) ;
for ( const message of result . messages) {
console . log ( ` ${ message . getType () } : ${ message . content } ` ) ;
}
对于涉及聊天模型 的应用来说,这是一种通用的状态表示方式。为了方便使用,LangGraph 内置了 MessagesValue,因此我们可以这样写:
import { StateSchema , MessagesValue } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
messages : MessagesValue ,
extraField : z . number () ,
} ) ;
定义输入和输出 schema
默认情况下,StateGraph 使用单个 schema 运行,并且期望所有节点都使用该 schema 进行通信。不过,也可以为图定义不同的输入和输出 schema。
当指定了不同的 schema 时,节点之间的通信仍会使用内部 schema。输入 schema 确保提供的输入符合预期结构,而输出 schema 会过滤内部数据,只返回根据已定义输出 schema 相关的信息。
下面我们将了解如何定义不同的输入和输出 schema。
import { StateGraph , StateSchema , GraphNode , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
// 定义输入的 schema
const InputState = new StateSchema ( {
question : z . string () ,
} ) ;
// 定义输出的 schema
const OutputState = new StateSchema ( {
answer : z . string () ,
} ) ;
// 定义整体 schema,将输入和输出合并
const OverallState = new StateSchema ( {
question : z . string () ,
answer : z . string () ,
} ) ;
// 定义处理输入的节点
const answerNode : GraphNode < typeof OverallState > = ( state ) => {
// 示例答案和一个额外 key
return { answer : "再见" , question : state . question };
};
// 构建图,并指定输入和输出 schema
const graph = new StateGraph ( {
input : InputState ,
output : OutputState ,
state : OverallState ,
} )
. addNode ( "answerNode" , answerNode)
. addEdge (START , "answerNode" )
. addEdge ( "answerNode" , END)
. compile () ;
// 使用输入调用图并打印结果
console . log ( await graph . invoke ( { question : "你好" } )) ;
请注意,invoke 的输出只包含输出 schema。
在节点之间传递私有状态
在某些情况下,你可能希望节点交换一些对中间逻辑至关重要、但不需要成为图主 schema 一部分的信息。这些私有数据与图的整体输入/输出无关,并且应该只在特定节点之间共享。
下面,我们将创建一个由三个节点(node_1、node_2 和 node_3)组成的顺序图示例,其中私有数据在前两个步骤(node_1 和 node_2)之间传递,而第三个步骤(node_3)只能访问公开的整体状态。
import { StateGraph , StateSchema , GraphNode , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
// 图的整体状态(这是跨节点共享的公共状态)
const OverallState = new StateSchema ( {
a : z . string () ,
} ) ;
// node1 的输出包含不属于整体状态的私有数据
const Node1Output = new StateSchema ( {
privateData : z . string () ,
} ) ;
// 节点 2 的输入只请求 node1 之后可用的私有数据
const Node2Input = new StateSchema ( {
privateData : z . string () ,
} ) ;
// 私有数据只在 node1 和 node2 之间共享
const node1 : GraphNode < typeof OverallState > = ( state ) => {
const output = { privateData : "由 node1 设置" };
console . log ( `进入节点 'node1': \n\t 输入: ${ JSON . stringify ( state ) } 。 \n\t 返回: ${ JSON . stringify ( output ) } ` ) ;
return output ;
};
const node2 : GraphNode < typeof Node2Input > = ( state ) => {
const output = { a : "由 node2 设置" };
console . log ( `进入节点 'node2': \n\t 输入: ${ JSON . stringify ( state ) } 。 \n\t 返回: ${ JSON . stringify ( output ) } ` ) ;
return output ;
};
// 节点 3 只能访问整体状态(不能访问来自 node1 的私有数据)
const node3 : GraphNode < typeof OverallState > = ( state ) => {
const output = { a : "由 node3 设置" };
console . log ( `进入节点 'node3': \n\t 输入: ${ JSON . stringify ( state ) } 。 \n\t 返回: ${ JSON . stringify ( output ) } ` ) ;
return output ;
};
// 按顺序连接节点
// node2 接收来自 node1 的私有数据,而
// node3 看不到这些私有数据。
const graph = new StateGraph (OverallState)
. addNode ( "node1" , node1)
. addNode ( "node2" , node2 , { input : Node2Input } )
. addNode ( "node3" , node3)
. addEdge (START , "node1" )
. addEdge ( "node1" , "node2" )
. addEdge ( "node2" , "node3" )
. addEdge ( "node3" , END)
. compile () ;
// 使用初始状态调用图
const response = await graph . invoke ( { a : "在开始时设置" } ) ;
console . log ( ` \n 图调用的输出: ${ JSON . stringify ( response ) } ` ) ;
进入节点 'node1':
Input: {"a":"在开始时设置"}.
Returned: {"privateData":"由 node1 设置"}
进入节点 'node2':
Input: {"privateData":"由 node1 设置"}.
Returned: {"a":"由 node2 设置"}
进入节点 'node3':
Input: {"a":"由 node2 设置"}.
Returned: {"a":"由 node3 设置"}
图调用的输出:{"a":"由 node3 设置"}
替代的状态定义方式
虽然 StateSchema 是定义状态的推荐方式,但 LangGraph 也支持其他几种方法。本节涵盖所有可用选项。
Channels API
channels API 提供了对状态管理的底层控制。LangGraph 提供了几种内置通道类型:
Channel Type Behavior Use Case LastValue存储最新的值 会被覆盖的简单字段 BinaryOperatorAggregate使用 reducer 函数组合值 累积值(计数器、列表) Topic将所有值收集为一个序列 事件流、审计日志 EphemeralValue在 superstep 之间重置的值 临时计算状态
使用对象简写:
当你传入一个带有 reducer 和 default 的对象时,它会创建一个 BinaryOperatorAggregate 通道。传入 null 会创建一个 LastValue 通道:
import { BaseMessage } from "@langchain/core/messages" ;
import { StateGraph } from "@langchain/langgraph" ;
interface WorkflowState {
messages : BaseMessage[] ;
question : string ;
answer : string ;
}
const workflow = new StateGraph < WorkflowState > ( {
channels : {
// BinaryOperatorAggregate:使用 reducer 组合值
messages : {
reducer : ( current , update ) => current . concat (update) ,
default : () => [] ,
},
// LastValue:存储最新的值(null = 无 reducer)
question : null ,
answer : null ,
},
} ) ;
直接使用通道类:
如需更多控制,可以直接实例化通道类:
import { BaseMessage } from "@langchain/core/messages" ;
import { StateGraph , LastValue , BinaryOperatorAggregate , Topic } from "@langchain/langgraph" ;
interface WorkflowState {
messages : BaseMessage[] ;
question : string ;
events : string [] ;
}
const workflow = new StateGraph < WorkflowState > ( {
channels : {
messages : new BinaryOperatorAggregate < BaseMessage[] > (
( current , update ) => current . concat (update) ,
() => []
) ,
question : new LastValue < string > () ,
// Topic 会收集执行期间推送的所有值
events : new Topic < string > () ,
},
} ) ;
Annotation.Root
Annotation.Root 提供了一种声明式方式来定义带有 reducer 的状态。它类似于 StateSchema,但语法不同:
import { BaseMessage } from "@langchain/core/messages" ;
import { Annotation , StateGraph , messagesStateReducer } from "@langchain/langgraph" ;
const State = Annotation . Root ( {
messages : Annotation < BaseMessage[] > ( {
reducer : messagesStateReducer ,
default : () => [] ,
} ) ,
question : Annotation < string > () ,
count : Annotation < number > ( {
reducer : ( current , update ) => current + update ,
default : () => 0 ,
} ) ,
} ) ;
const graph = new StateGraph (State) ;
使用 Zod v3 的 Zod 对象
使用 Zod v3 时,可以用普通的 z.object() schema 定义状态。LangGraph 通过 .langgraph 插件扩展了 Zod v3,该插件提供 .reducer() 和 .metadata() 方法:
import { z } from "zod/v3" ;
import { BaseMessage } from "@langchain/core/messages" ;
import { StateGraph , messagesStateReducer } from "@langchain/langgraph" ;
const State = z . object ( {
// 使用 .langgraph.reducer() 附加 reducer 函数
messages : z
. array (z . custom < BaseMessage > ())
. default ([])
. langgraph . reducer (messagesStateReducer) ,
// 简单字段可直接工作(最后写入者获胜)
question : z . string () . optional () ,
answer : z . string () . optional () ,
// 用于累积值的自定义 reducer
count : z
. number ()
. default ( 0 )
. langgraph . reducer ( ( current , update ) => current + update) ,
} ) ;
const graph = new StateGraph (State) ;
使用 Zod v4 的 Zod 对象
Zod v4 使用基于 registry 的方式。使用 LangGraph registry 为 schema 字段附加元数据:
import * as z from "zod" ;
import { BaseMessage } from "@langchain/core/messages" ;
import { StateGraph , MessagesZodMeta , messagesStateReducer } from "@langchain/langgraph" ;
import { registry } from "@langchain/langgraph/zod" ;
const State = z . object ( {
// 使用 .register() 搭配 LangGraph registry 和 MessagesZodMeta
messages : z
. array (z . custom < BaseMessage > ())
. default ([])
. register (registry , MessagesZodMeta) ,
// 简单字段可直接工作(最后写入者获胜)
question : z . string () . optional () ,
answer : z . string () . optional () ,
// 通过 registry 元数据使用自定义 reducer
count : z
. number ()
. default ( 0 )
. register (registry , { reducer : ( current : number , update : number ) => current + update } ) ,
} ) ;
const graph = new StateGraph (State) ;
对比表
Approach Reducers Type Safety Zod Version Recommended StateSchema✅ 内置 ✅ 完整 v3 或 v4 ✅ 是 Channels API ✅ 手动 ⚠️ 部分 N/A 适用于高级场景 Annotation.Root✅ 内置 ✅ 完整 N/A 旧版 Zod v3 + .langgraph ✅ 通过插件 ✅ 完整 仅 v3 旧版 Zod v4 + registry ✅ 通过 registry ✅ 完整 仅 v4 旧版
添加运行时配置
有时你希望在调用图时能够配置它。例如,你可能希望能够在运行时指定要使用哪个 LLM 或系统提示,而不把这些参数污染到图状态中 。
要添加运行时配置:
为配置指定 schema
将配置添加到节点或条件边的函数签名中
将配置传入图。
下面是一个简单示例:
import { StateGraph , StateSchema , GraphNode , END , START } from "@langchain/langgraph" ;
import * as z from "zod" ;
// 1. 指定配置 schema
const ContextSchema = z . object ( {
myRuntimeValue : z . string () ,
} ) ;
// 2. 定义一个在节点中访问配置的图
const State = new StateSchema ( {
myStateValue : z . number () ,
} ) ;
const node : GraphNode < typeof State > = ( state , runtime ) => {
if (runtime ?. context ?. myRuntimeValue === "a" ) {
return { myStateValue : 1 };
} else if (runtime ?. context ?. myRuntimeValue === "b" ) {
return { myStateValue : 2 };
} else {
throw new Error ( "未知值。" ) ;
}
};
const graph = new StateGraph (State , ContextSchema)
. addNode ( "node" , node)
. addEdge (START , "node" )
. addEdge ( "node" , END)
. compile () ;
// 3. 在运行时传入配置:
console . log ( await graph . invoke ( {}, { context : { myRuntimeValue : "a" } } )) ;
console . log ( await graph . invoke ( {}, { context : { myRuntimeValue : "b" } } )) ;
{ myStateValue: 1 }
{ myStateValue: 2 }
下面我们演示一个实用示例:在运行时配置要使用的 LLM。我们将同时使用 OpenAI 和 Anthropic 模型。 import { ChatOpenAI } from "@langchain/openai" ;
import { ChatAnthropic } from "@langchain/anthropic" ;
import { StateGraph , StateSchema , MessagesValue , GraphNode , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const ConfigSchema = z . object ( {
modelProvider : z . string () . default ( "anthropic" ) ,
} ) ;
const State = new StateSchema ( {
messages : MessagesValue ,
} ) ;
const MODELS = {
anthropic : new ChatAnthropic ( { model : "claude-haiku-4-5-20251001" } ) ,
openai : new ChatOpenAI ( { model : "gpt-5.4-mini" } ) ,
};
const callModel : GraphNode < typeof State > = async ( state , config ) => {
const modelProvider = config ?. configurable ?. modelProvider || "anthropic" ;
const model = MODELS[modelProvider as keyof typeof MODELS] ;
const response = await model . invoke (state . messages) ;
return { messages : [response] };
};
const graph = new StateGraph (State , ConfigSchema)
. addNode ( "model" , callModel)
. addEdge (START , "model" )
. addEdge ( "model" , END)
. compile () ;
// 用法
const inputMessage = { role : "user" , content : "你好" };
// 不传配置时,使用默认值(Anthropic)
const response1 = await graph . invoke ( { messages : [inputMessage] } ) ;
// 或者,可以设置为 OpenAI
const response2 = await graph . invoke (
{ messages : [inputMessage] },
{ configurable : { modelProvider : "openai" } },
) ;
console . log (response1 . messages . at ( - 1 ) ?. response_metadata ?. model) ;
console . log (response2 . messages . at ( - 1 ) ?. response_metadata ?. model) ;
claude-haiku-4-5-20251001
gpt-5.4-mini
下面我们演示一个实用示例:在运行时配置两个参数:要使用的 LLM 和系统消息。 import { ChatOpenAI } from "@langchain/openai" ;
import { ChatAnthropic } from "@langchain/anthropic" ;
import { SystemMessage } from "@langchain/core/messages" ;
import { StateGraph , StateSchema , MessagesValue , GraphNode , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const ConfigSchema = z . object ( {
modelProvider : z . string () . default ( "anthropic" ) ,
systemMessage : z . string () . optional () ,
} ) ;
const State = new StateSchema ( {
messages : MessagesValue ,
} ) ;
const MODELS = {
anthropic : new ChatAnthropic ( { model : "claude-haiku-4-5-20251001" } ) ,
openai : new ChatOpenAI ( { model : "gpt-5.4-mini" } ) ,
};
const callModel : GraphNode < typeof State > = async ( state , config ) => {
const modelProvider = config ?. configurable ?. modelProvider || "anthropic" ;
const systemMessage = config ?. configurable ?. systemMessage ;
const model = MODELS[modelProvider as keyof typeof MODELS] ;
let messages = state . messages ;
if (systemMessage) {
messages = [ new SystemMessage (systemMessage) , ... messages] ;
}
const response = await model . invoke (messages) ;
return { messages : [response] };
};
const graph = new StateGraph (State , ConfigSchema)
. addNode ( "model" , callModel)
. addEdge (START , "model" )
. addEdge ( "model" , END)
. compile () ;
// 用法
const inputMessage = { role : "user" , content : "你好" };
const response = await graph . invoke (
{ messages : [inputMessage] },
{
configurable : {
modelProvider : "openai" ,
systemMessage : "请用意大利语回答。"
}
}
) ;
for ( const message of response . messages) {
console . log ( ` ${ message . getType () } : ${ message . content } ` ) ;
}
human: 你好
ai: Ciao! Come posso aiutarti oggi?
添加重试策略
在很多使用场景中,你可能希望节点拥有自定义重试策略,例如调用 API、查询数据库或调用 LLM 等。LangGraph 允许你为节点添加重试策略。
要配置重试策略,请将 retryPolicy 参数传给 addNode 。retryPolicy 参数接收一个 RetryPolicy 对象。下面我们使用默认参数实例化一个 RetryPolicy 对象,并将它关联到一个节点:
import { RetryPolicy } from "@langchain/langgraph" ;
const graph = new StateGraph (State)
. addNode ( "nodeName" , nodeFunction , { retryPolicy : {} } )
. compile () ;
默认情况下,重试策略会对除以下异常之外的任何异常进行重试:
TypeError
SyntaxError
ReferenceError
考虑一个从 SQL 数据库读取数据的示例。下面我们向节点传入两个不同的重试策略: import Database from "better-sqlite3" ;
import { ChatAnthropic } from "@langchain/anthropic" ;
import { StateGraph , StateSchema , MessagesValue , GraphNode , START , END } from "@langchain/langgraph" ;
import { AIMessage } from "@langchain/core/messages" ;
const State = new StateSchema ( {
messages : MessagesValue ,
} ) ;
// 创建一个内存数据库
const db : typeof Database . prototype = new Database ( ":memory:" ) ;
const model = new ChatAnthropic ( { model : "claude-3-5-sonnet-20240620" } ) ;
const callModel : GraphNode < typeof State > = async ( state ) => {
const response = await model . invoke (state . messages) ;
return { messages : [response] };
};
const queryDatabase : GraphNode < typeof State > = async ( state ) => {
const queryResult : string = JSON . stringify (
db . prepare ( "SELECT * FROM Artist LIMIT 10;" ) . all () ,
) ;
return { messages : [ new AIMessage ( { content : "queryResult" } )] };
};
const workflow = new StateGraph (State)
// 定义我们将在其间循环的两个节点
. addNode ( "call_model" , callModel , { retryPolicy : { maxAttempts : 5 } } )
. addNode ( "query_database" , queryDatabase , {
retryPolicy : {
retryOn : ( e : any ) : boolean => {
if (e instanceof Database . SqliteError ) {
// 对 "SQLITE_BUSY" 错误进行重试
return e . code === "SQLITE_BUSY" ;
}
return false ; // 不对其他错误进行重试
},
},
} )
. addEdge (START , "call_model" )
. addEdge ( "call_model" , "query_database" )
. addEdge ( "query_database" , END) ;
const graph = workflow . compile () ;
在节点内部访问执行信息
你可以通过 runtime.executionInfo 访问执行身份和重试信息。它会暴露线程、运行和检查点标识符以及重试状态,而无需直接从 config 中读取。
属性 类型 描述 threadIdstring | undefined当前执行的线程 ID。 runIdstring | undefined当前执行的运行 ID。 checkpointIdstring当前执行的检查点 ID。 checkpointNsstring当前执行的检查点命名空间。 taskIdstring当前执行的任务 ID。 nodeAttemptnumber当前执行尝试次数(从 1 开始计数)。 nodeFirstAttemptTimenumber | undefined第一次尝试开始时的 Unix 时间戳(秒)。在多次重试之间保持不变。
访问线程 ID 和运行 ID
使用 executionInfo 在节点内部访问线程 ID、运行 ID 和其他身份字段:
import { StateGraph , StateSchema , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
result : z . string () ,
} ) ;
const myNode : GraphNode < typeof State > = async ( state , runtime ) => {
const info = runtime . executionInfo ;
console . log ( `Thread: ${ info . threadId } , Run: ${ info . runId } ` ) ;
return { result : "done" };
};
const graph = new StateGraph (State)
. addNode ( "my_node" , myNode)
. addEdge (START , "my_node" )
. addEdge ( "my_node" , END)
. compile () ;
根据重试状态调整行为
当节点有重试策略时,使用 executionInfo 检查当前尝试次数,并在第一次尝试失败后切换到备用方案:
import { StateGraph , StateSchema , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
result : z . string () ,
} ) ;
const myNode : GraphNode < typeof State > = async ( state , runtime ) => {
const info = runtime . executionInfo ;
if (info . nodeAttempt > 1 ) {
// 在重试时使用备用方案
return { result : await callFallbackApi () };
}
return { result : await callPrimaryApi () };
};
const graph = new StateGraph (State)
. addNode ( "my_node" , myNode , { retryPolicy : { maxAttempts : 3 } } )
. addEdge (START , "my_node" )
. addEdge ( "my_node" , END)
. compile () ;
即使没有重试策略,Runtime 对象上也可以使用 executionInfo —— nodeAttempt 默认值为 1,nodeFirstAttemptTime 会设置为节点开始执行的时间。
在节点内部访问服务器信息
当你的图运行在 LangGraph Server 上时,可以通过 runtime.serverInfo 访问服务器特定的元数据。
属性 类型 描述 assistantIdstring当前部署的助手 ID。 graphIdstring当前部署的图 ID。 userBaseUser | null已认证用户(如果配置了 custom auth )。
const myNode : GraphNode < typeof State > = async ( state , runtime ) => {
const server = runtime . serverInfo ;
if (server != null ) {
console . log ( `Assistant: ${ server . assistantId } , Graph: ${ server . graphId } ` ) ;
if (server . user != null ) {
console . log ( `User: ${ server . user . identity } ` ) ;
}
}
return { result : "done" };
};
当图未运行在 LangGraph Server 上时,serverInfo 为 null。
runtime.executionInfo 和 runtime.serverInfo 需要 deepagents>=1.9.0(或 @langchain/langgraph>=1.2.8)。
创建一系列步骤
前置条件
本指南假设你熟悉上文关于 state 的部分。
这里我们演示如何构建一个简单的步骤序列。我们将展示:
如何构建顺序图
用于构建类似图的内置简写方式。
要添加一系列节点,我们使用 graph 的 .addNode 和 .addEdge 方法:
import { START , StateGraph } from "@langchain/langgraph" ;
const builder = new StateGraph (State)
. addNode ( "step1" , step1)
. addNode ( "step2" , step2)
. addNode ( "step3" , step3)
. addEdge (START , "step1" )
. addEdge ( "step1" , "step2" )
. addEdge ( "step2" , "step3" ) ;
为什么要用 LangGraph 将应用步骤拆分成一个序列?
LangGraph 让你可以轻松地为应用添加底层持久化层。
这允许在节点执行之间对状态进行检查点保存,因此你的 LangGraph 节点会决定: 它们还决定执行步骤如何被 streamed ,以及如何使用 Studio 可视化和调试你的应用。 我们来演示一个端到端示例。我们将创建一个包含三个步骤的序列:
在状态的某个键中填充一个值
更新同一个值
填充另一个不同的值
首先定义我们的 state 。它决定了 图的 schema ,也可以指定如何应用更新。更多细节请参阅 使用 reducer 处理状态更新 。 在本例中,我们只跟踪两个值: import { StateSchema , GraphNode } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
value1 : z . string () ,
value2 : z . number () ,
} ) ;
我们的 nodes 只是一些 TypeScript 函数,它们读取图的状态并对其进行更新。这个函数的第一个参数始终是状态: const step1 : GraphNode < typeof State > = ( state ) => {
return { value1 : "a" };
};
const step2 : GraphNode < typeof State > = ( state ) => {
const currentValue1 = state . value1 ;
return { value1 : ` ${ currentValue1 } b` };
};
const step3 : GraphNode < typeof State > = ( state ) => {
return { value2 : 10 };
};
最后,我们定义图。我们使用 StateGraph 来定义一个在该状态上运行的图。 然后我们将使用 addNode 和 addEdge 来填充图并定义其控制流。 import { START , StateGraph } from "@langchain/langgraph" ;
const graph = new StateGraph (State)
. addNode ( "step1" , step1)
. addNode ( "step2" , step2)
. addNode ( "step3" , step3)
. addEdge (START , "step1" )
. addEdge ( "step1" , "step2" )
. addEdge ( "step2" , "step3" )
. compile () ;
指定自定义名称
你可以使用 .addNode 为节点指定自定义名称:const graph = new StateGraph (State)
. addNode ( "myNode" , step1)
. compile () ;
请注意:
.addEdge 接收节点名称;对于函数,默认是 node.name。
我们必须指定图的入口点。为此,我们添加一条带有 START node 的边。
当没有更多节点可执行时,图会停止。
接下来,我们 compile 图。这会对图结构进行一些基本检查(例如识别孤立节点)。如果我们通过 checkpointer 为应用添加持久化,也会在这里传入它。 LangGraph 提供了用于可视化图的内置工具。我们来检查一下这个序列。有关可视化的详细信息,请参阅 可视化你的图 。 import * as fs from "node:fs/promises" ;
const drawableGraph = await graph . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;
接下来进行一次简单调用: const result = await graph . invoke ( { value1 : "c" } ) ;
console . log (result) ;
{ value1: 'a b', value2: 10 }
请注意:
我们通过为单个状态键提供一个值来启动调用。我们必须始终为至少一个键提供值。
我们传入的值被第一个节点覆盖了。
第二个节点更新了该值。
第三个节点填充了另一个不同的值。
创建分支
节点的并行执行对于加快整体图运行至关重要。LangGraph 原生支持节点的并行执行,可以显著提升基于图的工作流性能。这种并行化通过 fan-out 和 fan-in 机制实现,并同时利用标准边和 conditional_edges 。下面是一些示例,展示如何创建适合你的分支数据流。
并行运行图节点
在这个示例中,我们从 Node A 扇出到 B and C,然后扇入到 D。在状态中,我们指定 reducer add 操作 。它会对 State 中特定键的值进行合并或累积,而不是简单地覆盖现有值。对于列表,这意味着将新列表与现有列表连接起来。有关使用 reducer 更新状态的更多细节,请参阅上文 state reducers 部分。
import { StateGraph , StateSchema , ReducedValue , GraphNode , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
// reducer 使其只能追加
aggregate : new ReducedValue (
z . array (z . string ()) . default ( () => []) ,
{ reducer : ( x , y ) => x . concat (y) }
) ,
} ) ;
const nodeA : GraphNode < typeof State > = ( state ) => {
console . log ( `Adding "A" to ${ state . aggregate } ` ) ;
return { aggregate : [ "A" ] };
};
const nodeB : GraphNode < typeof State > = ( state ) => {
console . log ( `Adding "B" to ${ state . aggregate } ` ) ;
return { aggregate : [ "B" ] };
};
const nodeC : GraphNode < typeof State > = ( state ) => {
console . log ( `Adding "C" to ${ state . aggregate } ` ) ;
return { aggregate : [ "C" ] };
};
const nodeD : GraphNode < typeof State > = ( state ) => {
console . log ( `Adding "D" to ${ state . aggregate } ` ) ;
return { aggregate : [ "D" ] };
};
const graph = new StateGraph (State)
. addNode ( "a" , nodeA)
. addNode ( "b" , nodeB)
. addNode ( "c" , nodeC)
. addNode ( "d" , nodeD)
. addEdge (START , "a" )
. addEdge ( "a" , "b" )
. addEdge ( "a" , "c" )
. addEdge ( "b" , "d" )
. addEdge ( "c" , "d" )
. addEdge ( "d" , END)
. compile () ;
import * as fs from "node:fs/promises" ;
const drawableGraph = await graph . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;
通过 reducer,你可以看到每个节点中添加的值会被累积起来。
const result = await graph . invoke ( {
aggregate : [] ,
} ) ;
console . log (result) ;
Adding "A" to []
Adding "B" to ['A']
Adding "C" to ['A']
Adding "D" to ['A', 'B', 'C']
{ aggregate: ['A', 'B', 'C', 'D'] }
在上面的示例中,节点 "b" 和 "c" 会在同一个 superstep 中并发执行。因为它们位于同一步中,所以节点 "d" 会在 "b" 和 "c" 都完成之后执行。 重要的是,来自并行 superstep 的更新顺序可能并不总是一致。如果你需要并行 superstep 中更新具有一致且预先确定的顺序,应将输出写入状态中的单独字段,并附带一个可用于排序的值。
LangGraph 在 supersteps 中执行节点,这意味着虽然并行分支是并行执行的,但整个 superstep 是事务性的 。如果其中任何一个分支抛出异常,不会 将任何更新应用到状态(整个 superstep 会报错)。 重要的是,在使用 checkpointer 时,superstep 中成功节点的结果会被保存,并且在恢复时不会重复执行。 如果你有容易出错的操作(比如想处理不稳定的 API 调用),LangGraph 提供了两种方式来解决:
你可以在节点中编写普通的 python 代码来捕获并处理异常。
你可以设置一个 retry_policy ,指示图对抛出特定类型异常的节点进行重试。只有失败的分支会被重试,因此你无需担心执行冗余工作。
这两者结合起来,可以让你执行并行流程,并完全控制异常处理。
设置最大并发数
你可以在调用图时,通过在 configuration 中设置 max_concurrency 来控制最大并发任务数。const result = await graph . invoke ( { value1 : "c" }, { configurable : { max_concurrency : 10 }} ) ;
条件分支
如果你的 fan-out 需要在运行时根据状态变化,可以使用 addConditionalEdges 通过图状态选择一条或多条路径。请看下面的示例,其中节点 a 生成一个状态更新,用来决定后续节点。
import { StateGraph , StateSchema , ReducedValue , GraphNode , ConditionalEdgeRouter , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
aggregate : new ReducedValue (
z . array (z . string ()) . default ( () => []) ,
{ reducer : ( x , y ) => x . concat (y) }
) ,
// 向状态添加一个键。我们将设置这个键来决定
// 如何分支。
which : z . string () ,
} ) ;
const nodeA : GraphNode < typeof State > = ( state ) => {
console . log ( `Adding "A" to ${ state . aggregate } ` ) ;
return { aggregate : [ "A" ] , which : "c" };
};
const nodeB : GraphNode < typeof State > = ( state ) => {
console . log ( `Adding "B" to ${ state . aggregate } ` ) ;
return { aggregate : [ "B" ] };
};
const nodeC : GraphNode < typeof State > = ( state ) => {
console . log ( `Adding "C" to ${ state . aggregate } ` ) ;
return { aggregate : [ "C" ] };
};
const conditionalEdge : ConditionalEdgeRouter < typeof State , " b " | " c " > = ( state ) => {
// 在这里填写使用状态的任意逻辑
// 来决定下一个节点
return state . which as "b" | "c" ;
};
const graph = new StateGraph (State)
. addNode ( "a" , nodeA)
. addNode ( "b" , nodeB)
. addNode ( "c" , nodeC)
. addEdge (START , "a" )
. addEdge ( "b" , END)
. addEdge ( "c" , END)
. addConditionalEdges ( "a" , conditionalEdge)
. compile () ;
import * as fs from "node:fs/promises" ;
const drawableGraph = await graph . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;
const result = await graph . invoke ( { aggregate : [] } ) ;
console . log (result) ;
Adding "A" to []
Adding "C" to ['A']
{ aggregate: ['A', 'C'], which: 'c' }
你的条件边可以路由到多个目标节点。例如: const routeBcOrCd : ConditionalEdgeRouter < typeof State , " b " | " c " | " d " > = ( state ) => {
if (state . which === "cd" ) {
return [ "c" , "d" ] ;
}
return [ "b" , "c" ] ;
};
Map-Reduce 和 send API
LangGraph 支持使用 Send API 实现 map-reduce 和其他高级分支模式。下面是一个使用示例:
import { StateGraph , StateSchema , ReducedValue , GraphNode , START , END , Send } from "@langchain/langgraph" ;
import * as z from "zod" ;
const OverallState = new StateSchema ( {
topic : z . string () ,
subjects : z . array (z . string ()) ,
jokes : new ReducedValue (
z . array (z . string ()) . default ( () => []) ,
{ reducer : ( x , y ) => x . concat (y) }
) ,
bestSelectedJoke : z . string () ,
} ) ;
const generateTopics : GraphNode < typeof OverallState > = ( state ) => {
return { subjects : [ "lions" , "elephants" , "penguins" ] };
};
const generateJoke : GraphNode < typeof OverallState > = ( state ) => {
const jokeMap : Record < string , string > = {
lions : "Why don't lions like fast food? Because they can't catch it!" ,
elephants : "Why don't elephants use computers? They're afraid of the mouse!" ,
penguins : "Why don't penguins like talking to strangers at parties? Because they find it hard to break the ice."
};
return { jokes : [jokeMap[state . subject]] };
};
const continueToJokes : ConditionalEdgeRouter < typeof OverallState , " generateJoke " > = ( state ) => {
return state . subjects . map ( ( subject ) => new Send ( "generateJoke" , { subject } )) ;
};
const bestJoke : GraphNode < typeof OverallState > = ( state ) => {
return { bestSelectedJoke : "penguins" };
};
const graph = new StateGraph (OverallState)
. addNode ( "generateTopics" , generateTopics)
. addNode ( "generateJoke" , generateJoke)
. addNode ( "bestJoke" , bestJoke)
. addEdge (START , "generateTopics" )
. addConditionalEdges ( "generateTopics" , continueToJokes)
. addEdge ( "generateJoke" , "bestJoke" )
. addEdge ( "bestJoke" , END)
. compile () ;
import * as fs from "node:fs/promises" ;
const drawableGraph = await graph . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;
// 调用图:这里我们调用它来生成一个笑话列表
for await ( const step of await graph . stream ( { topic : "animals" } )) {
console . log (step) ;
}
{ generateTopics: { subjects: [ 'lions', 'elephants', 'penguins' ] } }
{ generateJoke: { jokes: [ "Why don't lions like fast food? Because they can't catch it!" ] } }
{ generateJoke: { jokes: [ "Why don't elephants use computers? They're afraid of the mouse!" ] } }
{ generateJoke: { jokes: [ "Why don't penguins like talking to strangers at parties? Because they find it hard to break the ice." ] } }
{ bestJoke: { bestSelectedJoke: 'penguins' } }
创建和控制循环
创建带循环的图时,我们需要一种终止执行的机制。最常见的做法是添加一个 conditional edge ,当达到某个终止条件时路由到 END 节点。
你也可以在调用或流式处理图时设置图的递归限制。递归限制设置的是图在抛出错误之前允许执行的 super-steps 数量。请阅读更多关于 recursion limit concept 的内容。
我们来看一个带循环的简单图,以便更好地理解这些机制如何工作。
如果想返回状态的最后一个值,而不是收到递归限制错误,请参阅 下一节 。
创建循环时,你可以包含一条指定终止条件的条件边:
const route : ConditionalEdgeRouter < typeof State , " b " > = ( state ) => {
if ( terminationCondition (state)) {
return END ;
} else {
return "b" ;
}
};
const graph = new StateGraph (State)
. addNode ( "a" , nodeA)
. addNode ( "b" , nodeB)
. addEdge (START , "a" )
. addConditionalEdges ( "a" , route)
. addEdge ( "b" , "a" )
. compile () ;
要控制递归限制,请在配置中指定 "recursionLimit"。这会抛出一个 GraphRecursionError,你可以捕获并处理它:
import { GraphRecursionError } from "@langchain/langgraph" ;
try {
await graph . invoke (inputs , { recursionLimit : 3 } ) ;
} catch (error) {
if (error instanceof GraphRecursionError ) {
console . log ( "递归错误" ) ;
}
}
让我们定义一个带有简单循环的图。请注意,我们使用条件边来实现终止条件。
import { StateGraph , StateSchema , ReducedValue , GraphNode , ConditionalEdgeRouter , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
// reducer 会让它只能追加
aggregate : new ReducedValue (
z . array (z . string ()) . default ( () => []) ,
{ reducer : ( x , y ) => x . concat (y) }
) ,
} ) ;
const nodeA : GraphNode < typeof State > = ( state ) => {
console . log ( `节点 A 看到 ${ state . aggregate } ` ) ;
return { aggregate : [ "A" ] };
};
const nodeB : GraphNode < typeof State > = ( state ) => {
console . log ( `节点 B 看到 ${ state . aggregate } ` ) ;
return { aggregate : [ "B" ] };
};
// 定义边
const route : ConditionalEdgeRouter < typeof State , " b " > = ( state ) => {
if (state . aggregate . length < 7 ) {
return "b" ;
} else {
return END ;
}
};
const graph = new StateGraph (State)
. addNode ( "a" , nodeA)
. addNode ( "b" , nodeB)
. addEdge (START , "a" )
. addConditionalEdges ( "a" , route)
. addEdge ( "b" , "a" )
. compile () ;
import * as fs from "node:fs/promises" ;
const drawableGraph = await graph . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;
这个架构类似于一个 ReAct agent ,其中节点 "a" 是一个调用工具的模型,节点 "b" 表示这些工具。
在我们的 route 条件边中,我们指定当状态中的 "aggregate" 列表超过某个长度阈值后就应该结束。
调用该图时,我们可以看到在达到终止条件之前,会在节点 "a" 和 "b" 之间交替执行。
const result = await graph . invoke ( { aggregate : [] } ) ;
console . log (result) ;
节点 A 看到 []
节点 B 看到 ['A']
节点 A 看到 ['A', 'B']
节点 B 看到 ['A', 'B', 'A']
节点 A 看到 ['A', 'B', 'A', 'B']
节点 B 看到 ['A', 'B', 'A', 'B', 'A']
节点 A 看到 ['A', 'B', 'A', 'B', 'A', 'B']
{ aggregate: ['A', 'B', 'A', 'B', 'A', 'B', 'A'] }
施加递归限制
在某些应用中,我们可能无法保证一定会达到给定的终止条件。在这些情况下,我们可以设置图的递归限制 。在经过给定数量的超级步 后,这会抛出一个 GraphRecursionError。然后我们可以捕获并处理这个异常:
import { GraphRecursionError } from "@langchain/langgraph" ;
try {
await graph . invoke ( { aggregate : [] }, { recursionLimit : 4 } ) ;
} catch (error) {
if (error instanceof GraphRecursionError ) {
console . log ( "递归错误" ) ;
}
}
节点 A 看到 []
节点 B 看到 ['A']
节点 A 看到 ['A', 'B']
节点 B 看到 ['A', 'B', 'A']
节点 A 看到 ['A', 'B', 'A', 'B']
递归错误
使用 Command 组合控制流和状态更新
将控制流(边)和状态更新(节点)组合起来会很有用。例如,你可能希望在同一个节点中既执行状态更新,又决定接下来要前往哪个节点。LangGraph 提供了一种方式:从节点函数返回一个 Command 对象:
import { Command } from "@langchain/langgraph" ;
const myNode = ( state : State ) : Command => {
return new Command ( {
// 状态更新
update : { foo : "bar" },
// 控制流
goto : "myOtherNode"
} ) ;
};
下面我们展示一个端到端示例。让我们创建一个包含 3 个节点的简单图:A、B 和 C。我们会先执行节点 A,然后根据节点 A 的输出决定接下来前往节点 B 还是节点 C。
import { StateGraph , StateSchema , GraphNode , Command , START } from "@langchain/langgraph" ;
import * as z from "zod" ;
// 定义图状态
const State = new StateSchema ( {
foo : z . string () ,
} ) ;
// 定义节点
const nodeA : GraphNode < typeof State , " nodeB " | " nodeC " > = ( state ) => {
console . log ( "调用了 A" ) ;
const value = Math . random () > 0.5 ? "b" : "c" ;
// 这是条件边函数的替代方案
const goto = value === "b" ? "nodeB" : "nodeC" ;
// 注意 Command 如何允许你同时更新图状态并路由到下一个节点
return new Command ( {
// 这是状态更新
update : { foo : value },
// 这是边的替代方案
goto ,
} ) ;
};
const nodeB : GraphNode < typeof State > = ( state ) => {
console . log ( "调用了 B" ) ;
return { foo : state . foo + "b" };
};
const nodeC : GraphNode < typeof State > = ( state ) => {
console . log ( "调用了 C" ) ;
return { foo : state . foo + "c" };
};
现在我们可以使用上面的节点创建 StateGraph。请注意,这个图没有用于路由的条件边 !这是因为控制流是在 nodeA 内部通过 Command 定义的。
const graph = new StateGraph (State)
. addNode ( "nodeA" , nodeA , {
ends : [ "nodeB" , "nodeC" ] ,
} )
. addNode ( "nodeB" , nodeB)
. addNode ( "nodeC" , nodeC)
. addEdge (START , "nodeA" )
. compile () ;
你可能已经注意到,我们使用 ends 来指定 nodeA 可以导航到哪些节点。这对于图渲染是必需的,它告诉 LangGraph nodeA 可以导航到 nodeB 和 nodeC。
import * as fs from "node:fs/promises" ;
const drawableGraph = await graph . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;
如果多次运行该图,我们会看到它会根据节点 A 中的随机选择走不同路径(A -> B 或 A -> C)。
const result = await graph . invoke ( { foo : "" } ) ;
console . log (result) ;
调用了 A
调用了 C
{ foo: 'cc' }
导航到父图中的节点
如果你正在使用子图 ,你可能希望从子图中的某个节点导航到另一个子图(也就是父图中的另一个节点)。为此,你可以在 Command 中指定 graph=Command.PARENT:
const myNode = ( state : State ) : Command => {
return new Command ( {
update : { foo : "bar" },
goto : "otherSubgraph" , // 其中 `otherSubgraph` 是父图中的一个节点
graph : Command . PARENT
} ) ;
};
让我们用上面的示例来演示这一点。我们会把上面示例中的 nodeA 改成一个单节点图,然后把它作为子图添加到父图中。
使用 Command.PARENT 进行状态更新
当你从子图节点向父图节点发送更新,并且更新的键同时存在于父图和子图的状态模式 中时,你必须 为父图状态中要更新的键定义一个 reducer 。请看下面的示例。
import { StateGraph , StateSchema , ReducedValue , GraphNode , Command , START } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
// 注意:我们在这里定义了一个 reducer
foo : new ReducedValue (
z . string () . default ( "" ) ,
{ reducer : ( x , y ) => x + y }
) ,
} ) ;
const nodeA : GraphNode < typeof State , " nodeB " | " nodeC " > = ( state ) => {
console . log ( "调用了 A" ) ;
const value = Math . random () > 0.5 ? "nodeB" : "nodeC" ;
// 注意 Command 如何允许你同时更新图状态并路由到下一个节点
return new Command ( {
update : { foo : "a" },
goto : value ,
// 这会告诉 LangGraph 导航到父图中的 nodeB 或 nodeC
// 注意:这会导航到相对于子图最近的父图
graph : Command . PARENT ,
} ) ;
};
const subgraph = new StateGraph (State)
. addNode ( "nodeA" , nodeA , { ends : [ "nodeB" , "nodeC" ] } )
. addEdge (START , "nodeA" )
. compile () ;
const nodeB : GraphNode < typeof State > = ( state ) => {
console . log ( "调用了 B" ) ;
// 注意:因为我们已经定义了 reducer,所以不需要手动追加
// 新字符到现有的 'foo' 值中。相反,reducer 会自动追加这些字符
// automatically
return { foo : "b" };
};
const nodeC : GraphNode < typeof State > = ( state ) => {
console . log ( "调用了 C" ) ;
return { foo : "c" };
};
const graph = new StateGraph (State)
. addNode ( "subgraph" , subgraph , { ends : [ "nodeB" , "nodeC" ] } )
. addNode ( "nodeB" , nodeB)
. addNode ( "nodeC" , nodeC)
. addEdge (START , "subgraph" )
. compile () ;
const result = await graph . invoke ( { foo : "" } ) ;
console . log (result) ;
调用了 A
调用了 C
{ foo: 'ac' }
在工具内部使用
一个常见用例是在工具内部更新图状态。例如,在客户支持应用中,你可能希望在对话开始时根据客户的账号或 ID 查询客户信息。要从工具更新图状态,你可以从工具返回 Command(update={"my_custom_key": "foo", "messages": [...]}):
import { tool } from "@langchain/core/tools" ;
import { Command } from "@langchain/langgraph" ;
import * as z from "zod" ;
const lookupUserInfo = tool (
async ( input , runtime ) => {
const userId = runtime . serverInfo ?. user ?. identity ;
const userInfo = getUserInfo (userId) ;
return new Command ( {
update : {
// 更新状态键
userInfo : userInfo ,
// 更新消息历史
messages : [ {
role : "tool" ,
content : "已成功查询用户信息" ,
tool_call_id : runtime . toolCall . id
} ]
}
} ) ;
},
{
name : "lookupUserInfo" ,
description : "使用此工具查询用户信息,以便更好地帮助他们解决问题。" ,
schema : z . object ( {} ) ,
}
) ;
当从工具返回 Command 时,你必须在 Command.update 中包含 messages(或任何用于消息历史的状态键),并且 messages 中的消息列表必须包含一个 ToolMessage。这是生成有效消息历史所必需的(LLM 提供商要求带有工具调用的 AI 消息后面必须跟随工具结果消息)。
如果你使用的是通过 Command 更新状态的工具,我们建议使用预构建的 ToolNode ,它会自动处理返回 Command 对象的工具,并将其传播到图状态。如果你正在编写一个调用工具的自定义节点,则需要手动将工具返回的 Command 对象作为节点更新进行传播。
可视化你的图
这里我们演示如何可视化你创建的图。
你可以可视化任意 Graph ,包括 StateGraph 。
让我们创建一个简单的示例图来演示可视化。
import { StateGraph , StateSchema , MessagesValue , ReducedValue , GraphNode , ConditionalEdgeRouter , START , END } from "@langchain/langgraph" ;
import * as z from "zod" ;
const State = new StateSchema ( {
messages : MessagesValue ,
value : new ReducedValue (
z . number () . default ( 0 ) ,
{ reducer : ( x , y ) => x + y }
) ,
} ) ;
const node1 : GraphNode < typeof State > = ( state ) => {
return { value : state . value + 1 };
};
const node2 : GraphNode < typeof State > = ( state ) => {
return { value : state . value * 2 };
};
const router : ConditionalEdgeRouter < typeof State , " node2 " > = ( state ) => {
if (state . value < 10 ) {
return "node2" ;
}
return END ;
};
const app = new StateGraph (State)
. addNode ( "node1" , node1)
. addNode ( "node2" , node2)
. addEdge (START , "node1" )
. addConditionalEdges ( "node1" , router)
. addEdge ( "node2" , "node1" )
. compile () ;
Mermaid
我们也可以将图类转换为 Mermaid 语法。
const drawableGraph = await app . getGraphAsync () ;
console . log (drawableGraph . drawMermaid ()) ;
%%{init: {'flowchart': {'curve': 'linear'}}}%%
graph TD;
tart__([<p>__start__</p>]):::first
e1(node1)
e2(node2)
nd__([<p>__end__</p>]):::last
tart__ --> node1;
e1 -.-> node2;
e1 -.-> __end__;
e2 --> node1;
ssDef default fill:#f2f0ff,line-height:1.2
ssDef first fill-opacity:0
ssDef last fill:#bfb6fc
PNG
如果愿意,我们也可以把 Graph 渲染成 .png。这里会使用 Mermaid.ink API 来生成图表。
import * as fs from "node:fs/promises" ;
const drawableGraph = await app . getGraphAsync () ;
const image = await drawableGraph . drawMermaidPng () ;
const imageBuffer = new Uint8Array ( await image . arrayBuffer ()) ;
await fs . writeFile ( "graph.png" , imageBuffer) ;