LangGraph 入门:图式编排心智模型

QuibblerAgentQuibblerAgent 2026-11-04 约 16 分钟 4 次阅读

Agent 专题写到今天,工具箱已经换过三代:裸 SDK 系列手写工具循环,messages 列表、tool call 分发、重试兜底全部亲手造过一遍;LangChain 基础系列换了趁手的家伙,用 create_agent 把同一套循环藏进库函数;TypeScript 侧还用 Mastra 对照着写过一轮。工具越用越高级,但有个问题一直没正面回答:当循环本身复杂到一定程度,while 就不够用了。

《LangGraph 从入门到实战》是 Agent 专题的收官系列,共 14 篇,前置只有裸 SDK 与 LangChain 基础两个系列。主角 LangGraph 做的事,是把 Agent 循环从代码里的控制流提升为一种显式数据结构——图。本篇是第 1 篇,只立"图"这个心智模型,不写可运行的完整代码(第 2 篇动手)。全文按"痛点 → 心智模型 → 与 LangChain 的关系 → 与 while 的等价性 → 超能力预告 → 路线图"展开。


1、while 循环的失控

裸 SDK 系列里那个工具循环,压缩后一共十行:

def run(messages, max_steps=10):
    for _ in range(max_steps):
        resp = model.invoke(messages, tools=TOOLS)
        if not resp.tool_calls:        # 终止条件一:不再要工具
            return resp.content
        messages.append(resp)          # 对话历史手工维护
        for call in resp.tool_calls:   # 分发、重试全是自己的事
            messages.append(run_tool(call))
    return "已达到步数上限"             # 兜底散落在循环外

十行代码干干净净,写 demo 完全够用,问题出在它长大之后。往循环里持续加需求:工具失败要重试、某类操作要人工确认、并发调用要聚合、超时要打断——每一项都往循环体里塞 if,终止条件从一个变成五个,散落在函数各个角落。三个月后回看,"这个循环什么时候停"没有一行代码能回答,答案分布在整个函数体里。

痛点归成三类。状态传递乱:messages 是个裸列表,谁都能 append、谁都能改,几个分支过后没人说得清当前上下文里到底有什么。循环控制散:终止、限流、降级全是手写 if,彼此没有统一形态,改一处牵一串。恢复难:进程一重启,循环里攒下的状态全部归零;想"跑到第三步暂停、明天接着跑",得自己设计存储与恢复协议,这部分代码常常比业务本身还长。

往根上说:这个循环其实是一张图——模型节点、工具节点、条件转移、回边——只不过藏在 while 和 if 的控制流里,看不见摸不着。看不见的结构没法审查、没法存档,更没法在它上面做基础设施。LangGraph 做的事,就是把它从代码里拉出来,变成显式声明的一等公民。

2、图式编排心智模型

LangGraph 的核心抽象一句话讲完:节点是函数,边是控制流,State 是共享黑板。

节点(node)是一个普通 Python 函数:读入完整 State,返回要更新的字段子集,框架负责把增量合并进黑板。边(edge)声明"下一步去哪":普通边是固定转移;条件边(conditional edge)挂一个路由函数,按当前 State 动态决定去向——分支、循环、提前收束,全部由边表达。State 是全图唯一的共享数据结构,典型形态是消息列表加若干业务字段,每个节点都读它、写它。

拿一个客服 Agent 举例,把流程画成三个节点的图:理解提问、查知识库、组织回答,查完知识库带着结果回到理解节点重新判断。图示如下:

flowchart LR S([START]) --> A[理解提问] A -->|信息不足| T[查知识库] T -->|结果写回| A A -->|信息足够| R[组织回答] R --> E([END])

解读这张图:里面没有任何循环语句,"查完再理解、理解完可能再查"的循环,被表达成一条从"查知识库"指回"理解提问"的边。循环还在,但它从控制流语句降级为图上的一条线,与普通边平起平坐。三要素与常规代码的对应关系整理成表:

图元素 代码世界里的对应物 职责
节点 一个函数 干活,返回状态增量
普通边 函数的调用顺序 固定转移
条件边 if 与循环判断 动态路由与回环
State 贯穿全程的上下文对象 全图共享的黑板

用这个模型回看第 1 节的失控循环:messages 就是 State,模型调用与工具执行就是两个节点,"还有没有 tool call"就是条件边。业务没变,变的是表达方式——从"读代码脑内模拟执行"变成"看图"。

3、与 LangChain 的关系

