W4D2 学习手册 1.动机2.State3.Node4.Edge5.组图6.ToolNode 7.Checkpointer8.Interrupt9.HITL10.实战11.易错12.资源 达标①达标②自测速记

FDE W4D2 学习手册 · LangGraph

W4 Week1 Day2 · A 级(必须掌握,面试核心)· 4h · 学完能讲清 LangGraph 如何用 State/Node/Edge/Conditional Edge 组成图,以及 Checkpointer/Interrupt 在 HITL 中的作用

本日定位:Day1 讲了 Agent Loop 的"朴素本质"(while 循环)。Day2 升级到工业级编排框架 LangGraph:用"图"(StateGraph)把节点、边、条件分支、工具调用、断点续跑、人工介入组织起来。这是把 Day1 的循环变成"可持久化、可中断、可审计"的生产系统的关键。
学完能回答:① State/Node/Edge/Conditional Edge 怎么组成一张图;② ToolNode 如何把工具挂进图;③ Checkpointer 与 Interrupt 在"人在回路(HITL)"里分别起什么作用;④ 结合特药理赔 Agent 说明哪里用条件边、哪里要断点等人审批。
使用方法:通读原理 → 重点看「面试话术」「易错点」→ 跑一遍 s10 的 StateGraph 代码 → 做自测清单 → 配合《FDE-W4D2-评测题.md》。选中不熟的词可标注(左下★重要 / 右下📌待查)。

一、为什么需要 LangGraph(图编排动机)

1.1 朴素 Loop 的局限

Day1 的手写 Agent Loop 在"玩具场景"够用,但生产里会遇到:循环状态怎么持久化(进程崩了怎么办)?中途要"等人审批"怎么暂停又恢复?多分支怎么表达?这些手写很痛苦。

1.2 LangGraph 的解法

LangGraph 把 Agent 抽象成一张有向图(StateGraph)

LangGraph 不是"又一个 Agent 框架",而是"把控制流显式变成一张图"。它的价值在于:① 流程可可视化、可审计(每笔理赔就是图里一条执行轨迹);② 原生支持持久化(Checkpointer)和中断恢复(Interrupt),这是企业上 Agent 的硬需求;③ 用 Conditional Edge 表达复杂分支,比手写 if/else 清爽且可控——本质是 Day1 循环的"工程化升级"。
LangGraph 把 Agent 画成一张图:节点=步骤,边=流转,状态=共享数据。它解决了手写循环的三大痛点:状态持久化、中途暂停等人、复杂分支。是 Day1 循环的工业级升级。
① LangGraph 不是"比手写 Loop 更牛",而是"更可控、可持久化"——小脚本用手写 Loop 也行,企业系统才需要它。② 别把 LangGraph 和 LangChain 混为一谈:LangChain 是"组件库",LangGraph 是"编排框架",两者互补。③ 图≠一定更灵活,图是开发者编排的确定性骨架,配合条件边才让模型参与分支决策。

二、State(状态)

2.1 原理

State 是一份贯穿整张图的共享数据。每个节点执行后返回对 State 的更新,LangGraph 按Reducer(归约函数)合并到全局 State。最常见的是 messages 字段,用"追加(append)"作为 reducer——这就是 Day1 里那个不断增长的 messages 列表。

from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END

class ClaimState(TypedDict):
    messages: Annotated[list, add_messages]   # 追加式 reducer
    policy_no: str
    risk_level: str
    need_human: bool

2.2 Reducer 的关键

