# LangGraph 核心架构入门:State、Node、Edge 与 Reducer 的工程理解

LangGraph 核心架构入门:State、Node、Edge 与 Reducer 的工程理解


导读:LangGraph 把 Agent 工作流抽象成一张有状态图。三个核心概念 State(共享数据快照)、Node(执行逻辑)和 Edge(流程路由)贯穿始终。本文从构建器到编译执行,从普通边到条件边,再到 Reducer 合并规则,配合一个完整的「智能客服意图分类」案例,一次性讲透 LangGraph 核心架构。


适合读者

  • 刚接触 LangGraph,需要建立整体认知的新手
  • 已经用过 LangChain 但不知道 LangGraph 解决什么问题的开发者
  • 准备面试中回答「LangGraph 的核心组件是什么」的求职者

阅读收益

  • 理解 StateGraph 构建 -> compile -> invoke 的完整生命周期
  • 掌握 Node 函数签名、异步写法、显式命名规范
  • 学会普通 Edge 和 Conditional Edge 的用法
  • 理解 Reducer 的本质:不是语法糖,而是并行节点的合并规则
  • 获得一份可直接运行的「智能客服意图分类」完整代码

目录

  1. [LangGraph 为什么用图来建模 Agent](#LangGraph 为什么用图来建模 Agent)
  2. [核心三要素:State + Node + Edge](#核心三要素:State + Node + Edge)
  3. [StateGraph 构建与编译](#StateGraph 构建与编译)
  4. State:共享快照
  5. Node:执行单元
  6. Edge:流程控制
  7. Reducer:合并规则
  8. 完整案例:智能客服意图分类
  9. 踩坑清单
  10. 面试速答版
  11. 总结与延伸
  12. 思考题

1. LangGraph 为什么用图来建模 Agent

传统 LangChain 的 LCEL(LangChain Expression Language)是管道式链式调用:

复制代码
prompt -> model -> output_parser

适合简单的问答,但 Agent 需要:

  • 循环(模型生成 tool_call -> 执行工具 -> 回到模型)
  • 条件分支(根据意图走不同路径)
  • 状态持久化(中断后恢复、多轮对话)
  • 并行执行(多个工具同时调用)

LangGraph 把 Agent 建模成有向图

复制代码
START -> classify_intent -> [query_order | query_logistics | human_handoff] -> final_answer -> END

这不是架构上的炫技,而是让 Agent 的行为从「黑箱链式调用」变成「可观测、可调试、可中断的状态图」。


2. 核心三要素:State + Node + Edge

概念 角色 类比
State 共享数据快照 图运行时的「全局黑板」
Node 执行逻辑 每个节点是具体干活的函数
Edge 流程控制 决定下一步走哪个节点

一句话:Node 负责做事,Edge 负责决定下一步,State 负责保存过程中的数据。

再加上两个基础设施:

概念 角色
Reducer 多个节点写同一个字段时的合并规则
Checkpointer 保存图状态快照,支持中断与恢复

3. StateGraph 构建与编译

python 复制代码
from langgraph.graph import StateGraph
from typing_extensions import TypedDict


class State(TypedDict):
    question: str
    answer: str


builder = StateGraph(State)       # ① 构建器:声明 State 结构
builder.add_node("analyze", analyze_fn)  # ② 添加节点
builder.add_edge(START, "analyze")       # ③ 添加边
builder.add_edge("analyze", END)

graph = builder.compile()          # ④ 编译:生成可执行图
result = graph.invoke({"question": "你好"})  # ⑤ 执行

编译的意义:StateGraph 只是构建器,不能直接执行。compile() 后会验证节点和边的完整性,生成 CompiledGraph。如果边连接了不存在的节点,编译时报错。


4. State:共享快照

4.1 TypedDict 定义

python 复制代码
from typing_extensions import TypedDict


class State(TypedDict):
    question: str
    answer: str
    intent: str

State 是 TypeDict,不是普通 dict。编译时 LangGraph 会根据字段名检查节点返回的更新是否合法。

4.2 节点如何读写 State

节点函数签名:

python 复制代码
def analyze(state: State):
    """读取 question,写入 intent"""
    intent = classify(state["question"])
    return {"intent": intent}   # 只返回更新,不是完整 State

关键:节点返回的是 State 的局部更新,LangGraph 自动合并。不要返回完整 State 字典,也不要直接原地修改 state。

4.3 State 不等于 messages

初学者常把 State 理解为聊天消息列表。messages 只是 State 里的一个字段:

python 复制代码
class AgentState(TypedDict):
    messages: list          # 聊天历史只是 State 的一部分
    order_id: str           # 业务数据也要存
    intent: str             # 意图分类结果
    risk_level: str         # 风险评估

真实项目中,State 应该保存结构化业务数据,而不只是聊天历史。

4.4 State 设计原则

  1. 字段语义清晰:不要所有东西都塞到 messages 或 metadata
  2. 只放流程需要的数据:不要把无关数据放进全局 state
  3. 中间结果结构化order_status: "shipped" 比自然语言描述更容易判断
  4. 注意并行更新:多个节点会写同一个字段时,要定义 reducer
  5. 区分 runtime context:user_id、trace_id 等不一定要持久化的上下文,用 config

思考题 1

假设你在设计一个「电商退货 Agent」,State 里应该包含哪些字段?哪些字段需要 Reducer?哪些字段是运行时 context 不需要进 State?


5. Node:执行单元

Node 可以是普通 Python 函数、异步函数、LLM 调用、Tool 调用、子图或 Runnable。

5.1 最简单的 Node

python 复制代码
def greet(state: State):
    return {"answer": f"你好,{state['question']}"}

5.2 显式命名 vs 省略命名

python 复制代码
# 显式命名:生产项目推荐
builder.add_node("intent_classifier", classify_intent)

# 省略命名:默认用函数名
builder.add_node(classify_intent)  # 节点名 = classify_intent

显式命名让图结构更清晰,日志和 tracing 更容易看。

5.3 异步 Node

python 复制代码
async def async_fetch(state: State):
    result = await aiohttp.get(state["url"])
    return {"data": result}

调用异步图:

python 复制代码
result = await graph.ainvoke({"url": "https://api.example.com"})

5.4 Node 设计原则

  • 职责单一:一个节点只做一件事
  • 输入输出清晰:函数签名明确 state 类型
  • 不偷偷修改外部全局状态:副作用显式通过 state 传递
  • 返回结构化更新 :不要写 return state
  • 出错时容易定位:节点名就是定位线索

不要写成这样

python 复制代码
def bad_node(state):
    state["x"] = 1           # 原地修改!LangGraph 无法追踪
    call_external_api()      # 副作用隐藏在函数里
    return state             # 返回完整 state,冗余

更好的拆法

python 复制代码
def prepare(state):
    return {"x": 1}

def call_api(state):
    result = call_external_api(state["params"])
    return {"api_result": result}

6. Edge:流程控制

6.1 普通 Edge:固定流转

python 复制代码
from langgraph.constants import START, END

builder.add_edge(START, "classify")     # 入口
builder.add_edge("classify", "answer")  # 顺序执行
builder.add_edge("answer", END)         # 结束

START 和 END 是特殊节点,必须指定入口,否则编译报错。

6.2 Conditional Edge:条件分支

根据 State 的值动态选择下一步:

python 复制代码
def route_by_intent(state: State) -> str:
    intent = state["intent"]
    if intent == "order":
        return "query_order"
    elif intent == "logistics":
        return "query_logistics"
    else:
        return "human_handoff"

# conditional edge:classify 节点之后,根据 state 决定走向
builder.add_conditional_edges("classify", route_by_intent)

返回的字符串必须是已添加的节点名,或者 END。

6.3 条件边的可视化理解

复制代码
START -> classify_intent
            |
            |-- intent="order" -> query_order -> final_answer -> END
            |-- intent="logistics" -> query_logistics -> final_answer -> END
            |-- intent="other" -> human_handoff -> END

LangGraph 的条件边本质是一个路由函数,在节点执行完后调用,根据当前 State 返回下一个节点名。


7. Reducer:合并规则

7.1 为什么需要 Reducer

假设有两个节点并行执行,都往 audit_log 里写数据:

python 复制代码
node_a returns {"audit_log": ["A 完成"]}
node_b returns {"audit_log": ["B 完成"]}

如果没有 reducer,LangGraph 不知道 audit_log 最终应该是 ["A 完成", "B 完成"] 还是只保留最后一个 ["B 完成"]

7.2 使用 Annotated 定义 Reducer

python 复制代码
from typing import Annotated
import operator

class State(TypedDict):
    audit_log: Annotated[list[str], operator.add]

这表示:当多个节点都返回 audit_log 时,用 operator.add 把列表拼起来。

7.3 Reducer 函数签名

Reducer 本质上是一个函数。接收旧值和新值,返回合并后的值:

python 复制代码
def merge_unique(old: list, new: list) -> list:
    return old + [x for x in new if x not in old]

class State(TypedDict):
    tags: Annotated[list[str], merge_unique]

7.4 常见 Reducer 写法

字段类型 推荐 reducer 说明
liststr operator.add 列表拼接
messages add_messages 聊天消息合并
set 自定义 union 集合合并
dict 自定义 merge 字典合并
计数器 自定义 sum 数字累加

7.5 Reducer 不是语法糖

很多初学者把 Reducer 当成「让代码更优雅的装饰器」。实际上:

  • 没有 Reducer 时,多个节点写同一字段,LangGraph 可能只保留最后一个值
  • 有 Reducer 时,并行节点的更新被正确合并
  • 自定义 Reducer 可以实现去重、求和、字典合并等复杂逻辑

Reducer 是 LangGraph 支持并行节点的基础设施。


8. 完整案例:智能客服意图分类

流程设计:

复制代码
START -> classify_intent
            |
            |-- intent="order" -> query_order -> final_answer -> END
            |-- intent="logistics" -> query_logistics -> final_answer -> END
            |-- intent="other" -> human_handoff -> END
python 复制代码
from typing import TypedDict, Literal, Annotated
from langgraph.graph import StateGraph
from langgraph.constants import START, END
import operator


# ========== State 定义 ==========
class State(TypedDict):
    question: str
    intent: Literal["order", "logistics", "other"]
    order_id: str
    answer: str
    audit_log: Annotated[list[str], operator.add]


# ========== Node 函数 ==========
def classify_intent(state: State):
    """节点1:意图分类"""
    q = state["question"].lower()
    if "订单" in q or "买" in q:
        intent = "order"
    elif "物流" in q or "快递" in q or "发货" in q:
        intent = "logistics"
    else:
        intent = "other"
    return {
        "intent": intent,
        "audit_log": [f"意图分类结果: {intent}"]
    }


def query_order(state: State):
    """节点2a:查询订单"""
    return {
        "answer": "订单 1001 已发货,预计明天送达。",
        "audit_log": ["执行订单查询"]
    }


def query_logistics(state: State):
    """节点2b:查询物流"""
    return {
        "answer": "顺丰快递,单号 SF1234567890,当前位置:北京转运中心。",
        "audit_log": ["执行物流查询"]
    }


def human_handoff(state: State):
    """节点2c:转人工"""
    return {
        "answer": "已为您转接人工客服,请稍候。",
        "audit_log": ["转人工处理"]
    }


def final_answer(state: State):
    """节点3:生成最终回答"""
    return {
        "answer": f"【客服回复】{state['answer']}",
        "audit_log": ["生成最终回答"]
    }


# ========== 路由函数 ==========
def route_by_intent(state: State) -> str:
    """根据意图选择分支"""
    intent = state["intent"]
    if intent == "order":
        return "query_order"
    elif intent == "logistics":
        return "query_logistics"
    else:
        return "human_handoff"


# ========== 构建图 ==========
builder = StateGraph(State)

builder.add_node("classify_intent", classify_intent)
builder.add_node("query_order", query_order)
builder.add_node("query_logistics", query_logistics)
builder.add_node("human_handoff", human_handoff)
builder.add_node("final_answer", final_answer)

builder.add_edge(START, "classify_intent")
builder.add_conditional_edges("classify_intent", route_by_intent)
builder.add_edge("query_order", "final_answer")
builder.add_edge("query_logistics", "final_answer")
builder.add_edge("human_handoff", "final_answer")
builder.add_edge("final_answer", END)

graph = builder.compile()

# ========== 运行 ==========
result = graph.invoke({"question": "我的订单什么时候到?"})
print(result["answer"])
print(result["audit_log"])

# 输出:
# 【客服回复】订单 1001 已发货,预计明天送达。
# ['意图分类结果: order', '执行订单查询', '生成最终回答']

思考题 2

上面的案例中,如果用户说「我想退货」,当前会被分类为 "other" 转人工。如果要支持退货流程,需要修改哪些部分?退货流程是否应该有独立的子图?


9. 踩坑清单

序号 坑点 现象 正确做法
1 忘记 compile() AttributeError: 'StateGraph' object has no attribute 'invoke' 构建完后必须调用 compile()
2 节点返回完整 state 编译时报类型错误 只返回需要更新的字段字典
3 原地修改 state LangGraph 无法追踪变化 返回新字典,不要修改入参
4 忘记指定 START 编译报错:没有入口节点 必须 add_edge(START, "...")
5 条件边返回非法节点名 ValueError: Node ... not found 返回的字符串必须是已添加的节点名
6 并行节点没有 reducer 后执行的覆盖先执行的 用 Annotated..., operator.add 定义合并规则
7 节点里藏副作用 调试困难,复现不了 副作用显式通过 state 传递或拆成独立节点
8 节点名和函数名不一致 日志里看不懂 显式命名,让节点名表达业务含义

10. 面试速答版

Q1:LangGraph 的核心三要素是什么?

A:State(共享数据快照)、Node(执行逻辑)、Edge(流程控制)。Node 负责做事,Edge 负责决定下一步,State 负责保存过程中的数据。

Q2:StateGraph 和 CompiledGraph 的区别?

A:StateGraph 是构建器,只声明结构和连接关系,不能直接执行。compile() 后得到 CompiledGraph,验证完整性并生成可执行的图对象。

Q3:Conditional Edge 怎么写?

A:定义一个路由函数,接收 state 返回下一个节点名的字符串。用 builder.add_conditional_edges("node_name", route_fn) 添加。

Q4:Reducer 的作用是什么?

A:当多个并行节点都更新同一个字段时,Reducer 定义如何合并这些更新。没有 Reducer 时可能只保留最后一个值。

Q5:Node 为什么不能原地修改 state?

A:LangGraph 需要追踪每次更新的来源,用于 checkpoint、streaming 和调试。原地修改破坏了不可变性,导致无法正确追踪状态变化。


11. 总结与延伸

本章核心要点

  • LangGraph 用有向图建模 Agent 工作流,解决循环、分支、持久化、并行四大问题
  • StateGraph -> compile() -> invoke() 是标准生命周期
  • State 是共享快照,节点返回局部更新,LangGraph 自动合并
  • Node 职责单一,不显式命名,不原地修改 state
  • Edge 分普通边(固定流转)和条件边(动态路由)
  • Reducer 是并行节点的基础设施,不是语法糖

延伸思考

  • 并行节点:LangGraph 支持多个节点同时执行,需要 Reducer 合并结果
  • Checkpointer:为图配置 checkpointer 后,支持中断、恢复和多轮对话
  • Subgraph:复杂流程可以拆成子图,提高复用性(见第十章)
  • Streaming:stream() 和 astream() 可以实时获取图的执行进度(见第八章)

12. 思考题

  1. State 设计:设计一个「电商退货 Agent」的 State 结构,包含退货原因、订单信息、审批状态等字段。哪些字段需要 Reducer?

  2. 条件边路由:如果用户输入同时包含「订单」和「物流」关键词,当前案例会走订单分支。如何设计更智能的路由逻辑?

  3. 并行查询:假设查询订单和查询物流可以并行执行(不互相依赖),如何修改图结构让它们并行?需要注意什么?

  4. 错误处理 :如果 query_order 节点查询外部 API 失败,图应该如何处理?是走到错误处理节点,还是让条件边支持异常路由?

  5. 状态持久化 :把当前的 InMemorySaver 换成 Postgres 持久化存储后,用户隔一天再问「刚才的订单查得怎么样了」,系统该如何恢复之前的对话状态?


如果本文对你有帮助,欢迎点赞 + 收藏 + 关注!有任何问题欢迎在评论区交流。

参考资料:LangGraph 官方文档 Core Concepts 章节

相关推荐
RoboWizard10 分钟前
三星和金士顿内存条哪个更适合游戏超频
大数据·人工智能
深圳市恒星物联科技有限公司17 分钟前
轻量MCU设备通过OpenHarmony兼容性测评的全流程关键要点与实战踩坑经验
大数据·人工智能·物联网·鸿蒙
TDengine (老段)43 分钟前
TDengine 常见问题 TOP3
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
AI闲人1 小时前
企业 AI 最大的问题,不是数据不足,而是数据没有业务语义
人工智能·数字化·企业ai落地
米小虾1 小时前
一周 AI 观察(9.14–9.18):Anthropic 自曝"AI 写了我四分之一的研发",于是这一周所有人都在买同一样东西
人工智能
米小虾1 小时前
加了 20 条示例反而变差:你的 few-shot 提升,可能只是 prompt 变长的功劳
人工智能·llm
东方-教育技术博主1 小时前
自进化智能体编码软件用法
人工智能
运维@小兵1 小时前
LangChain系列——中间件(Middleware)
langchain·langchain中间件
jimmyleeee1 小时前
大模型安全之十八:AI常见漏洞类别与缓解策略
人工智能·安全
火山引擎开发者社区2 小时前
多行业专家招募|参与线上访谈拿 2000元/h 现金报酬!
人工智能