Chapter 04 · 自测题库
自测题库 · 三层梯度 + 跨章场景
前三章建立了状态图、原理与选型——这章逼你在场景里判别。题干在前、答案统一压在文末单个折叠块里。直接展开答案 = 把这一章当成第四遍精读,前三章靠主动回忆攒下的记忆红利当场清零。三层共 17 题,按"能不能背 → 能不能讲 → 能不能迁移"的难度梯度排开;应用判别层 5 道跨章场景题是本教程的 capstone 替代。
如何用本章
- 每题先合上教程,把答案写在纸上或编辑器里,再往下做。
- 三层各自做完一层,再一次性翻到文末对答案——不要边做边瞄。
- 做错的题不要纠结答案文字,顺着题干里的章节链接回原文重读,比反复看答案有效得多。
- 应用判别层 5 题是 capstone 替代。做不出 ≥3 道,说明只熟悉了概念、缺少迁移能力——前三章还没读到位。
§1概念层 · 6 题 · Recall
对应 01 章。每题只允许写关键词,不允许翻教程。题干末尾的链接是对答案之后回查用的,不是提示。
- 用一句话写出
StateGraph的三要素,并说清三者各自的职责边界。(回查 01 章 §1.1 State+/a> 与 §1.3 Edge) - reducer 是什么?为什么它不能省——把它去掉,并发写同一个字段时框架会怎样?(回查 01 章 §1.1)
- 普通边(静态
add_edge)和条件边(add_conditional_edges)的差别是什么?各举一个最小典型用法。(回查 01 章 §1.3) StateGraph与.compile()之后的产物是什么关系?.compile()到底产出了一个什么东西,它能做什么?(回查 01 章 §1.4 compile())- checkpointer 配合
thread_id起什么作用?两者合起来如何定位一段历史?(回查 01 章 §1.5 Checkpointer+thread_id) - checkpoint 的存档颗粒度是 node 还是 super-step?
interrupt()与Command(resume=…)是什么关系,resume 时 node 从哪里开始重新跑?(回查 01 章 §1.6 Interrupt)
§2原理层 · 6 题 · Understand
对应 02 章。每题都要回答「为什么不是别的做法」——只描述现状不算答案。
- 同样一段「prompt 接模型接解析器」的线性流程,为什么不必上 LangGraph 的状态图,用 LCEL 的
|链就够?反过来,出现什么信号就该从链下沉到图?(回查 02 章 · 为什么用图) - 一个带条件分支 + 重试回边的流程,为什么手写
while True循环可以跑,却仍建议上图?图比手写循环多给了什么?(回查 02 章 · 为什么用图) - 为什么说"持久化 / HITL / 时间旅行 / 崩溃恢复"是 checkpoint 的副产品,而不是四件各自实现的功能?把 checkpointer 拿掉,这四件事分别会退化成什么?(回查 02 章 · checkpoint 持久化)
- durable execution 的 sync 与 async 两种取舍,分别牺牲什么、换来什么?什么场景下必须选其中一种?(回查 02 章 · durable execution)
interrupt()触发的暂停,为什么用 raise-then-resume 协议、而不是 Python 协程的 yield 暂停?根本障碍是什么?(回查 02 章 · HITL)- HITL 是哪三个机制协同的产物?任意去掉一个,HITL 会退化成什么样子?(回查 02 章 · HITL)
§3应用判别层 · 5 个跨章场景 · capstone 替代
本节是本教程的 capstone。每题给一个具体场景,要求判别:这要用 01 章哪个概念、02 章哪个机制、03 章哪条判别轴?每题都必须说清判据与代价——只给结论不给理由不算答对。做不出 ≥3 道,请回前三章。
- 场景 A · 这个需求该停在 LCEL 还是上 LangGraph 状态图?
一个文档审核流水线:先抽取要点(一次 LLM 调用),然后条件分支——若命中合规风险词,转人工不在此处、而是走"重写要点"再回到抽取重判一次,最多重试 3 次;不命中则直接产出结论。没有人工中断,没有跨进程崩溃恢复需求,QPS < 10。
(a) 把它拆成 LCEL 的线性|链能不能干净表达?卡在哪一处?
(b) 结论:停在 LCEL(配一点胶水)还是上 LangGraph 状态图?给出判据与代价(选错那条路要多付什么)。
(c) 如果后来"重试 3 次仍命中风险词"时要求转人工复核,这个变化会不会推翻 (b) 的结论?为什么? - 场景 B · 要人工审批的多步流程,怎么用 checkpoint + interrupt 落地 HITL?
一个采购 agent:拉供应商报价 → 生成采购建议 → 等财务审批(审批人 4–6 小时后才点"批准 / 驳回")→ 批准则下单、驳回则重做建议。进程没法空等 6 小时不重启。
(a) 这个流程要落地 HITL,用到 01 章哪几个概念 + 02 章哪个机制?把它们对应到具体 node。
(b) 审批人离开的 6 小时里,凭什么进程可以重启、回来还能从原地继续?说清是哪个机制在兜底。
(c)interrupt()之前的"拉报价 + 生成建议"在 resume 时会重跑——这件事要怎么处理才不会重复下单或重复扣费?给一种做法并说明代价。 - 场景 C · 同样要多 agent,LangGraph 还是 CrewAI / AutoGen?按什么约束选?
三个团队各报一个需求,都要"多个 agent 协作":
— 团队甲:要一个能精确控制每步走向、可崩溃恢复、要人工审批节点的报告生成系统;
— 团队乙:要在两天内拼一个"研究员 + 写手 + 审稿"的内容流水线 demo 给客户看,控制力要求不高;
— 团队丙:要做一个多个 agent 自由对话、互相质疑直到收敛的方案讨论室。
(a) 用 03 章的三条判别轴,分别给甲乙丙各推一个框架(LangGraph / CrewAI / AutoGen 三选一)。
(b) 每个选择写清主导约束是哪一条(控制力 / 速度 / 会话)以及代价(选了它要放弃什么)。
(c) 团队乙的 demo 上线后要补"崩溃恢复 + 人工审批",最省力的演进路径是什么? - 场景 D · 长任务的 durable 设计
一个 agent 每月给每个 SaaS 客户生成健康度报告——单客户报告动辄 30+ 次工具调用、跑 20 分钟,期间要财务部审批一次"是否调用付费 API 拉征信数据",审批人 4–6 小时后才响应。
(a) 用 03 章三条判别轴各评一次"重 / 轻",给出选型结论。
(b) durable execution 选 sync 还是 async?说清这个 4–6 小时等待如何决定了取舍。
(c) checkpoint 颗粒度是 super-step 而非单 node,会怎样影响图的拓扑设计?给出至少一条具体拓扑建议(提示:付费 API 这种有外部代价的调用该摆在哪)。 - 场景 E · 编辑式 HITL(不是 yes/no)
agent 起草客户邮件,需要市场经理在发送前编辑正文里好几行,再继续走"发送 + 写 CRM"。审批不是按个钮放行,而是要把改后的正文回填进流程。
(a) 这种"审批人能改内容"的 HITL,为什么单靠 input/output 校验式的护栏(只能放行 / 拒绝)做不干净?
(b) 用 LangGraph 落地,要用到 01 章哪几个概念 + 02 章哪个机制?特别说明:改后的正文靠什么机制合并回流程状态?
(c)interrupt()之前"草拟邮件 + 拉 CRM 上下文"会在 resume 时重跑。给两种处理思路并比较代价。
§4亲手画一张图 · 把状态图落到纸面
合上教程,在纸上 / Excalidraw 里画一张带条件边回路、并标出 checkpoint 存档点的 StateGraph。就用场景 A 那个文档审核流程,约束如下:
- ≤ 6 个 node:
START → 抽取要点 → 合规判别 → (命中)重写要点 → 回到抽取要点,不命中则→ 产出结论 → END。 - 画出那条条件边回路:从"合规判别"分叉,一条回边指向"抽取要点"(重试),一条直行到"产出结论"。回边上标出条件("命中风险词 且 重试<3")。
- 在每个 super-step 之间画一个小方块代表 checkpoint 存档点——重点标出"重写要点"这一步前后的存档,问自己:崩在重试第 2 圈,从哪个 checkpoint 恢复?
画完做三个自检:① 那条回边的判断基于哪个 state 字段(重试计数存在哪)?② checkpoint 是钉在 node 上还是 super-step 上,重试一圈产生几个存档点?③ 把这张图和 起点章的概念地图对照——你画的回边,对应的是 01 章的条件边还是普通边?
这张图画不出来 = 概念没真正锚定。不画的人,最终不会真的用 LangGraph。
§5刚好够不着的进阶挑战
把场景 D 的 durable 行为,用一个不带 checkpointer 的裸 while 循环实现一版
挑场景 D(月度健康度报告)里你最熟的那段,先不用 LangGraph:用一个 Python while 循环 + 一份手写的进度文件,去实现"跑到一半崩了能从原地恢复"和"等审批 6 小时期间进程可重启"。
实现时盯三个问题,逐一写下答案:
- 进度文件里到底要存什么,才能让重启后的循环知道"上次跑到第几个工具调用、哪一步还在等审批输入"?这跟 checkpoint 存的东西差在哪?
- 付费 API 那次调用,怎么保证崩溃重跑时不会重复扣费?你的幂等守卫写在哪一层?
- "等审批"这一步,进程退出后靠什么把"在等什么、等谁"这件事留住?
写完和 LangGraph 版本对比代码长度、边界条件数量、以及"再加一个审批节点"的改动量。对比之后,你才知道 checkpointer 在你心里值多少钱——这比再读一遍教程有用得多。
§6答案 · 三层一并展开
展开全部答案(先把上面 17 题都做完再展开)
§1 概念层答案
- 三要素 = State + Node + Edge。State 是贯穿全图的共享数据(每个字段带一个合并函数);Node 是读 State、做一段计算、返回一份"要改哪些字段"的局部更新的函数;Edge 决定下一个轮到哪个 node。职责边界:State 管"存什么",Node 管"算什么",Edge 管"下一步走谁"——三者解耦,所以同一组 node 换不同 edge 接法就是不同的图。
- reducer 是挂在每个 State 字段上的
(旧值, 新增值) -> 合并后函数;一个 super-step 结束时,框架用它把各 node 的返回值折叠回 State。不能省:没显式声明时默认是"后写覆盖"(last-write-wins)。并发写同一字段时,两个 node 在同一步都想写它,框架无法决定保哪个,直接抛InvalidUpdateError: Can receive only one value per step——这正是 reducer 存在的理由:它把"多个并发更新如何合一"显式化。 - 普通边(
add_edge(A, B))是无条件静态接线,编译期就固定:A 跑完必去 B,典型用法add_edge(START, "planner")。条件边(add_conditional_edges(A, router_fn))在运行时调router_fn(state),按当前 state 返回的字符串选下一个 node,典型用法"按 LLM 是否要调工具,决定去 tools 还是去 END"。一句话:普通边是固定线路,条件边是运行时分叉。 StateGraph是声明阶段的可变构建器(可不断add_node/add_edge);.compile()把这套声明冻结,产出一个CompiledStateGraph。这个产物实现了统一的Runnable接口——能invoke/stream/batch,能挂 checkpointer,能和 LCEL 链互相嵌套。分开两段是为了"构建期可随意改 + 编译时做一次校验(断头边、孤儿 node)+ 编译后才是稳定可跑的对象"。- checkpointer 是存档后端,每个 super-step 结束自动落一份 state 快照。
thread_id是这条对话/会话的标识。两者合起来用(thread_id, checkpoint_id)二元定位一段历史:thread_id选"这是谁的历史线",checkpoint_id选"这条线上的哪一步"。只给thread_id不给checkpoint_id默认取最新一步——这就是"接着上次聊"。 - 颗粒度是 super-step,不是单个 node:一步里并发跑的多个 node 共享同一个存档点。
interrupt(payload)在 node 内部抛出 → 框架捕获后存 checkpoint,并把 payload 返回给调用方;Command(resume=value)重新发起后,整个 node 从头重跑,跑到原来那行interrupt()时它直接返回 value,然后继续往下。关键代价:interrupt 之前的代码会重跑一遍。
§2 原理层答案
- LCEL 的
|是线性组合:数据从左到右单向流,每个组件一次过。"prompt 接模型接解析器"就是这种形状,上图只会徒增三层抽象(State / Reducer / 图结构)却不解决任何新问题——所以停在链。下沉信号:出现①条件分支(按中间结果选不同下一步)②回边/循环(同一步要重复跑直到满足条件)③人工中断(要暂停几小时等人)④需要崩溃恢复 / 时间旅行。任一出现,线性链就表达不了,该上图。 - 手写
while True当然能跑通条件分支 + 重试。图比它多给的是把控制流变成数据:① 每圈循环自动落 checkpoint,崩了能从本圈恢复,而手写循环崩了从头再来;② 重试状态(计数、上一轮结果)进 state、可被持久化和重放,手写循环的状态散在局部变量里、进程一死就没;③ 拓扑可被可视化 / 流式观测,手写循环是黑盒。代价:图要先学 State + Reducer + Edge 三件事,简单循环上图是过度工程——这也是场景 A 要判别的点。 - 因为这四件事共用同一个底层动作:把每一步的完整 state 存下来、能按 key 取回。checkpoint 一旦存在——"接着上次聊"就是取回上一步 state(持久化);"暂停等人再继续"就是存档后停、resume 时取回(HITL);"回到第 3 步重走"就是取回第 3 步的 checkpoint(时间旅行);"崩了重启"就是取回崩溃前最后一个 checkpoint(恢复)。拿掉 checkpointer:持久化退化为"重启即失忆";HITL 退化为"只能同进程同步阻塞等待,不能跨重启";时间旅行直接消失;崩溃恢复退化为"从头重跑"。
- sync(每步存完档再继续,存档在关键路径上):牺牲吞吐 / 延迟,换来"任何一步崩了最多丢当前这步"的强保证——适合长任务、有人工审批、单步代价高(如付费 API)的场景。async(存档在后台异步落盘,不挡主流程):牺牲"崩溃时最近一两步未必落盘",换来更高吞吐——适合高 QPS、单步代价低、丢一两步可接受的场景。必须选 sync 的典型:单步会扣费或触发不可逆外部动作,绝不能因为"档没存上"而崩溃重跑导致重复执行。
- 因为协程/生成器的暂停状态活在调用栈帧里,栈帧无法被序列化、跨进程重启无法恢复——这和 checkpoint"把暂停状态持久化、重启后还能回来"的目标根本冲突。raise-then-resume 把暂停所需的一切塞进 state(普通 dict,可序列化),于是暂停点能落进 checkpoint、几小时后换个进程也能 resume。代价:node 内
interrupt()之前的代码会在 resume 时重跑一遍(因为整个 node 重新执行)。 - HITL = Checkpointer + Interrupt + Reducer 三件套协同。① Checkpointer 提供 durable 存储,暂停后可跨重启恢复;② Interrupt 提供 node 内部"抛出并带 payload"的暂停原语,触发停顿并把待审内容交给外部;③ Reducer 让
Command(resume=…, update={…})里的 update 能干净折叠回 state,于是审批人能修改内容而不只是放行。各去掉一个:去 Checkpointer = 重启即丢,HITL 退化为同进程同步死等;去 Interrupt = 只能在 node 边界静态停,无法在一个 node 中途触发;去 Reducer = 审批人改的内容回填不进 state,HITL 退化为只能 yes/no 的开关式审批。
§3 应用判别层答案 · capstone 替代
场景 A 答案 · 停 LCEL 还是上图
(a) 不能干净表达。LCEL 的 | 是单向线性管道,卡在两处:① "合规判别"后要条件分叉(命中走重写、不命中走结论)——线性链没有分支;② "重写要点 → 回到抽取要点、最多 3 次"是回边 + 循环——线性链没有回头路。这正是 02 章那两个下沉信号(条件分支 + 回边)同时出现。
(b) 上 LangGraph 状态图。判据:条件分支 + 重试回边是状态图的本职,用条件边 + 一个 retry_count 字段两行就表达干净。代价:要付出 State + Reducer + Edge 三层抽象的学习/搭建成本,比一条 | 链重。选错那条路的代价:硬塞进 LCEL,分支和循环只能靠 node 内部手写 if + 递归调用,把控制流埋进代码、丢掉可视化与可恢复——等于用错工具把图的好处全放弃。
(c) 不推翻,反而加固。加"转人工复核"= 引入 HITL,而 HITL 需要 checkpointer + interrupt(02 章 HITL 三件套),裸 LCEL 更做不了。原本就该上图的结论只会更确定——这道小变化恰好示范:HITL 是把人推向 LangGraph 的最强单一信号。
场景 B 答案 · 审批 HITL 落地
(a) 01 章概念:State(存报价、采购建议、审批结果)、Node(拉报价 / 生成建议 / 审批闸门 / 下单 / 重做建议)、条件边(审批闸门后按"批准/驳回"分叉)、Checkpointer(落档)、Interrupt(在审批闸门内抛出、把建议交给审批 UI)。02 章机制:HITL 三件套(Checkpointer + Interrupt + Reducer)。对应到 node:审批闸门 node 里调 interrupt(采购建议) 暂停,审批人点完用 Command(resume="批准"/"驳回") 恢复。
(b) 靠 Checkpointer 兜底。interrupt() 抛出时,框架已把当前 state(含采购建议、走到审批这一步的事实)落进 checkpoint。进程重启后,用同一个 thread_id 发起 Command(resume=…),框架从那个 checkpoint 取回 state、重跑审批 node 到 interrupt 处拿到审批结果继续——所以 6 小时里进程死了也不丢。
(c) 因为 interrupt 之前的"拉报价 + 生成建议"会随审批 node 重跑而再执行一次。一种做法:把这两步挪到审批闸门之前的独立 node(各自一个 super-step),审批 node 里只剩 interrupt() + 读审批结果——这样重跑的只是审批 node 本身,拉报价/生成建议已在前面的 super-step 存过档、不会重跑。代价:图的 node 数变多、拓扑变长,但换来"重跑面积最小、绝不重复下单"。(另一种做法是 state flag 幂等守卫,代价是 state 里塞很多 _done 字段。)
场景 C 答案 · 多 agent 选型
(a)(b) 按 03 章三轴(durable / HITL / 动态并发),主导约束决定选型:
- 团队甲 → LangGraph。主导约束 = 控制力(精确控每步 + 崩溃恢复 + 人工审批节点,三轴全重)。代价:开发最重,要搭 State/Edge/checkpointer,两天拼不出 demo。
- 团队乙 → CrewAI。主导约束 = 速度(两天出 demo、控制力要求低)。CrewAI 的角色/任务抽象上手最快。代价:放弃细粒度控制与原生 durable,后期要精确编排会顶到天花板。
- 团队丙 → AutoGen。主导约束 = 会话(多 agent 自由对话、互相质疑到收敛,正是多轮对话编排的强项)。代价:对话驱动的流程"走向"不如图可控,要做严格审批/恢复会别扭。
(c) 团队乙补"崩溃恢复 + 人工审批"= ② 轴从轻变重。最省力路径:不整体重写——把 CrewAI 定义的单个 agent 当作内层执行单元,外面用 LangGraph 包一层做 durable + HITL 的编排(LangGraph 负责 checkpoint 与 interrupt,CrewAI 负责单 agent 内部)。这样保住已写的 agent,只在外层加状态图。
场景 D 答案 · 长任务 设计
(a) 三轴评估:
- ① durable:极重——20 分钟单跑 + 30+ 工具调用,任一步失败要从原地 resume 而非整跑重来 + 4–6 小时审批等待要跨重启。
- ② HITL:重——中途要财务审批一次(且决定是否动用付费 API,是有后果的决策)。
- ③ 动态并发:中——多客户可并行、单客户内多数据源可并行,但不是"由 state 动态拆出 N 路"那种。
三轴皆重 → LangGraph 是最强 fit,几乎无替代。
(b) 选 sync durable。决定性的点是"付费 API 拉征信"这一步有真实外部代价:必须保证"存完档才推进",绝不能因为档没落上、崩溃重跑而重复扣费。4–6 小时审批等待本身也要求暂停点被可靠持久化(sync 保证)。async 的"丢最近一两步可接受"在这里不可接受。
(c) checkpoint 钉在 super-step:把有外部代价的调用拆成独立的单 node 单 super-step,不要塞进 fan-out。具体建议——付费 API 调用单独占一个 node,且放在 interrupt() 审批之后;它前后各有 checkpoint,于是审批通过后才扣费,崩溃恢复颗粒度最细、不会因为一个 super-step 里别的 node 失败而把扣费动作连带重跑。再配一个 state flag 做幂等守卫双保险。
场景 E 答案 · 编辑式 HITL
(a) 因为 input/output 校验式护栏(如 OpenAI Agents SDK 的 Guardrails)本质是放行 / 拒绝的钩子:它能拦下一次输入或输出,但既不能把跑到一半的 agent 状态存下来等几小时,也没有"中途暂停、把内容交出去、等人改完再回填"的能力。编辑式 HITL 需要的恰是"暂停 + 持久化 + 把修改合并回状态"这三件——校验护栏一件都不提供。
(b) 01 章概念:State(存待审草稿 + CRM 上下文)、Node(草拟邮件 / 审批闸门 / 发送+写CRM)、条件/静态边、Checkpointer、Interrupt(在审批闸门内抛出、把草稿交给经理)。02 章机制:HITL 三件套,其中改后的正文靠 Reducer 回填——Command(resume="ok", update={"draft": "…改过的正文…"}) 里的 update 由该字段的 reducer 折叠回 state,于是经理改的几行干净地覆盖/合并进草稿字段。这正是"去掉 Reducer 就只能 yes/no"在本场景的体现。
(c) interrupt 前的"草拟 + 拉 CRM"会随审批 node 重跑。两种思路:
- 思路 1 · 把有代价的动作挪到 interrupt 之后:草拟与拉 CRM 放在审批闸门之前的独立 node 完成并存档,审批 node 只剩 interrupt + 读回填;所有外部副作用(发送、写 CRM)全在审批之后的 node。代价:拓扑变长、node 变多。
- 思路 2 · state flag 幂等守卫:草拟代码加
if not state.get("draft_ready"): 生成草稿(); 置 draft_ready=True,重跑时跳过。代价:state 里塞一堆_ready字段,但代码改动最小。 - 怎么选:草拟有外部代价(调大模型写邮件要花钱)→ 用幂等 flag 避免重复扣费;草拟无外部代价(套本地模板)→ 直接挪到 interrupt 之后更干净。
§7读完之后
到这里,本教程结束。回到 起点章那句能力承诺,用两条可验证标准收口:
- 看到一段需求,能在 30 秒内判断"该停在 LCEL 链"还是"该上 LangGraph 状态图",并说出判据与代价——场景 A 做对了吗?
- 给一个要人工审批 / 长任务 / 多 agent 的场景,能选对落地方式(checkpoint+interrupt / sync durable / 三框架选型)并讲清主导约束——场景 B、C、D、E 做对了 ≥3 道吗?
两条都达到,教程目的达到。某条不达到,顺着 01 / 02 / 03 对应章节回查,而不是把本章答案再读一遍。
验证对 LangGraph 的理解,最有效的方式不是再读一遍 LangGraph 教程,而是用不是它的东西把同一个用例实现一遍。§5 的进阶挑战就是这件事:挑场景 D 里最熟的需求,先用裸 while 循环写一版,再用 LangGraph 写一版,对比代码长度、担心的边界条件数量、以及"再加一个审批节点"的改动量。对比之后,checkpointer 在心里值多少钱,自有答案。