State 的设计决定"节点之间怎么传数据"。生产里把"会变长、要累积"的东西(对话、工具结果)用 append reducer;把"单值结论"(risk_level、need_human)用 replace。特药理赔里 messages 累积每轮对话,risk_level 是最终结论覆盖即可。Reducer 选错(本该 append 用了 replace)会丢历史,是经典 bug。
State 是图的共享数据,节点返回更新、按 reducer 合并。messages 用 append(累积),结论字段用 replace(覆盖)。Reducer 选错会丢历史。
① append reducer 是 LangGraph 的"魔法点",新手常以为节点返回会整体覆盖 State,其实 append 字段是累加——若想覆盖要显式返回新列表或用 replace。② State 字段越多越重,每步都序列化,太大的字段(如 50KB OCR 原文)会拖慢且占存储,应只存 id/摘要。③ TypedDict 只是静态类型提示,运行时靠 reducer 决定合并方式,两者要一致。

三、Node(节点)

3.1 定义

Node 是一个 Python 函数(或 Runnable),签名 (state) -> 部分更新。它读 State、做计算、返回要合并进 State 的字段。

def call_model(state: ClaimState):
    resp = model.invoke(state["messages"])
    return {"messages": [resp]}          # append 到 messages

def check_completeness(state: ClaimState):
    ok = bool(state.get("policy_no"))
    return {"need_human": not ok}        # replace 覆盖

3.2 节点类型

节点作用
LLM 节点调模型决策 / 生成
Tool 节点执行工具(常配合 ToolNode)
逻辑/校验节点纯函数:完整性检查、规则校验
人工节点(human node)中断点,等人输入(配合 Interrupt)
Node 就是一个 (state)→更新 的函数。图里可以有 LLM 节点、工具节点、校验节点、人工节点。每个节点只干一件事,职责清晰。

四、Edge(边)与 Conditional Edge(条件边)

4.1 普通 Edge

固定从一个节点走到另一个节点:graph.add_edge("node_a", "node_b")。对应"必然的下一步"。

4.2 Conditional Edge(条件边)

根据 State 动态选择下一个节点:add_conditional_edges("node_a", 路由函数, {"分支1":"node_b","分支2":"node_c"})。路由函数读 State,返回分支名。

def route_after_model(state):
    last = state["messages"][-1]
    if last.tool_calls:        # 模型要调工具
        return "tools"
    return "risk_check"        # 否则去风险校验

builder.add_conditional_edges("call_model", route_after_model,
    {"tools":"call_tools", "risk_check":"risk_check"})

4.3 为什么重要

条件边是"让模型参与控制流"的机制:模型输出决定走哪条边,这正是 Day1 说的"模型动态决策下一步"在图里的表达。普通边=确定性 Workflow,条件边=把决策点交给模型=Agentic Workflow。

普通边是"写死的流程"(Workflow),条件边是"按 State 选分支"(让模型/逻辑决策)。特药理赔里:call_model 后用量边——要工具走 tools,否则走 risk_check;check_completeness 后按 need_human 决定走人工节点还是继续。条件边让图既可控又灵活。
普通边固定走向(Workflow 部分);条件边按 State 动态选分支(Agent 部分,模型决策体现在路由函数的依据上)。图 = 普通边(骨架) + 条件边(决策点)。
① 条件边路由函数必须返回在映射表里声明的分支名,返回未注册的分支会报错。② 路由函数应是纯函数、可重放(中断恢复时会再跑一次),不能有副作用(别在里面发短信)。③ 条件边不等于"模型直接选节点"——是"函数读 State(含模型输出)来选",模型间接驱动,这点面试要讲清。

五、StateGraph 如何组成一张图

5.1 拼装步骤

定义 State → 建 StateGraph(State) → add_node 加节点 → add_edge / add_conditional_edges 连边 → add_edge(START, 首节点) / add_edge(末节点, END) → compile(checkpointer=...) 得到可运行图
builder = StateGraph(ClaimState)
builder.add_node("call_model", call_model)
builder.add_node("call_tools", ToolNode(tools))
builder.add_node("risk_check", risk_check)
builder.add_node("human_approve", human_node)

builder.add_edge(START, "call_model")
builder.add_conditional_edges("call_model", route_after_model,
    {"tools":"call_tools", "risk_check":"risk_check"})
