在构建复杂的 AI Agent 时,如何优雅地编排多步骤流程?LangGraph 提供了三个核心概念:StateNodeEdge。本文用大白话讲清楚它们如何协作,助你快速掌握 Agent 流程编排的精髓。

为什么你需要理解这三个概念?

想象你要开发一个客服机器人,它需要:接收用户问题 → 判断类型(技术/售后)→ 查询对应数据 → 生成回复。这个流程涉及分支、顺序和数据传递。在传统编程中,你可能会用一堆 if-else 和全局变量,但维护起来非常痛苦。LangGraph 的 State、Node、Edge 正是为了解决这类问题而生。

很多开发者初次接触 LangGraph 时,看到 StateNodeEdge 三个概念混在一起,完全分不清各自职责。本文就帮你彻底理清它们的协作关系。

另外,如果你熟悉 GoJava 中的状态机模式,会发现 LangGraph 的设计思路非常类似——只是将状态转移的逻辑交给了 Node 和 Edge 来更灵活地定义。

State:流程中的数据载体

State 是整个流程中流转的数据。它就像一个工厂流水线上的产品,每个工位(Node)加工后,产品的状态会发生变化,最终变成成品。

在 LangGraph 中,State 通常是一个字典(dict),包含了流程中所有需要传递的数据。例如,一个客服 Agent 的 State 可能包含:用户输入、意图分类结果、查询结果、最终回复等。

关键点:State 不是全局变量,而是消息传递的载体。每个 Node 接收上一个 Node 返回的 State,然后修改或扩展它,再传递给下一个 Node。这种设计避免了共享内存带来的竞态问题,也让流程更容易调试和追踪。

下面是一个典型的 State 定义示例:

from typing import TypedDict
class CustomerServiceState(TypedDict):
"""客服 Agent 的状态"""
user_question: str          # 用户问题
question_type: str          # 问题类型:tech / aftersales / unknown
retrieved_info: str         # 查询到的信息
response: str               # 最终回复
step_count: int            # 走了几步(用于调试)

TypeScript 开发者的视角来看,这类似于强类型接口定义,确保每个 Node 知道它能访问哪些字段。而在 JavaScript 中,你可能会用更灵活的对象,但推荐保持结构清晰。

⚙️ Node:流程中的处理单元

Node 代表流程中的一个处理步骤。每个 Node 接收当前 State,执行一些逻辑(比如调用 LLM、查询数据库、执行计算),然后返回更新后的 State。

Node 的设计原则是原子化:每个 Node 只做一件事。比如:

  • 意图识别 Node:分析用户输入,判断是技术问题还是售后问题
  • 查询 Node:根据意图,从文档库或订单系统获取数据
  • 回复生成 Node:基于查询结果,组织自然语言回复

这种设计让每个 Node 可以独立测试和复用。如果后续需要增加“退货处理”流程,只需新增一个 Node,而不需要修改现有逻辑。

Node 的函数签名通常如下:

def classify_question(state: CustomerServiceState) -> CustomerServiceState:
"""Node 1: 判断问题类型"""
question = state["user_question"]
# 简单规则判断(实际项目用 LLM)
if "怎么" in question or "如何" in question:
qtype = "tech"
elif "订单" in question or "快递" in question:
qtype = "aftersales"
else:
qtype = "unknown"
return {"question_type": qtype, "step_count": state["step_count"] + 1}

⚠️ 常见误区:Node 的执行顺序不是由代码中添加的顺序决定的!你完全可以先添加 Node F 再添加 Node A,但通过 Edge 控制让 A 先执行。这就像在 C++ 中,函数调用的顺序由调用者决定,而非定义顺序。

Edge:流程中的路由规则

Edge 决定了流程的走向。它不是简单的“从 A 到 B”,而是根据当前 State 动态决定下一步

Edge 可以是一条无条件边(始终从 A 到 B),也可以是一条条件边(根据 State 中的某个字段决定去 A1 还是 A2)。条件边的判断逻辑可以非常复杂:可以是简单的 if-else,也可以是调用 LLM 进行决策,甚至是一个规则引擎。

Edge 的定义示例:

from langgraph.graph import StateGraph
# 构建图
graph = StateGraph(CustomerServiceState)
# 添加 Node(工位)
graph.add_node("classify", classify_question)
graph.add_node("query_docs", query_technical_docs)
graph.add_node("query_orders", query_order_status)
graph.add_node("generate_response", generate_response)
# 添加 Edge(流转规则)
graph.add_edge("classify", "query_docs", condition=lambda s: s["question_type"] == "tech")
graph.add_edge("classify", "query_orders", condition=lambda s: s["question_type"] == "aftersales")
graph.add_edge("classify", "generate_response", condition=lambda s: s["question_type"] == "unknown")

实际应用:在客服 Agent 中,Edge 会根据意图识别结果路由到不同的处理 Node。如果用户说“我的订单还没到”,Edge 会判断为售后问题,路由到订单查询 Node;如果用户说“怎么配置 API 密钥”,则路由到技术文档 Node。

Java 中,这类似于策略模式或状态模式中的转移逻辑,但 LangGraph 的 Edge 更加灵活,因为条件函数可以访问完整的 State 上下文。

三者的协作流程

State、Node、Edge 的协作就像一个精心设计的流水线:

  1. 初始 State 包含用户输入等起始数据
  2. Node A 处理 State,更新其中的字段(比如添加意图标签)
  3. Edge 根据 State 中的意图标签,决定下一步去 Node B 还是 Node C
  4. Node B/C 继续处理,更新 State
  5. 重复步骤 3-4,直到流程结束

整个过程中,State 是唯一的数据源,Node 只读写 State,Edge 只读 State 并返回下一个 Node 名称。这种职责分离让流程非常清晰。

下图直观展示了这种协作关系:
在这里插入图片描述

完整代码示例:客服 Agent

下面是一个完整的客服 Agent 实现,展示了 State、Node、Edge 如何配合工作:

from typing import TypedDict
from langgraph.graph import StateGraph
# ========== 1. 定义 State ==========
class CustomerServiceState(TypedDict):
"""客服 Agent 状态"""
user_question: str
question_type: str  # tech / aftersales / unknown
retrieved_info: str
response: str
step_count: int
# ========== 2. 定义 Node ==========
def classify_question(state: CustomerServiceState) -> CustomerServiceState:
"""Node 1: 判断问题类型"""
question = state["user_question"]
if any(k in question for k in ["怎么", "如何", "设置", "配置"]):
qtype = "tech"
elif any(k in question for k in ["订单", "快递", "发货"]):
qtype = "aftersales"
else:
qtype = "unknown"
print(f"  [classify] 问题: {question} -> 类型: {qtype}")
return {"question_type": qtype, "step_count": state["step_count"] + 1}
def query_technical_docs(state: CustomerServiceState) -> CustomerServiceState:
"""Node 2: 查技术文档"""
print(f"  [query_docs] 正在查询技术文档...")
docs = "技术文档:根据您的问题,我们推荐您查看 XXX 配置文档。"
return {"retrieved_info": docs, "step_count": state["step_count"] + 1}
def query_order_status(state: CustomerServiceState) -> CustomerServiceState:
"""Node 3: 查订单状态"""
print(f"  [query_orders] 正在查询订单...")
order_info = "订单状态:您的订单已于昨天发货,预计明天送达。"
return {"retrieved_info": order_info, "step_count": state["step_count"] + 1}
def generate_response(state: CustomerServiceState) -> CustomerServiceState:
"""Node 4: 组织回复"""
info = state.get("retrieved_info", "")
if info:
response = f"您好!根据您的问题:{info}"
else:
response = "您好!您的问题已收到,我会尽力帮助您。"
print(f"  [generate] 最终回复: {response}")
return {"response": response, "step_count": state["step_count"] + 1}
# ========== 3. 构建 Graph ==========
graph = StateGraph(CustomerServiceState)
# 添加 Node
graph.add_node("classify", classify_question)
graph.add_node("query_docs", query_technical_docs)
graph.add_node("query_orders", query_order_status)
graph.add_node("generate_response", generate_response)
# 设置起点和终点
graph.set_entry_point("classify")
graph.set_finish_point("generate_response")
# 添加条件边(根据 question_type 决定下一步)
graph.add_conditional_edges(
"classify",
lambda s: s["question_type"],
{
"tech": "query_docs",
"aftersales": "query_orders",
"unknown": "generate_response"
}
)
# query_docs 和 query_orders 之后都去 generate_response
graph.add_edge("query_docs", "generate_response")
graph.add_edge("query_orders", "generate_response")
# 编译
app = graph.compile()
# ========== 4. 运行 ==========
if __name__ == "__main__":
print("=== 测试技术问题 ===")
result = app.invoke({
"user_question": "怎么配置 API 密钥?",
"question_type": "",
"retrieved_info": "",
"response": "",
"step_count": 0
})
print(f"最终状态: question_type={result['question_type']}, response={result['response']}")
print()
print("=== 测试售后问题 ===")
result = app.invoke({
"user_question": "我的订单什么时候发货?",
"question_type": "",
"retrieved_info": "",
"response": "",
"step_count": 0
})
print(f"最终状态: question_type={result['question_type']}, response={result['response']}")

