# LangGraph 核心架构详解:State、Node、Edge 的实战编排

LangGraph 核心架构详解:State、Node、Edge 的实战编排

导读:LangGraph 用"有状态图"重新定义了 Agent 的编排方式。State 是数据,Node 是动作,Edge 是流程。本文从项目真实代码出发,完整讲解 TypedDict 状态设计、节点编写规范、条件路由边、compile 编译过程,以及 6 个常见图结构(线性、分支、循环、并行、子图、多 Agent)。读完可以直接落地到自己的 LangGraph 项目中。

适合读者

  • 已经会用 LangChain 但还没接触过 LangGraph 的开发者
  • 被传统 AgentExecutor 循环困扰的工程师
  • 需要构建有状态、可持久化、可恢复 Agent 系统的架构师
  • 想了解为什么 “State + Node + Edge” 比单纯 prompt 工程更强大的人

阅读收益

  • 理解 LangGraph 相比传统 AgentExecutor 的 5 大核心改进
  • 掌握 TypedDict 定义 State 的规范写法
  • 学会设计职责单一的 Node(函数/LLM/工具/子图)
  • 理解普通 Edge vs Conditional Edge 的区别和适用场景
  • 拿到完整客服 Agent 的图结构代码(意图识别 → 路由 → 查询 → 回复)

目录

  1. 为什么需要 LangGraph
  2. State:图的共享状态
  3. Node:图中的执行单元
  4. Edge:节点之间的连接
  5. compile:从 Builder 到可执行图
  6. 常见图结构速查
  7. 完整案例:客服 Agent 工作流
  8. 踩坑清单:LangGraph 的 10 个常见错误
  9. 总结
  10. 文末互动

1. 为什么需要 LangGraph

1.1 从 LLM 到 Agent

最早的 LLM 应用是单向的:

用户输入 → Prompt → LLM → 模型输出

但当用户说"帮我查一下订单状态,如果还没发货就提醒客服加急"时,需要多步决策:

理解意图 → 查询订单系统 → 判断状态 → 必要时调用客服系统 → 生成回复 → 记录日志

这就是 Agent 场景,核心是 Think → Act → Observe 循环。

1.2 传统 AgentExecutor 的 5 大局限

问题说明
控制流不清晰下一步调用什么工具、是否继续、何时结束,都由模型决定,难以保证执行顺序
状态管理混乱所有内容塞进 messages,越来越长,token 成本高,中间变量难以验证
难以恢复服务重启后丢失上下文,无法从中断位置继续
人工介入不自然需要人工审批时只能临时写 if/else,无法暂停→保存→恢复
多 Agent 协作困难谁先执行、谁负责审查、如何共享状态,缺乏标准机制

1.3 LangGraph 的核心思想

LangGraph = Lang + Graph

把 Agent 应用建模成一个有状态的图。

图由三部分组成:

概念含义
Node执行单元(函数、LLM、工具、Agent、子图)
Edge节点之间的连接,决定执行顺序
State图运行过程中的共享状态

LangGraph 解决的不是"怎么写 prompt",而是 Agent 的编排问题

  • 执行流程怎么控制
  • 状态怎么更新和持久化
  • 中断后怎么恢复
  • 人工怎么介入
  • 多 Agent 怎么协作

2. State:图的共享状态

2.1 使用 TypedDict 定义 State

from typing_extensions import TypedDict

class State(TypedDict):
    user_input: str   # 用户原始输入
    intent: str       # 识别出的意图
    answer: str       # 最终回复

这表示图的状态包含三个字段:

{
    "user_input": "...",
    "intent": "...",
    "answer": "..."
}

2.2 Node 如何读写 State

读取:

def classify_intent(state: State):
    text = state["user_input"]
    if "订单" in text:
        return {"intent": "order"}
    return {"intent": "general"}

写入:

def answer_node(state: State):
    return {
        "answer": f"当前意图是:{state['intent']}"
    }

关键:节点返回的是 Partial[State],也就是 state 的一部分,不是完整的 state。

2.3 State ≠ messages

很多初学者把 state 理解成聊天消息,这是不完整的。

真实项目中 state 应该保存结构化业务数据:

class AgentState(TypedDict):
    messages: list      # 聊天历史(只是其中一个字段)
    user_id: str
    intent: str
    order_id: str
    tool_result: dict
    final_answer: str

