首页/LangGraph 框架指南/第 1 章 从一个失控的脚本说起
LangGraph 框架指南 · 第 01 章

第 1 章 从一个失控的脚本说起

本章不写一行 LangGraph 代码,只回答一个问题:当 LLM 应用长到一定程度,为什么必然会走向"图"?

约 10 分钟4,176 字5 个代码示例

本章不写一行 LangGraph 代码,只回答一个问题:当 LLM 应用长到一定程度,为什么必然会走向"图"?

1.1 一切从一个朴素需求开始

假设你在一家招聘 SaaS 公司任职。产品同学提了个看起来不大的需求:

用户上传一份简历,系统读取后输出一个 0~100 的匹配分,并给出三条理由。

用 LangChain 写,十来行就搞定:

from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

class Score(BaseModel):
    score: int = Field(ge=0, le=100)
    reasons: list[str] = Field(max_items=3)

grader = llm.with_structured_output(Score)

def grade(resume: str, job: str) -> Score:
    prompt = f"岗位要求:{job}\n候选人简历:{resume}\n请给出匹配分和理由。"
    return grader.invoke(prompt)

干净、直接、能用。可是第二周产品又来了:

评分之前先抽干简历结构化字段,再对照岗位要求逐条比对,最后综合出分;另外简历里如果有公司名,去查一下公司背景,作为加分参考。

于是你在 grade 里加判断、加 if、加临时变量。一个月后,这个函数变成了这样:

def grade(resume: str, job: str) -> dict:
    parsed = parse_resume(resume)            # LLM 抽取结构化字段
    if needs_company_lookup(parsed):        # 有公司名?
        company = lookup_company(parsed.company)   # 查公司背景
        parsed.company_info = company
    hard_ok = check_hard(parsed, job)        # 硬性条件
    soft_ok = check_soft(parsed, job)        # 软性条件
    if not hard_ok:
        parsed.rejection_reason = "硬性条件不满足"
        return parsed
    score = summarize(parsed, hard_ok, soft_ok)   # 综合评分
    if score < 60 and can_retry(parsed):    # 重试一次
        parsed.retry_note = "已重试"
        return grade(resume, job)
    return score

代码还在跑,但你已经不敢动了。每一个 if 都对应一条产品规则,每一处临时变量都是隐含的状态。这类代码有个共同特征——控制流和数据流纠缠在一起。

1.2 三类必然而来的工程痛点

把上面这段"失控脚本"拆开看,它暴露的是 LLM 应用规模化后的三类共性问题。LangGraph 的所有设计,几乎都是对着这三类痛点来的。

痛点一:控制流无法稳定扩展

朴素的写法靠函数嵌套和 if/else 表达"先做 A 再做 B,若失败则做 C"。一旦规则到十几条,控制流就成了意大利面:

  • 想插入一步"查学历真伪",要改三个地方
  • 想把"软性条件"和"硬性条件"并行跑,得重写大段逻辑
  • 新人接手时,没人能一眼说清"这个函数到底走了哪几步"

根因:控制流隐没在代码里,没有显式的、可观察的结构。

痛点二:中间状态无法保存与恢复

面试官中途想看"评分到哪一步了",你做不到——因为状态散落在局部变量里,函数返回就消失。更现实的场景:

  • 评估跑到一半调外部 API 失败,想从失败那一步重跑,而不是从头来
  • 用户离开后回来,希望继续上一次的评估
  • 高风险简历评分到一半,要等 HR 确认一个信息再继续

根因:执行过程是"黑盒推进",中间状态既不可见、也不可恢复。

痛点三:并发与扇出无章可循

产品需求继续演进:

现在用户一次上传 5 份简历,系统要并行评估,最后出一份横向比较报告。

你想到用 asyncio.gather,可每个评估内部又有子步骤、有重试、有共享的岗位上下文。手撸并发控制很快又会失控:

  • 哪些步骤可以并行、哪些必须串行,没有统一表达
  • 并行结果怎么汇回到统一的状态里,各自赋值容易覆盖
  • 想动态决定扇出几份(用户上传几份就跑几份),没有现成的抽象

根因:并发语义散落在调用代码里,没有一等公民式的抽象。

1.3 走向"图"是自然收敛

把上面三类痛点的解法抽象出来,会得到三件事:

痛点 解法 LangGraph 中的对应物
控制流不可扩展 把步骤显式画成节点,把流转关系画成边 StateGraph
状态不可保存 每一步结束把状态快照下来,按"线程"组织 Checkpointer / Thread
并发无章法 用"超步"组织并行,用 Send 动态扇出 Superstep / Send

这三件事合起来,就是"有向状态转换图"——一种把"做什么、按什么顺序做、做到什么程度"全部显式化的结构。它不是新发明:2010 年 Google 的 Pregel 论文就用类似的模型跑大规模图计算,LangGraph 把这个模型搬到了 LLM 工作流编排上。