运行结果如下:

=== 测试技术问题 ===
  [classify] 问题: 怎么配置 API 密钥? -> 类型: tech
  [query_docs] 正在查询技术文档...
  [generate] 最终回复: 您好!根据您的问题:技术文档:根据您的问题,我们推荐您查看 XXX 配置文档。
最终状态: question_type=tech, response=您好!根据您的问题:技术文档:根据您的问题,我们推荐您查看 XXX 配置文档。
=== 测试售后问题 ===
  [classify] 问题: 我的订单什么时候发货? -> 类型: aftersales
  [query_orders] 正在查询订单...
  [generate] 最终回复: 您好!根据您的问题:订单状态:您的订单已于昨天发货,预计明天送达。
最终状态: question_type=aftersales, response=您好!根据您的问题:订单状态:您的订单已于昨天发货,预计明天送达。

生成的流程图:
在这里插入图片描述

这个示例中,State 包含了用户输入、意图、查询结果和回复。Node 依次处理:意图识别 → 数据查询 → 回复生成。Edge 根据意图字段决定查询路径。

[AFFILIATE_SLOT_1]

✅ 常见误区与最佳实践

误区 1:State 是全局变量

❌ 错误理解:所有 Node 共享同一个 State 实例。
✅ 正解:State 是不可变数据流,每个 Node 接收上一个 Node 返回的新 State,而不是修改全局变量。这保证了流程的可回溯性和并发安全性。

误区 2:Node 顺序 = 代码添加顺序

❌ 错误理解:先添加的 Node 先执行。
✅ 正解:执行顺序完全由 Edge 定义。你可以用条件边实现循环、分支等复杂流程。

误区 3:Edge 只能是 if-else

❌ 错误理解:Edge 的条件判断很简单。
✅ 正解:Edge 的条件函数可以是任意逻辑,包括调用 LLM、执行 SQL 查询、甚至发起 HTTP 请求。只要返回一个合法的 Node 名称即可。

最佳实践清单

  • State 要精简:只传递必要数据,避免过大的 State 影响性能。例如,不要在 State 中存储完整的文档内容,只存文档 ID 或摘要。
  • Node 要原子化:每个 Node 只做一件事,便于调试和复用。如果一个 Node 既查数据库又调用 LLM,拆分成两个 Node。
  • Edge 逻辑要清晰:condition 函数最好单独定义,不要写复杂的 lambda。给每个 Edge 起个有意义的名字(如 route_by_intent)。
  • 添加 step_count:在 State 中维护一个计数器,防止死循环。例如,设置最大步数为 10,超时自动终止。

项目实战与延伸

State、Node、Edge 的组合可以应对多种场景:

  • 客服机器人:根据问题类型走不同流程,支持多轮对话
  • 数据分析 Agent:清洗 → 分析 → 可视化,每个阶段是一个 Node
  • 代码审查 Agent:接收代码 → 静态分析 → 生成报告,Edge 可以根据代码复杂度决定审查深度

如果你熟悉 Go 的并发模型,可以将每个 Node 视为一个 goroutine,Edge 作为 channel 来路由数据。这种类比能帮助你快速理解 LangGraph 的设计哲学。

[AFFILIATE_SLOT_2]

总结与下篇预告

核心要点回顾

  • State = 流程中传递的数据,像流水线上的产品
  • Node = 处理步骤,接收 State、返回新的 State
  • Edge = 流转规则,根据当前 State 决定下一步走哪个 Node
  • 三者配合:Node 处理数据,Edge 控制流向,State 承载数据

理解这三者后,你就能像搭积木一样构建复杂的 Agent 流程。无论是用 TypeScript 还是 JavaScript,这套机制都保持一致。

下篇预告:我们将探讨《Agent 为什么需要 Checkpoint?》,理解当 Agent 执行中断时(如断电、超时),Checkpoint 如何帮助恢复状态,避免从头开始。这对于生产环境中的长时间运行任务至关重要。