先交代版本事实。langgraph 是独立发布的包,不装 langchain 也能单独使用;它出自 LangChain 团队之手,官方定位是低层编排框架(low-level orchestration framework)与 Agent 运行时。版本线方面,2025 年 10 月 LangGraph 发布 1.0 进入稳定期,截至 2026 年 10 月已迭代到 1.2.x,PyPI 最新为 1.2.18。文档并入 LangChain 统一文档站,入口在官方文档的 LangGraph 分区,源码在 GitHub 仓库。1.0 之后 StateGraph 有了正式名字 Graph API,与 Functional API 相对,但它依旧是搭图主入口,from langgraph.graph import StateGraph, START, END 这行导入从 0.x 时代用到今天没变过。

两者的分工一句话:LangChain 给组件,LangGraph 给编排。LangChain 负责与模型、工具、提示词打交道,create_agent 封装了开箱即用的标准 Agent;LangGraph 负责把任何东西——包括 create_agent 造出的 Agent——当作图上的一个节点,与自定义节点、条件边、检查点组装成任意流程。对照如下:

维度 LangChain LangGraph
定位 组件与集成 编排与运行时
典型入口 create_agent StateGraph
解决什么 怎么调模型、怎么定义工具 流程怎么走、状态怎么存
单独使用 可以 可以
组合形态 产出的 Agent 作为图中节点 为组件提供流程骨架

两个常见误解当场拆掉。其一,"用 LangGraph 必须用 LangChain"——不必,节点就是普通函数,接什么模型客户端都行。其二,"LangGraph 是 LangChain 的高级封装"——方向反了,官方定位恰恰是低层框架:预封装入口越不合身,越要下沉到 LangGraph 自己搭图。顺带一提,0.x 时代住在 langgraph.prebuilt 里的 create_react_agent,1.0 起推荐入口收拢为 LangChain 侧的 create_agent。本系列两边都会用:中段以 StateGraph 讲透机制,实战篇回到 create_agent 享受封装红利。

4、同一个循环的两种写法

心智模型立住了,现在把它与 while 循环正面并排。同一个"模型-工具"循环,裸写版本如下(LangChain 基础系列口径,9 行):

def run_agent(messages: list) -> str:
    for _ in range(MAX_STEPS):
        resp = model.invoke(messages)
        if not resp.tool_calls:      # 不再要工具即收尾
            return resp.content
        messages.append(resp)
        for call in resp.tool_calls:
            messages.append(run_tool(call))
    raise StepLimitError("已达到步数上限")

解读:控制流长在代码里——for 管步数上限,if 管出口,函数体就是流程的全部真相。换成 LangGraph 的图写法(15 行,ToolNode 来自 langgraph.prebuilt,第 6 篇展开):

from langgraph.graph import MessagesState, StateGraph, START, END

def agent(state):
    return {"messages": [model.invoke(state["messages"])]}

def route(state):  # 有工具调用去 tools,否则直达终点
    return "tools" if state["messages"][-1].tool_calls else END

builder = StateGraph(MessagesState)
builder.add_node("agent", agent)                # 节点一:模型
builder.add_node("tools", ToolNode(TOOLS))      # 节点二:工具
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", route)   # 条件边:去或停
builder.add_edge("tools", "agent")              # 回边:循环在此
graph = builder.compile()

对照着读三件事。其一,循环消失了:没有任何循环语句,"回到 agent"是一条普通边,回环与直线在图里地位相同。其二,State 不再手工维护:agent 节点只返回增量消息,MessagesState 内置的 reducer 负责拼回黑板(reducer 是第 3 篇的主角)。其三,原先散落各处的步数上限,变成运行时统一参数 recursion_limit(1.0.6 起默认 1000)——从"每个循环自己数"变成"框架替你数"。

两种写法表达力等价:图能做的 while 都做得到,反之亦然。真正的差别在于,结构显式化之后,框架获得了在结构上做事的位置——这正是下一节的内容。

5、图带来的超能力

流程一旦成为显式数据结构,框架就在任意两个节点之间拿到了插入点。四类生产环境最值钱的能力,都从这个插入点长出来。

循环与多代理。条件边加回边,任意复杂的拓扑都是同一种写法,不存在"嵌套 while 管不住"的复杂度天花板。多代理协作也顺理成章:一张图可以作为节点嵌入另一张图,supervisor 循环派活、worker 干活交差,组网方式与单 Agent 同构(第 10 篇展开)。

持久化。每一步都是图上的超步(superstep),框架能在每个超步结束时给 State 打快照(checkpointer)。进程崩溃后从最近快照续跑,对话历史、工具结果、中间判断一样不丢——第 1 节的"恢复难"在这里被系统性解决(第 7 篇)。