builder.add_edge("call_tools", "call_model")        # 工具结果回填后再决策
builder.add_conditional_edges("risk_check", route_after_risk,
    {"human":"human_approve", "done":"END"})
builder.add_edge("human_approve", "risk_check")

graph = builder.compile(checkpointer=memory)        # 持久化
组图四步:① 定义 State;② add_node 加节点;③ add_edge/add_conditional_edges 连边(含 START/END);④ compile 时挂 checkpointer。一张图就成形了。

六、ToolNode(工具节点)

6.1 作用

ToolNode 是 LangGraph 内置节点,自动:读取 State 里最后一条 assistant 消息的 tool_calls → 逐个分发到对应工具函数 → 把结果包装成 tool

6.2 工具从哪来

工具通常是 LangChain 的 @tool 装饰函数,或 OpenAI 风格的 function schema。你的 Java 微服务可以包成一个 @tool 函数(内部 HTTP 调你的服务)。

from langchain_core.tools import tool

@tool
def ocr_scan(file_url: str) -> str:
    """识别理赔材料图片,返回结构化字段"""
    return requests.post("http://your-java-svc/ocr", json={"url":file_url}).text

tools = [ocr_scan, query_policy]
tool_node = ToolNode(tools)
ToolNode 让"复用你已有工具"变得自然:Java 微服务、规则引擎包成 @tool,直接挂进图,不用重写。这正是 W4D3 特药理赔 Agent 的基石——编排而非重写。
ToolNode 自动解析 tool_calls、执行工具、回填结果(append 到 messages),省去手写回填。你的 Java 服务包成 @tool 就能挂进去。
① ToolNode 执行失败默认会抛异常中断图——生产要配合 try/except 或 handle_tool_errors 把错误当 tool 消息回填(对应 Day1 的"错误也回填"铁律)。② 工具必须幂等(Day1 考点),因为图可能重放。③ 工具里不要做"人工审批"这类需要暂停的事,那要用 Interrupt,不是 ToolNode。

七、Checkpointer(检查点 / 持久化)

7.1 原理

Checkpointer 是图的"存档器":每执行一步,把当前 State 快照按 thread_id 存起来。这样:

7.2 使用

graph = builder.compile(checkpointer=MemorySaver())   # 或 Sqlite/Postgres
cfg = {"configurable": {"thread_id": "claim_12345"}}
graph.invoke({"messages":[("user","审核这张单")]}, cfg)  # 同 thread_id 共享状态
Checkpointer 是企业敢用 Agent 的底座:① 理赔这种长流程,中途服务重启不能重跑 10 步;② 一笔理赔一个 thread_id,全程状态可追溯、可审计;③ 它是 Interrupt/HITL 的前提。生产用 Postgres/Redis 后端,别用 MemorySaver(重启即丢)。
Checkpointer 按 thread_id 给图"存档"。作用:崩溃断点续跑、同线程多轮共享状态、支撑中断恢复。生产用持久化后端(Postgres/Redis)。
① 不传 checkpointer,图也能跑但不能中断/恢复;要 HITL 必须传。② MemorySaver 仅测试用,生产用 PostgresSaver 等,否则重启丢全部进度。③ thread_id 要业务唯一(如理赔单号),混用会串状态。

八、Interrupt(中断)与 Command(命令)

8.1 原理

interrupt() 在节点里调用,会让图暂停执行并把控制权交回调用方,当前 State 已被 Checkpointer 存下。等人工/外部输入后,用 Command(resume=...) 带着输入"唤醒"图,从断点继续。

def human_approve(state):
    decision = interrupt({"question":"是否批准该特药理赔?", "risk":state["risk_level"]})
    # 图在此暂停;外部调用 graph.invoke(Command(resume="approved"), cfg) 后继续
    return {"approved": decision == "approved"}

graph.invoke(Command(resume=user_input), cfg)   # 带输入恢复