2.4 State 设计原则

  1. 字段语义清晰——不要所有东西都塞到 messages 或 metadata
  2. 只放流程需要的数据——不要把无关数据放进全局 state
  3. 中间结果结构化——order_status 比自然语言"订单还没发货"更容易判断
  4. 注意并行更新——多个节点写同一个字段时,要定义 reducer
  5. 区分 runtime context 和 state——user_idtrace_id 用 context,不放入 state

2.5 可选字段:total=False

class State(TypedDict, total=False):
    user_input: str
    intent: str
    answer: str

total=False 表示 invoke 时可以不传递所有字段。但使用时要注意:

# 如果某个字段可能不存在,用 get 避免 KeyError
intent = state.get("intent", "general")

3. Node:图中的执行单元

3.1 Node 可以是这些

  • 普通 Python 函数
  • 异步函数
  • LLM 调用
  • Tool 调用
  • Agent
  • Subgraph(子图)
  • Runnable

3.2 最简单的 Node

def greet(state: State):
    return {
        "answer": f"你好,你说的是:{state['user_input']}"
    }

节点函数签名:

State -> Partial[State]
输入当前状态 → 输出状态更新

3.3 注册 Node

# 显式命名(推荐生产环境)
builder.add_node("classify_intent", classify_intent)

# 省略名称(用函数名)
builder.add_node(greet)

建议生产项目显式命名,图结构更清晰,日志和 tracing 也更容易看。

3.4 Node 中调用 LLM

from langchain_core.messages import HumanMessage

def llm_node(state: State):
    response = model.invoke([
        HumanMessage(content=state["user_input"])
    ])
    return {
        "answer": response.content
    }

LangGraph 不要求每个 node 都调用 LLM。有些 node 只是普通业务逻辑:

def extract_order_id(state: State):
    # 正则、规则、数据库查询
    return {"order_id": "10086"}

3.5 异步 Node

async def async_node(state: State):
    response = await model.ainvoke(state["user_input"])
    return {"answer": response.content}

调用异步图:

result = await graph.ainvoke(inputs)
# 或
async for chunk in graph.astream(inputs):
    print(chunk)

3.6 Node 设计原则

一个好的 node 应该:

  • 职责单一
  • 输入输出清晰
  • 不偷偷修改外部全局状态
  • 返回结构化更新
  • 出错时容易定位

不要写成

def giant_agent_node(state):
    # 识别意图
    # 查数据库
    # 调工具
    # 生成回答
    # 写日志
    # 审批判断
    ...

更好的拆法

classify_intent → extract_order_id → query_order → decide_need_human → generate_answer

4. Edge:节点之间的连接

4.1 普通 Edge

builder.add_edge("classify_intent", "query_order")

含义:classify_intent 执行完成后,进入 query_order

完整线性流程:

builder.add_edge(START, "classify_intent")
builder.add_edge("classify_intent", "query_order")
builder.add_edge("query_order", "final_answer")
builder.add_edge("final_answer", END)

对应:START → classify_intent → query_order → final_answer → END

4.2 START 和 END 节点

from langgraph.graph import START, END

# START:图的入口,必须指定
builder.add_edge(START, "classify_intent")

# END:图的结束,显式连接更清晰
builder.add_edge("final_answer", END)

4.3 Conditional Edge(条件路由)

def route_by_intent(state: State):
    if state["intent"] == "order":
        return "query_order"
    if state["intent"] == "logistics":
        return "query_logistics"
    return "general_answer"

builder.add_conditional_edges(
    "classify_intent",          # 源节点
    route_by_intent,            # 路由函数
    {
        "query_order": "query_order",
        "query_logistics": "query_logistics",
        "general_answer": "general_answer",
    },
)

执行逻辑:

classify_intent 执行完成
      ↓
调用 route_by_intent(state)
      ↓
根据返回值选择下一个节点

4.4 Conditional Edge 的简化写法

如果路由函数返回的字符串和节点名一样:

builder.add_conditional_edges("classify_intent", route_by_intent)

4.5 Edge 设计原则

  • Edge 应该表达流程关系
  • 不要把大量业务逻辑藏在 route 函数里
  • 复杂逻辑放 node 中,route 只做轻量判断

