# 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 章节

相关推荐
redfred17 分钟前
Upscayl 使用教程:开源 AI 图片放大器 模糊图片变清晰 2x/4x 放大 本地处理 免费
人工智能·开源
一切皆是因缘际会18 分钟前
人工智能底层架构缺陷
人工智能·架构
木圭的AI时代指南19 分钟前
我的8G显存电脑实测本地部署qwen3.8-27B记录
人工智能·ai·语言模型·电脑
Ai-_Man21 分钟前
我是想导出豆包智能体的聊天记录,能分开用户和智能体对话吗?记录有点太多了
人工智能·ai·小程序
zhougl99631 分钟前
Elasticsearch 7.x vs 8.x
大数据·elasticsearch·jenkins
qq_252941316833 分钟前
无人机目标检测数据集(UAV Detection Dataset)| 无人机检测 反无人机 低空安防5001期
人工智能·yolo·目标检测·计算机视觉·视觉检测·无人机·反无人机
Rocky Ding*33 分钟前
【三年面试五年模拟】2026-08-16_拼多多_笔试题与题解全解析
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·拼多多
饼饼学习空间智能37 分钟前
具身智能实训平台有哪些供应商?2026年机器人教学与仿真平台选型指南
运维·人工智能
国科安芯40 分钟前
ASC8T245S:把 8 位并行总线稳稳“跨过“电压域的双电源收发器
网络·分布式·单片机·嵌入式硬件·fpga开发·架构