8.2 与 Checkpointer 的关系

Interrupt 负责"暂停",Checkpointer 负责"把暂停前的状态存好",二者配合才能实现 HITL。没有 Checkpointer,Interrupt 一暂停状态就没了,恢复不了。

Interrupt 是"人工审批"的技术实现:高风险的特药理赔不能让模型自己拍板,要在人工节点 interrupt() 暂停、把案件推给审核员,审核员在系统点"批准/驳回"后,前端用 Command(resume=...) 唤醒图继续。整个过程中 Checkpointer 保证状态不丢、可审计。
Interrupt 让图暂停等人(断点),Command(resume=...) 带人工输入唤醒图继续。它必须配 Checkpointer 才能存住暂停前的状态。这是 HITL 的实现方式。
① Interrupt 必须在节点函数里调用,不是在边里;② 恢复时必须用同一个 thread_idCommand(resume=...),否则从不了断点;③ 路由函数/节点会被重放,Interrupt 前的节点可能再执行一次,所以节点要幂等、不能有副作用;④ 别把"人工输入"塞进 ToolNode,那不是暂停,是同步等返回值。

九、HITL(人在回路)的工程含义

HITL = 高风险节点让人工拍板。LangGraph 用"条件边路由到 human 节点 + interrupt 暂停 + Checkpointer 存状态 + Command 恢复"实现,且全程可审计,满足医保合规。

十、实战:构建特药理赔 Agent 的 LangGraph

把 Day1 的 Agent Loop 升级为图,含工具调用、条件分支、人工审批中断。

from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import ToolNode, tools_condition