推荐

def route_by_intent(state):
    return state["intent"]  # 直接返回意图,简洁

不推荐

def route_by_intent(state):
    # 查数据库
    # 调模型
    # 写日志
    # 做审批
    # 再决定路由
    ...

5. compile:从 Builder 到可执行图

5.1 compile 的作用

StateGraph 只是构建器,不能直接执行。必须调用:

graph = builder.compile()

compile 会做结构检查:

  • 是否有入口(START)
  • 节点名是否存在
  • 是否有孤立节点
  • 图结构是否合法

5.2 compile 后调用

# 同步执行
result = graph.invoke(inputs)

# 异步执行
result = await graph.ainvoke(inputs)

# 流式执行
for chunk in graph.stream(inputs, stream_mode="updates"):
    print(chunk)

# 获取历史状态
graph.get_state(thread_id)
graph.get_state_history(thread_id)

5.3 配置 Checkpointer(持久化)

from langgraph.checkpoint.memory import InMemorySaver

graph = builder.compile(
    checkpointer=InMemorySaver()  # 生产环境换 Redis / Postgres
)

有了 checkpointer:

  • 保存执行状态
  • 查看历史状态
  • 中断后恢复
  • 支持多轮对话
  • 支持 Human-in-the-Loop

6. 常见图结构速查

6.1 线性图

START → A → B → C → END

适合固定流程:文档处理、固定审批、流水线。

6.2 条件分支图

START → A
         ├→ B
         └→ C

适合意图识别、分类路由、不同业务路径。

6.3 循环图

A → B → C
     ↑   ↓
     └───┘

适合 Tool Calling Agent、代码生成后 review、反复检索直到足够。

注意:必须设置终止条件,避免无限循环。

6.4 并行图

START → A  → D
         ├→ B ┘
         └→ C

适合多路检索、多文档处理、多 Agent 并行。

注意:并行图经常需要 reducer 合并结果。

6.5 子图

Parent Graph
      ↓
  Subgraph
      ↓
Parent Graph

适合大型项目模块化,把复杂系统拆成可复用的子图。


7. 完整案例:客服 Agent 工作流

7.1 定义 State

from typing_extensions import TypedDict

class ServiceState(TypedDict):
    user_input: str
    intent: str
    order_id: str
    result: str
    final_answer: str

7.2 定义节点

def classify_intent(state: ServiceState):
    text = state["user_input"]
    if "订单" in text:
        intent = "order"
    elif "物流" in text or "快递" in text:
        intent = "logistics"
    else:
        intent = "general"
    return {"intent": intent}

def extract_order_id(state: ServiceState):
    return {"order_id": "10086"}

def query_order(state: ServiceState):
    return {
        "result": f"订单 {state['order_id']} 当前状态:已支付"
    }

def query_logistics(state: ServiceState):
    return {
        "result": f"订单 {state['order_id']} 的物流状态:运输中"
    }

def general_answer(state: ServiceState):
    return {
        "result": "我可以帮你查询订单或物流。"
    }

def final_answer(state: ServiceState):
    return {
        "final_answer": state["result"]
    }

7.3 定义路由

def route_by_intent(state: ServiceState):
    if state["intent"] == "order":
        return "query_order"
    if state["intent"] == "logistics":
        return "query_logistics"
    return "general_answer"

7.4 构建图

from langgraph.graph import StateGraph, START, END

builder = StateGraph(ServiceState)

# 添加节点
builder.add_node("classify_intent", classify_intent)
builder.add_node("extract_order_id", extract_order_id)
builder.add_node("query_order", query_order)
builder.add_node("query_logistics", query_logistics)
builder.add_node("general_answer", general_answer)
builder.add_node("final_answer", final_answer)

# 添加边
builder.add_edge(START, "classify_intent")
builder.add_edge("classify_intent", "extract_order_id")

builder.add_conditional_edges(
    "extract_order_id",
    route_by_intent,
    {
        "query_order": "query_order",
        "query_logistics": "query_logistics",
        "general_answer": "general_answer",
    },
)

builder.add_edge("query_order", "final_answer")
builder.add_edge("query_logistics", "final_answer")
builder.add_edge("general_answer", "final_answer")
builder.add_edge("final_answer", END)