中断恢复。在任意节点声明 interrupt,图跑到此处自动挂起、存档、返回等待;人工审批完成后带着决定恢复执行。human-in-the-loop 从"自己设计暂停协议"变成一行声明(第 8 篇)。

可观测。图的执行历史就是"走到过哪些节点、每步 State 长什么样",回放、逐步重跑、按图调试天然可用(第 12 篇)。

while 循环里,这四项能力每一项都得自己造轮子;图里它们是框架的默认责任——因为框架看得见你的流程。

6、系列路线图

全系列 14 篇,四段推进:本篇立模型,第 2~6 篇把单 Agent 图写透,第 7~9 篇解决生产可靠性,第 10~14 篇组网与上线。完整地图如下:

  • 第 1 篇:图式编排心智模型(本篇)
  • 第 2 篇:环境搭建与第一个 StateGraph
  • 第 3 篇:State 深入——共享黑板与 reducer
  • 第 4 篇:节点与边——路由、分支与并行
  • 第 5 篇:create_agent——官方预置的 Agent 图
  • 第 6 篇:工具接入——ToolNode 与工具循环
  • 第 7 篇:Checkpointer——持久化与时间旅行
  • 第 8 篇:中断与恢复——human-in-the-loop
  • 第 9 篇:流式输出——token 与节点事件
  • 第 10 篇:子图与多 Agent 协作
  • 第 11 篇:Functional API——函数式写图
  • 第 12 篇:测试与调试——图的可观测性
  • 第 13 篇:部署——从 pm2 到 LangGraph Platform
  • 第 14 篇:实战——生产级多 Agent 项目

节奏上唯一的例外就是本篇:整个系列里它只讲不写,从第 2 篇起每篇都有可运行代码,第 14 篇把全部能力合成一个完整项目。

7、总结

本篇立了一个心智模型:Agent 循环本质是一张图,节点是函数、边是控制流、State 是共享黑板;while 循环把这张图藏在控制流里,LangGraph 把它显式声明出来。两种写法表达力等价,但结构显式化之后,循环控制、持久化、中断恢复、多代理组网从"自己造轮子"变成"框架的默认责任"。LangGraph 与 LangChain 的分工也清楚了:一个给组件,一个给编排,可分可合,版本事实上 langgraph 已独立迭代到 1.2.x。

留一个不动手的练习:把你手头一个真实业务——订单审核、周报生成、代码评审都行——在纸上画成一张图,依次回答三个问题。哪些步骤是节点?一句话能说清输入输出的才配当节点。哪里需要条件边?把"判断去哪"的逻辑单独拎出来。State 黑板上放哪几个字段?够用就好,多一个字段就多一份合并烦恼。画完自查有没有回边——没有循环的业务流程,多半还不需要 LangGraph。对于准备入坑的读者,建议把这张纸留好,等第 2 篇动手时直接把它翻译成第一个 StateGraph。

相关推荐

获取系统SDK版本、判断手机ROM
精选Code

获取系统SDK版本、判断手机ROM

Build获取系统SDK版本Android中部分API的使用,需要在特定的SDK版本之后才能使用,因此在兼容老版本SDK的时候,经常需要判断API的版本。各种Android版本的对应关系参考《Android各版本对应的SDK版本》判断手机ROM有时候需要判断手机系统的ROM,检测ROM是MIUI、EMUI还是Flyme,可以使用getprop命令,去系统build.prop文件(关于build.p

4.2k
AndroidStudio中各种中文乱码问题
精选Code

AndroidStudio中各种中文乱码问题

AndroidStudio中各种中文乱码问题1、编译Java错误信息乱码在出现这个Annotation processors must be explicitly declared now...问题的时候,正好也发现AndroidStudio Build Output错误信息都乱码。经常遇到各种问题,习惯了,现在遇到的问题,都是以后的答案。 1.1、修改项目build.gradle(无效)在整个p

2.8k
PydanticAI 详解:类型安全的 Python AI SDK
精选AI

PydanticAI 详解:类型安全的 Python AI SDK

PydanticAI 详解:类型安全的 Python AI SDK写 LLM 应用最疼的两件事:模型返回的 JSON 不合预期、换一家模型商要改一串代码。pydantic/pydantic-ai(PydanticAI)用 Pydantic 团队的老本行回答了第一件——输出用 BaseModel 定义、运行前先校验,错误从运行时提前到编码时;第二件则用"模型即字符串"回答:换个厂商只改一个参数。如今

75