builder = StateGraph(ClaimState)
builder.add_node("call_model", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_node("risk_check", risk_check)
builder.add_node("human_approve", human_approve)

builder.add_edge(START, "call_model")
builder.add_conditional_edges("call_model", tools_condition)  # 内置:有tool_calls→tools
builder.add_edge("tools", "call_model")
builder.add_conditional_edges("risk_check", route_after_risk,
    {"human":"human_approve", "done":END})
builder.add_edge("human_approve", "risk_check")

graph = builder.compile(checkpointer=MemorySaver())

# 运行
cfg = {"configurable": {"thread_id": "claim_8848"}}
graph.invoke({"messages":[("user","审核 /tmp/claim.png,保单 P123")]}, cfg)
# 若中断在 human_approve,前端拿审核员输入后:
# graph.invoke(Command(resume="approved"), cfg)
对照 Day1:这里的 call_model→tools→call_model 循环正是 Day1 的 Tool Calling Loop;tools_condition 是内置条件边(对应你的 route_after_model);human_approve + interrupt 是 Day1 "降级转人工"的工程化实现。图把这些"显式表达"了。

十一、LangGraph 高频易错点

Reducer 选错:messages 本该 append 却用 replace,历史被覆盖。② 条件边返回未注册分支:路由函数返回值不在映射表,运行时报错。③ 路由函数有副作用:图会重放节点,在里面发短信会重复发送。④ ToolNode 错误不回填:工具异常直接中断图,应配 handle_tool_errors。⑤ Interrupt 没配 Checkpointer:暂停即丢状态,无法恢复。⑥ 恢复用错 thread_id:Command(resume) 必须同一 thread_id。⑦ 节点不幂等:重放时重复副作用。⑧ 把人工审批塞进 ToolNode:那不是暂停 HITL,要用 interrupt。

十二、学习资源(中文 / 非 OpenAI)

先看 B站视频建立"图"的直觉,再用官方 Quick Start 跑通最小例子,最后翻 GitHub 查 Checkpointer/Interrupt 的精确 API。三件套覆盖本日面试。

十三、面试达标线①:State / Node / Edge / Conditional Edge 如何组成图

组件作用在理赔图中举例
State共享数据,按 reducer 合并messages(append) / risk_level(replace)
Node一个计算步骤 (state)→更新call_model / tools / risk_check
Edge固定流转START→call_model;tools→call_model
Conditional Edge按 State 动态选分支call_model 后:有 tool_calls→tools,否则→risk_check
State(共享数据) + Node(步骤) + Edge(固定走向) + Conditional Edge(动态分支) → compile(checkpointer) → 可运行图
达标线①核心:能讲清"State 是共享数据(reducer 合并)、Node 是步骤、Edge 固定走向、Conditional Edge 按 State 选分支",并用一句"四者 compile 成图"收尾,最好结合理赔图举例。

十四、面试达标线②:Checkpointer 与 Interrupt 在 HITL 中的作用

机制角色在 HITL 里的作用
Checkpointer存档器按 thread_id 存每步 State 快照,支撑断点续跑、状态共享、可审计
Interrupt暂停器在 human 节点暂停,把控制权交回调用方,等人工输入
Command(resume)唤醒器带人工输入恢复图,从断点继续
human 节点调用 interrupt() → 图暂停 + Checkpointer 存状态 → 审核员输入 → Command(resume=决策) 同 thread_id 唤醒 → 图从断点继续
三者关系一句话:Interrupt 负责"暂停",Checkpointer 负责"把暂停前的状态存好",Command 负责"带输入唤醒"。没有 Checkpointer,Interrupt 一暂停状态就没了,HITL 无法实现。特药理赔用它在高风险节点让审核员拍板,且全程留痕可审计。
达标线②核心:能说清"Interrupt 暂停、Checkpointer 存状态、Command 带输入恢复"三者协作,并指出"Checkpointer 是 Interrupt 能恢复的前提",结合理赔人工审批举例。

十五、W4D2 自测清单

十六、高频面试题速记卡

Q:LangGraph 的 State 是什么?
贯穿整张图的共享数据,节点返回更新、按 reducer 合并。messages 用 append(累积),结论字段用 replace(覆盖)。
Q:普通 Edge 和 Conditional Edge 区别?
普通边固定走向(Workflow);条件边按 State 动态选分支(模型间接驱动,Agent 部分)。图=普通边骨架+条件边决策点。
Q:ToolNode 帮你做了什么?
自动解析最后一条 assistant 消息的 tool_calls、逐个执行工具、把结果 append 回 messages。省去手写回填,你的 Java 服务包成 @tool 即可挂载。
Q:Checkpointer 有什么用?
按 thread_id 存每步 State 快照:崩溃断点续跑、同线程多轮共享状态、支撑 Interrupt 中断恢复。生产用 Postgres/Redis,别用 MemorySaver。
Q:Interrupt 和 Checkpointer 什么关系?
Interrupt 负责暂停把控制权交回调用方,Checkpointer 负责把暂停前状态存好;没 Checkpointer 就恢复不了。两者配合实现 HITL。
Q:怎么实现人工审批(HITL)?
高风险节点用条件边路由到 human 节点,节点内 interrupt() 暂停,审核员输入后用 Command(resume=决策) 同一 thread_id 唤醒继续。
Q:为什么路由函数不能有副作用?
图会重放节点,路由函数可能再执行一次,里面发短信会重复发送。路由要纯函数。
Q:LangGraph 和手写 Agent Loop 比有何优势?
显式图可可视化/可审计、原生持久化(Checkpointer)、中断恢复(Interrupt),适合企业长流程(如理赔)而非玩具脚本。
Q:工具在图里要注意什么?
必须幂等(图会重放);ToolNode 错误要配置回填而非中断;别把"人工审批"塞进工具,那要用 interrupt。
Q:reducer 选错会怎样?
本该 append 的 messages 用了 replace 会被整体覆盖,丢历史;结论字段反之会不断累积。按"是否累积"选 reducer。
FDE W4D2 学习手册 · LangGraph(面试级)· 配合《FDE-W4D2-评测题.md》自测
📌 待查★ 重要