graph = builder.compile()

7.5 调用图

result = graph.invoke({
    "user_input": "帮我查一下订单物流",
    "intent": "",
    "order_id": "",
    "result": "",
    "final_answer": "",
})

print(result["final_answer"])
# 输出:你的订单物流正在运输中

执行流程:

START → classify_intent → extract_order_id → route_by_intent → query_logistics → final_answer → END

8. 踩坑清单:LangGraph 的 10 个常见错误

坑1:StateGraph 就是画流程图

LangGraph 的重点不是画图,而是让图可执行、可持久化、可恢复、可观测

坑2:Node 必须是 LLM

Node 可以是普通函数、工具调用、数据库查询、规则判断、Agent 或子图。

坑3:State 就是 messages

messages 只是 state 的一个字段。真实项目应该用结构化 state

坑4:Edge 只能固定连接

LangGraph 支持 conditional edge、Command、Send 等动态控制方式。

坑5:compile 后还能随便改图

通常不会直接改 compiled graph。应该修改 builder 后重新 compile。

坑6:Reducer 是可选小知识

只要有并行更新、Send、Map Reduce、多 Agent 汇总,就必须理解 reducer。

坑7:节点返回完整 state

节点通常返回 Partial[State],也就是局部更新,不需要返回全部字段。

坑8:忘记指定 START 入口

必须指定 START,否则 LangGraph 不知道从哪里开始:

builder.add_edge(START, "first_node")  # 必须有这条

坑9:路由函数做太多事

路由函数只做轻量判断,复杂逻辑放到 node 中:

# 错误:路由里查数据库
def route(state):
    result = db.query(...)  # 不应该在这里做
    return "a" if result else "b"

# 正确:路由只做判断
def route(state):
    return state["intent"]  # 意图已经由上一个节点计算好

坑10:invoke 时没传全字段

如果 total=True(默认),invoke 必须传递所有字段:

# 错误:漏传 order_id
graph.invoke({"user_input": "...", "intent": ""})  # 报错

# 正确:传全所有字段
cass State(TypedDict, total=False):
    # 或传全所有字段
    ...

9. 总结

9.1 核心记忆

State 是数据,Node 是动作,Edge 是流程。

构建 LangGraph 的 6 个步骤:

1. 定义 State(TypedDict)
2. 编写 Node(函数)
3. 添加 Node(builder.add_node)
4. 添加 Edge(builder.add_edge / add_conditional_edges)
5. compile(builder.compile())
6. invoke / stream(graph.invoke(...))

9.2 LangGraph vs AgentExecutor

特性AgentExecutorLangGraph
状态管理隐式(messages)显式(TypedDict State)
流程控制LLM 自由决定开发者显式编排
持久化不支持checkpointer 支持
恢复不支持支持
人工介入临时 if/elseinterrupt / resume 原生支持
多 Agent困难graph / subgraph 支持
调试困难stream 实时输出

9.3 延伸方向

  • Checkpointer:从 InMemorySaver 升级到 Redis / Postgres 持久化
  • Human-in-the-Loop:用 interrupt 实现审批流程
  • Streaming:stream_mode=“updates” 实时查看节点执行
  • Subgraph:把复杂系统拆成模块化子图
  • Multi-Agent:Supervisor + 多个子 Agent 协作

10. 文末互动

思考题

  1. 你现在的项目用的是什么 Agent 方案?是简单 prompt 调用、LangChain AgentExecutor,还是已经用了 LangGraph?
  2. 你的业务场景中,最迫切需要 LangGraph 解决的是哪个问题(流程控制、状态管理、持久化、人工介入)?
  3. 你会如何设计一个"生成代码 → 运行测试 → 如果失败则修复 → 再测试"的 LangGraph 循环?

如果本文对你有帮助

  • 点赞支持,让更多做 Agent 工程的开发者看到
  • 收藏备用,设计 LangGraph 工作流时翻出来参考
  • 评论交流,说说你的 State 设计经验

作者的话:LangGraph 的出现标志着 Agent 应用从"玩具 Demo"走向"生产系统"。理解 State + Node + Edge 的核心理念,比记住任何 API 都重要。当你能把业务逻辑拆成清晰的节点,用边把它们连接起来,你就已经掌握了 LangGraph 的精髓。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值