flowchart LR A[朴素的脚本] -->|控制流纠缠| B[显式节点与边] A2[不可恢复的状态] -->|中间结果丢失| C[可快照的通道] A3[手撸并发] -->|并发语义散落| D[超步并行模型] B --> G[LangGraph] C --> G D --> G

1.4 什么是 LangGraph

一句话定义:LangGraph 是一个把 LLM 工作流表达为有向状态转换图、并为之提供运行时与持久化的编排引擎。

它来自 LangChain 团队,但定位不同于 LangChain 本身:

  • LangChain 关注"单次能力":怎么调模型、怎么绑定工具、怎么构造消息
  • LangGraph 关注"多次组合":怎么把多次模型调用、工具调用、人工审核串成一个可控、可恢复、可观测的流程
flowchart TB subgraph LC[LangChain 单次能力] L1[模型接入] L2[消息构造] L3[工具绑定] end subgraph LG[LangGraph 多次组合] G1[节点编排] G2[状态管理] G3[持久化与恢复] G4[并行与扇出] end LC --> LG

LangGraph 不是要替代 LangChain,而是站在它肩膀上:图里的每一个节点,仍然是一个 LangChain Runnable 或一个普通可调用对象。LangGraph 负责把这些"能力单元"组织成一条可控的流水线。

1.5 本书会怎么带你走完全程

本书不会一章一个孤立知识点,而是围绕一个业务——智能简历筛选系统——逐章长出来。每一章解决上一章暴露出来的工程问题:

flowchart TD C1[第2章 搭出最小可运行图] -->|只有一个节点,无法表达决策| C2[第3章 引入状态与 Reducer] C2 -->|多轮评分要累加| C3[第4章 引入条件边与循环] C3 -->|硬/软条件想并行跑| C4[第5章 引入超步并行] C4 -->|节点内部想改流向| C5[第6章 引入 Command] C5 -->|多份简历并行评估| C6[第7章 引入 Send 扇出] C6 -->|要查公司背景| C7[第8章 引入工具调用] C7 -->|报告生成要复用| C8[第9章 引入子图] C8 -->|多角色协作| C9[第10章 引入多 Agent] C9 -->|要流式给前端| C10[第11章 引入流式输出] C10 -->|要会话恢复| C11[第12章 引入持久化] C11 -->|要回溯历史| C12[第13章 引入时间旅行] C12 -->|高风险要人工| C13[第14章 引入中断] C13 -->|跨会话记人| C14[第15章 引入长期记忆] C14 -->|故障要兜底| C15[第16章 引入容错] C15 --> C16[第17章 全景回顾]

到全书结尾,你会得到一个结构清晰、可恢复、可人工介入、可记忆、可容错的多 Agent 简历筛选系统——并且你清楚每一块为什么在那里。

常见坑与排错

本章不涉及代码,但有几个认知层面的坑值得提前点出:

  1. "图"不是 DAG。新手常以为工作流图必须是"无环有向图"。LangGraph 的图允许循环,甚至鼓励循环(ReAct 模式、重试、多轮审核本质上都是循环)。循环的终止靠条件边,而不是禁止出现环。

  2. 别把图当链表写。从单链式 if/else 迁移过来的人,容易把图画成一条直线 A→B→C→D。这浪费了图的表达力——真正发挥图价值的是分支、合并、并行、循环这些"非线性"结构。

  3. 节点是"纯函数"而非"状态机"。节点接收状态、返回更新,不要在节点里持有外部可变状态。这是后面持久化、时间旅行、并发正确性的共同前提。

  4. 状态是图级共享的,不是节点私有的。节点之间不直接传参,而是读写同一个图状态。理解这一点,是理解后面 reducer、checkpointer、子图状态共享的基础。

动手练习

  1. 回想你最近写的一个 LLM 应用,画出它的实际控制流(哪些步骤、哪些分支、哪些循环)。看看它是否已经具备"图"的雏形。

  2. 在 1.1 节那段 grade 函数里,找出三类痛点的具体证据:哪一行代码体现"控制流纠缠"?哪一处体现"状态散落"?哪一处如果要做并行会很难改?

  3. 思考题:如果让你自己设计一个"工作流引擎",你会给它哪些一等公民概念?和 LangGraph 的节点/边/状态/超步对照一下。

本章速查

概念 一句话
LangGraph 把 LLM 工作流表达为有向状态转换图的编排引擎
它解决的问题 控制流不可扩展、中间状态不可恢复、并发无章法
与 LangChain 的关系 LangChain 提供"单次能力",LangGraph 负责"多次组合"
本书的演进线索 单节点 → 状态 → 条件循环 → 并行 → Command → 动态扇出 → 工具 → 子图 → 多 Agent → 生产化