LLM和Agent——专题7:LangGraph基本使用(7)

LangGraph Memory 与 Checkpoint

想象一个场景:你的 Agent 正在执行一个长达 30 分钟的多步骤任务,在其中的第 14 步,模型返回了一个不完整的 JSON 导致解析失败。如果没有 Checkpoint,你只能从头再来一遍,白白浪费了大量的 Token 和时间。更糟糕的是,在面向用户的对话场景中,Agent 每次都"失忆"------忘记了用户三分钟前说过的话,也记不住用户的偏好。这正是 Memory 与 Checkpoint 要解决的核心问题。

本文将深入讲解 LangGraph 中的 Checkpoint 机制和 Memory 系统,并通过可运行的 Python 代码示例,带你掌握如何让 Agent 支持状态持久化、断点恢复、对话记忆与偏好存储。


一、Checkpoint 是什么

Checkpoint 是 LangGraph 在图的每一个"超步"(super-step)完成后自动保存的状态快照。每个 Checkpoint 记录了当前的 State 数据、上一步执行的节点名称、下一步待执行的节点集合、时间戳以及一个递增的步骤编号。

这种设计类似数据库的 Write-Ahead Log 或游戏中的存档机制。LangGraph 的 Checkpoint 天然支持版本回溯:你可以回到历史的任意一个 Checkpoint 并分叉出一条新的执行路径。

从代码角度看,Checkpoint 由 CheckpointTuple 这一数据结构承载。它包含三个核心字段:

  • config:当前 Checkpoint 的配置标识,包括 thread_idcheckpoint_id 等。
  • checkpoint:实际的快照数据,包括 channel_values(即 State)、channel_versionsversions_seen 等。
  • metadata:元信息,包括 source(input / loop / update / interrupt)、step 编号、writes 记录等。

通过持久化这些信息,LangGraph 实现了流程的可恢复性和执行历史的完全可追溯。


图1:Checkpoint 自动保存机制 ------ 每个超步写入状态快照
SQLite/Postgres Checkpointer LangGraph SQLite/Postgres Checkpointer LangGraph #mermaid-svg-c4d780I5TtHod5O8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c4d780I5TtHod5O8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c4d780I5TtHod5O8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c4d780I5TtHod5O8 .error-icon{fill:#552222;}#mermaid-svg-c4d780I5TtHod5O8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c4d780I5TtHod5O8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c4d780I5TtHod5O8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c4d780I5TtHod5O8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c4d780I5TtHod5O8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c4d780I5TtHod5O8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c4d780I5TtHod5O8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c4d780I5TtHod5O8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c4d780I5TtHod5O8 .marker.cross{stroke:#333333;}#mermaid-svg-c4d780I5TtHod5O8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c4d780I5TtHod5O8 p{margin:0;}#mermaid-svg-c4d780I5TtHod5O8 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-c4d780I5TtHod5O8 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-c4d780I5TtHod5O8 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-c4d780I5TtHod5O8 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-c4d780I5TtHod5O8 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-c4d780I5TtHod5O8 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-c4d780I5TtHod5O8 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-c4d780I5TtHod5O8 .sequenceNumber{fill:white;}#mermaid-svg-c4d780I5TtHod5O8 #sequencenumber{fill:#333;}#mermaid-svg-c4d780I5TtHod5O8 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-c4d780I5TtHod5O8 .messageText{fill:#333;stroke:none;}#mermaid-svg-c4d780I5TtHod5O8 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-c4d780I5TtHod5O8 .labelText,#mermaid-svg-c4d780I5TtHod5O8 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-c4d780I5TtHod5O8 .loopText,#mermaid-svg-c4d780I5TtHod5O8 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-c4d780I5TtHod5O8 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-c4d780I5TtHod5O8 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-c4d780I5TtHod5O8 .noteText,#mermaid-svg-c4d780I5TtHod5O8 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-c4d780I5TtHod5O8 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-c4d780I5TtHod5O8 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-c4d780I5TtHod5O8 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-c4d780I5TtHod5O8 .actorPopupMenu{position:absolute;}#mermaid-svg-c4d780I5TtHod5O8 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-c4d780I5TtHod5O8 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-c4d780I5TtHod5O8 .actor-man circle,#mermaid-svg-c4d780I5TtHod5O8 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-c4d780I5TtHod5O8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 如果此时崩溃,可从超步2恢复 Node A 执行完成 (超步1)保存 State 快照写入 checkpoint (channel_values, versions, metadata)✓Node B 执行完成 (超步2)保存 State 快照写入 checkpoint✓Node C 开始执行...

图2:Checkpoint 的六大核心价值
#mermaid-svg-YIyntGlkoo3MoaMY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YIyntGlkoo3MoaMY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YIyntGlkoo3MoaMY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YIyntGlkoo3MoaMY .error-icon{fill:#552222;}#mermaid-svg-YIyntGlkoo3MoaMY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YIyntGlkoo3MoaMY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YIyntGlkoo3MoaMY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YIyntGlkoo3MoaMY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YIyntGlkoo3MoaMY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YIyntGlkoo3MoaMY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YIyntGlkoo3MoaMY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YIyntGlkoo3MoaMY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YIyntGlkoo3MoaMY .marker.cross{stroke:#333333;}#mermaid-svg-YIyntGlkoo3MoaMY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YIyntGlkoo3MoaMY p{margin:0;}#mermaid-svg-YIyntGlkoo3MoaMY .edge{stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .section--1 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section--1 path,#mermaid-svg-YIyntGlkoo3MoaMY .section--1 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section--1 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section--1 text{fill:#ffffff;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth--1{stroke-width:17;}#mermaid-svg-YIyntGlkoo3MoaMY .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-0 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-0 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-0 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-0 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-0 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-0{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-0{stroke-width:14;}#mermaid-svg-YIyntGlkoo3MoaMY .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-1 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-1 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-1 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-1 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-1 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-1{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-1{stroke-width:11;}#mermaid-svg-YIyntGlkoo3MoaMY .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-2 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-2 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-2 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-2 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-2 text{fill:#ffffff;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-2{stroke-width:8;}#mermaid-svg-YIyntGlkoo3MoaMY .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-3 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-3 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-3 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-3 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-3 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-3{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-3{stroke-width:5;}#mermaid-svg-YIyntGlkoo3MoaMY .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-4 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-4 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-4 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-4 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-4 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-4{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-4{stroke-width:2;}#mermaid-svg-YIyntGlkoo3MoaMY .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-5 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-5 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-5 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-5 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-5 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-5{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-5{stroke-width:-1;}#mermaid-svg-YIyntGlkoo3MoaMY .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-6 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-6 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-6 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-6 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-6 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-6{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-6{stroke-width:-4;}#mermaid-svg-YIyntGlkoo3MoaMY .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-7 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-7 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-7 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-7 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-7 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-7{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-7{stroke-width:-7;}#mermaid-svg-YIyntGlkoo3MoaMY .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-8 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-8 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-8 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-8 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-8 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-8{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-8{stroke-width:-10;}#mermaid-svg-YIyntGlkoo3MoaMY .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-9 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-9 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-9 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-9 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-9 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-9{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-9{stroke-width:-13;}#mermaid-svg-YIyntGlkoo3MoaMY .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-10 rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-10 path,#mermaid-svg-YIyntGlkoo3MoaMY .section-10 circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-10 polygon,#mermaid-svg-YIyntGlkoo3MoaMY .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-10 text{fill:black;}#mermaid-svg-YIyntGlkoo3MoaMY .node-icon-10{font-size:40px;color:black;}#mermaid-svg-YIyntGlkoo3MoaMY .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .edge-depth-10{stroke-width:-16;}#mermaid-svg-YIyntGlkoo3MoaMY .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled,#mermaid-svg-YIyntGlkoo3MoaMY .disabled circle,#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:lightgray;}#mermaid-svg-YIyntGlkoo3MoaMY .disabled text{fill:#efefef;}#mermaid-svg-YIyntGlkoo3MoaMY .section-root rect,#mermaid-svg-YIyntGlkoo3MoaMY .section-root path,#mermaid-svg-YIyntGlkoo3MoaMY .section-root circle,#mermaid-svg-YIyntGlkoo3MoaMY .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-YIyntGlkoo3MoaMY .section-root text{fill:#ffffff;}#mermaid-svg-YIyntGlkoo3MoaMY .section-root span{color:#ffffff;}#mermaid-svg-YIyntGlkoo3MoaMY .section-2 span{color:#ffffff;}#mermaid-svg-YIyntGlkoo3MoaMY .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-YIyntGlkoo3MoaMY .edge{fill:none;}#mermaid-svg-YIyntGlkoo3MoaMY .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-YIyntGlkoo3MoaMY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Checkpoint

六大价值
长任务恢复
系统崩溃后继续
网络中断恢复
多轮对话记忆
上下文不丢失
会话持久化
人工介入恢复
interrupt暂停
Command恢复
调试历史状态
回溯任意快照
排查决策原因
失败重试
从成功点重试
分叉走不同路径
状态审计
完整执行轨迹
合规要求

二、Checkpoint 的六大核心价值

在构建企业级 AI 应用时,Checkpoint 的价值远远不止"保存状态"这么简单。以下是它支持的六种关键场景。

1. 长任务恢复。 复杂任务可能需要几十甚至上百步执行。系统崩溃、网络中断或 LLM 限流后,从最近的 Checkpoint 恢复可以避免一切从头开始。

2. 多轮对话记忆。 每次对话轮次都可以刷写 Checkpoint,确保上下文不丢失。用户离开后回来,Agent 仍然知道他上一轮说了什么。

3. 人工介入后继续执行。 LangGraph 支持在任意节点中断(interrupt),等待人工审核或修改 State 后再继续。中断和恢复的桥梁正是 Checkpoint。

4. 调试历史状态。 你可以回溯任意历史 Checkpoint,查看当时 State 的精确内容。这在排查"Agent 为什么做了那个决定"时无比重要。

5. 失败重试。 某个节点执行失败后,可以从上一个成功的 Checkpoint 重试,也可以重新分叉走一条不同的路径。

6. 状态审计。 在金融、医疗等高合规要求的场景中,Checkpoint 天然提供了一条可审计的执行轨迹。


三、Memory 是什么

如果说 Checkpoint 解决的是"流程可恢复",那么 Memory 解决的是"Agent 能记住"。

Memory 在 LangGraph 中是建立在 Checkpoint 之上的一个更高层的抽象。它利用 Checkpoint 的持久化能力,在跨对话、跨会话的维度上管理信息。LangGraph 提供了两种开箱即用的 Memory 实现:

  • InMemorySaver:将 Checkpoint 保存在内存的字典中,进程重启即丢失,适合开发和测试。
  • SqliteSaver / AsyncSqliteSaver:将 Checkpoint 持久化到 SQLite 数据库,进程重启后数据仍然保留,适合原型和轻量生产场景。

此外,LangGraph 的 Memory 系统支持为每个 thread_id 维护独立的对话历史。同一个用户可以拥有多个线程(thread),每个线程对应一段独立的上下文。

Memory 的能力可以归纳为三个层次:

  1. 短期记忆:当前对话线程内的消息和 State,通过 Checkpoint 自动持久化。
  2. 长期记忆:跨对话保留的用户偏好、个人档案、历史行为模式,通常需要结合外部存储(如数据库或向量库)来管理。
  3. 语义记忆:从历史交互中提取出来的结构化知识,例如"用户喜欢用中文回答"、"用户的技术栈是 Python + LangChain"。

图3:Memory 的三大层次
#mermaid-svg-3XYq2YcxcSfuVNZc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3XYq2YcxcSfuVNZc .error-icon{fill:#552222;}#mermaid-svg-3XYq2YcxcSfuVNZc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3XYq2YcxcSfuVNZc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3XYq2YcxcSfuVNZc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3XYq2YcxcSfuVNZc .marker.cross{stroke:#333333;}#mermaid-svg-3XYq2YcxcSfuVNZc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3XYq2YcxcSfuVNZc p{margin:0;}#mermaid-svg-3XYq2YcxcSfuVNZc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3XYq2YcxcSfuVNZc .cluster-label text{fill:#333;}#mermaid-svg-3XYq2YcxcSfuVNZc .cluster-label span{color:#333;}#mermaid-svg-3XYq2YcxcSfuVNZc .cluster-label span p{background-color:transparent;}#mermaid-svg-3XYq2YcxcSfuVNZc .label text,#mermaid-svg-3XYq2YcxcSfuVNZc span{fill:#333;color:#333;}#mermaid-svg-3XYq2YcxcSfuVNZc .node rect,#mermaid-svg-3XYq2YcxcSfuVNZc .node circle,#mermaid-svg-3XYq2YcxcSfuVNZc .node ellipse,#mermaid-svg-3XYq2YcxcSfuVNZc .node polygon,#mermaid-svg-3XYq2YcxcSfuVNZc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3XYq2YcxcSfuVNZc .rough-node .label text,#mermaid-svg-3XYq2YcxcSfuVNZc .node .label text,#mermaid-svg-3XYq2YcxcSfuVNZc .image-shape .label,#mermaid-svg-3XYq2YcxcSfuVNZc .icon-shape .label{text-anchor:middle;}#mermaid-svg-3XYq2YcxcSfuVNZc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3XYq2YcxcSfuVNZc .rough-node .label,#mermaid-svg-3XYq2YcxcSfuVNZc .node .label,#mermaid-svg-3XYq2YcxcSfuVNZc .image-shape .label,#mermaid-svg-3XYq2YcxcSfuVNZc .icon-shape .label{text-align:center;}#mermaid-svg-3XYq2YcxcSfuVNZc .node.clickable{cursor:pointer;}#mermaid-svg-3XYq2YcxcSfuVNZc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3XYq2YcxcSfuVNZc .arrowheadPath{fill:#333333;}#mermaid-svg-3XYq2YcxcSfuVNZc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3XYq2YcxcSfuVNZc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3XYq2YcxcSfuVNZc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3XYq2YcxcSfuVNZc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3XYq2YcxcSfuVNZc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3XYq2YcxcSfuVNZc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3XYq2YcxcSfuVNZc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3XYq2YcxcSfuVNZc .cluster text{fill:#333;}#mermaid-svg-3XYq2YcxcSfuVNZc .cluster span{color:#333;}#mermaid-svg-3XYq2YcxcSfuVNZc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3XYq2YcxcSfuVNZc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3XYq2YcxcSfuVNZc rect.text{fill:none;stroke-width:0;}#mermaid-svg-3XYq2YcxcSfuVNZc .icon-shape,#mermaid-svg-3XYq2YcxcSfuVNZc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3XYq2YcxcSfuVNZc .icon-shape p,#mermaid-svg-3XYq2YcxcSfuVNZc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3XYq2YcxcSfuVNZc .icon-shape .label rect,#mermaid-svg-3XYq2YcxcSfuVNZc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3XYq2YcxcSfuVNZc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3XYq2YcxcSfuVNZc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3XYq2YcxcSfuVNZc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 短期记忆

当前对话线程

通过 Checkpoint 自动持久化

━━━━━━━━━━

例:本轮对话的消息历史
长期记忆

跨对话保留

通过 Store 接口管理

━━━━━━━━━━

例:用户偏好、个人档案
语义记忆

结构化知识提取

从历史交互中学习

━━━━━━━━━━

例:用户技术栈=Python+LangChain

四、配置 Checkpointer:让 Agent 具备持久化能力

下面我们通过一个完整的代码示例,展示如何为 LangGraph 图配置 Checkpointer。

4.1 安装依赖

bash 复制代码
pip install langgraph langgraph-checkpoint-sqlite

4.2 定义 State 与图结构

我们构建一个简单的多步骤 Agent:它先分析用户问题,再执行一次模拟的工具调用,最后生成回答。

python 复制代码
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.checkpoint.memory import InMemorySaver


# 定义 State
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    analysis: str
    tool_result: str
    final_answer: str


# 节点 1:分析用户问题
def analyze_question(state: AgentState) -> dict:
    last_message = state["messages"][-1].content
    analysis = f"分析结果:用户问题涉及关键词 '{last_message[:30]}...',需要工具支持"
    return {"analysis": analysis}


# 节点 2:模拟工具调用
def call_tool(state: AgentState) -> dict:
    # 模拟对外部 API 或数据库的调用
    tool_result = "工具返回:查找到 3 条相关信息,置信度分别为 0.92, 0.87, 0.74"
    return {"tool_result": tool_result}


# 节点 3:生成最终回答
def generate_answer(state: AgentState) -> dict:
    analysis = state.get("analysis", "")
    tool_result = state.get("tool_result", "")
    answer = f"基于分析 [{analysis}] 和工具结果 [{tool_result}],这里是一个综合性的回答。"
    return {"final_answer": answer}

4.3 构建图并注入 Checkpointer

python 复制代码
# 构建图
builder = StateGraph(AgentState)

builder.add_node("analyze_question", analyze_question)
builder.add_node("call_tool", call_tool)
builder.add_node("generate_answer", generate_answer)

builder.add_edge(START, "analyze_question")
builder.add_edge("analyze_question", "call_tool")
builder.add_edge("call_tool", "generate_answer")
builder.add_edge("generate_answer", END)

# 使用 SQLite Checkpointer 持久化到文件
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)

    # 配置:每个 thread_id 维护独立的执行历史
    config = {"configurable": {"thread_id": "user-session-001"}}

    # 模拟用户输入
    from langchain_core.messages import HumanMessage
    user_input = {"messages": [HumanMessage(content="请帮我查找 LangGraph 中 Checkpoint 的用法")]}

    # 第一次执行
    result = graph.invoke(user_input, config)
    print("第一次执行完成,State 内容:")
    print(f"  analysis: {result['analysis']}")
    print(f"  tool_result: {result['tool_result']}")
    print(f"  final_answer: {result['final_answer']}")

执行上述代码后,你会在当前目录看到一个 checkpoints.db 文件。所有 Checkpoint 数据都已写入其中。


图4:中断与恢复完整流程
人工审核者 Checkpointer LangGraph 用户/调用方 人工审核者 Checkpointer LangGraph 用户/调用方 #mermaid-svg-LbGguX8TEO8ONJEM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LbGguX8TEO8ONJEM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LbGguX8TEO8ONJEM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LbGguX8TEO8ONJEM .error-icon{fill:#552222;}#mermaid-svg-LbGguX8TEO8ONJEM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LbGguX8TEO8ONJEM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LbGguX8TEO8ONJEM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LbGguX8TEO8ONJEM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LbGguX8TEO8ONJEM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LbGguX8TEO8ONJEM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LbGguX8TEO8ONJEM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LbGguX8TEO8ONJEM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LbGguX8TEO8ONJEM .marker.cross{stroke:#333333;}#mermaid-svg-LbGguX8TEO8ONJEM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LbGguX8TEO8ONJEM p{margin:0;}#mermaid-svg-LbGguX8TEO8ONJEM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LbGguX8TEO8ONJEM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-LbGguX8TEO8ONJEM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LbGguX8TEO8ONJEM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-LbGguX8TEO8ONJEM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-LbGguX8TEO8ONJEM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-LbGguX8TEO8ONJEM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-LbGguX8TEO8ONJEM .sequenceNumber{fill:white;}#mermaid-svg-LbGguX8TEO8ONJEM #sequencenumber{fill:#333;}#mermaid-svg-LbGguX8TEO8ONJEM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-LbGguX8TEO8ONJEM .messageText{fill:#333;stroke:none;}#mermaid-svg-LbGguX8TEO8ONJEM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LbGguX8TEO8ONJEM .labelText,#mermaid-svg-LbGguX8TEO8ONJEM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-LbGguX8TEO8ONJEM .loopText,#mermaid-svg-LbGguX8TEO8ONJEM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-LbGguX8TEO8ONJEM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LbGguX8TEO8ONJEM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-LbGguX8TEO8ONJEM .noteText,#mermaid-svg-LbGguX8TEO8ONJEM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-LbGguX8TEO8ONJEM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LbGguX8TEO8ONJEM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LbGguX8TEO8ONJEM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LbGguX8TEO8ONJEM .actorPopupMenu{position:absolute;}#mermaid-svg-LbGguX8TEO8ONJEM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-LbGguX8TEO8ONJEM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LbGguX8TEO8ONJEM .actor-man circle,#mermaid-svg-LbGguX8TEO8ONJEM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-LbGguX8TEO8ONJEM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} graph.invoke(initial_state)执行节点 generate_draft进入 human_review 节点interrupt("请审核...")保存 State 快照返回中断事件 __interrupt__通知审核(邮件/IM)审核内容,做出决定审核结果Command(resume={"decision": "approved"})加载 State 快照恢复 State从 human_review 继续执行执行后续节点返回最终结果

五、从 Checkpoint 恢复:中断后继续执行

下面演示如何"模拟中断"并从 Checkpoint 恢复。

python 复制代码
# 继续使用同一个 thread_id,LangGraph 会自动从最新的 Checkpoint 继续
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    config = {"configurable": {"thread_id": "user-session-001"}}

    # 查看当前线程的状态历史
    print("=== 当前线程的 Checkpoint 历史 ===")
    for checkpoint_tuple in checkpointer.list(config):
        cp = checkpoint_tuple.checkpoint
        print(f"  Checkpoint {cp['id'][:8]}... | step={checkpoint_tuple.metadata.get('step')} | "
              f"source={checkpoint_tuple.metadata.get('source')}")

    # 发送第二条消息------Agent 将重用之前的上下文
    user_input2 = {"messages": [HumanMessage(content="那个 0.92 置信度的结果,请详细解释一下")]}
    result2 = graph.invoke(user_input2, config)

    print("\n第二轮执行完成,最终回答:")
    print(f"  {result2['final_answer']}")

此时你会发现,Agent 能够理解"那个 0.92 置信度的结果"指的是上一轮工具调用中的第一条记录。这正是因为 Checkpoint 保留了完整的执行上下文。

图5:Checkpoint 回溯分叉 ------ 从历史快照创建新分支
#mermaid-svg-bfbpVk9Up3TBPzz5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bfbpVk9Up3TBPzz5 .error-icon{fill:#552222;}#mermaid-svg-bfbpVk9Up3TBPzz5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bfbpVk9Up3TBPzz5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bfbpVk9Up3TBPzz5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bfbpVk9Up3TBPzz5 .marker.cross{stroke:#333333;}#mermaid-svg-bfbpVk9Up3TBPzz5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bfbpVk9Up3TBPzz5 p{margin:0;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-id,#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-msg,#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms',verdana,arial,sans-serif;font-family:var(--mermaid-font-family);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label0{fill:#ffffff;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit0{stroke:hsl(240, 100%, 46.2745098039%);fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight0{stroke:hsl(60, 100%, 3.7254901961%);fill:hsl(60, 100%, 3.7254901961%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label0{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow0{stroke:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label1{fill:black;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit1{stroke:hsl(60, 100%, 43.5294117647%);fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight1{stroke:rgb(0, 0, 160.5);fill:rgb(0, 0, 160.5);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label1{fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow1{stroke:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label2{fill:black;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit2{stroke:hsl(80, 100%, 46.2745098039%);fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight2{stroke:rgb(48.8333333334, 0, 146.5000000001);fill:rgb(48.8333333334, 0, 146.5000000001);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label2{fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow2{stroke:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label3{fill:#ffffff;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit3{stroke:hsl(210, 100%, 46.2745098039%);fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight3{stroke:rgb(146.5000000001, 73.2500000001, 0);fill:rgb(146.5000000001, 73.2500000001, 0);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label3{fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow3{stroke:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label4{fill:black;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit4{stroke:hsl(180, 100%, 46.2745098039%);fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight4{stroke:rgb(146.5000000001, 0, 0);fill:rgb(146.5000000001, 0, 0);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label4{fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow4{stroke:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label5{fill:black;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit5{stroke:hsl(150, 100%, 46.2745098039%);fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight5{stroke:rgb(146.5000000001, 0, 73.2500000001);fill:rgb(146.5000000001, 0, 73.2500000001);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label5{fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow5{stroke:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label6{fill:black;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit6{stroke:hsl(300, 100%, 46.2745098039%);fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight6{stroke:rgb(0, 146.5000000001, 0);fill:rgb(0, 146.5000000001, 0);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label6{fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow6{stroke:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch-label7{fill:black;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit7{stroke:hsl(0, 100%, 46.2745098039%);fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight7{stroke:rgb(0, 146.5000000001, 146.5000000001);fill:rgb(0, 146.5000000001, 146.5000000001);}#mermaid-svg-bfbpVk9Up3TBPzz5 .label7{fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow7{stroke:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .branch{stroke-width:1;stroke:#333333;stroke-dasharray:2;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-label{font-size:10px;fill:#000021;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-label-bkg{font-size:10px;fill:#ffffde;opacity:0.5;}#mermaid-svg-bfbpVk9Up3TBPzz5 .tag-label{font-size:10px;fill:#131300;}#mermaid-svg-bfbpVk9Up3TBPzz5 .tag-label-bkg{fill:#ECECFF;stroke:hsl(240, 60%, 86.2745098039%);}#mermaid-svg-bfbpVk9Up3TBPzz5 .tag-hole{fill:#333;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-merge{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-reverse{stroke:#ECECFF;fill:#ECECFF;stroke-width:3;}#mermaid-svg-bfbpVk9Up3TBPzz5 .commit-highlight-inner{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-bfbpVk9Up3TBPzz5 .arrow{stroke-width:8;stroke-linecap:round;fill:none;}#mermaid-svg-bfbpVk9Up3TBPzz5 .gitTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bfbpVk9Up3TBPzz5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} main replay 超步1: analyze cp-001 超步2: retrieve 超步3: generate cp-003 生成英文回答 cp-003-fork 超步4: validate 超步5: END

5.1 回溯到特定 Checkpoint 并分叉

这是 Checkpoint 最强大的能力之一:回到历史的某个点,做出不同的决策。

python 复制代码
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    config = {"configurable": {"thread_id": "user-session-001"}}

    # 获取所有历史 Checkpoint
    history = list(checkpointer.list(config))

    # 回到第一个 Checkpoint(输入之后的快照)
    if len(history) >= 2:
        first_cp = history[-2]  # 倒数第二个 Checkpoint
        cp_id = first_cp.checkpoint["id"]
        print(f"回溯到 Checkpoint: {cp_id[:12]}...")

        # 使用 checkpoint_id 指定从哪个快照继续
        replay_config = {
            "configurable": {
                "thread_id": "user-session-replay",
                "checkpoint_id": cp_id,
            }
        }

        # 分叉执行:从同一个起点走一条不同的路径
        replay_input = {"messages": [HumanMessage(content="这次请用英文回答我")]}
        replay_result = graph.invoke(replay_input, replay_config)
        print(f"\n分叉执行结果:{replay_result['final_answer']}")

通过指定 checkpoint_id,你可以在任意历史点上创建一个新的执行分支。这在 A/B 测试、调试或探索不同策略时非常有用。


六、Memory 实战:跨对话记住用户偏好

Checkpoint 提供了基础设施,但 Memory 的使用场景更贴近业务。下面的示例展示如何构建一个能记住用户偏好的 Agent。

6.1 扩展 State 以支持 Memory

python 复制代码
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.store.memory import InMemoryStore
from langchain_core.messages import HumanMessage, AIMessage


class MemoryAgentState(TypedDict):
    messages: Annotated[list, add_messages]
    user_preference: str
    response: str


# 节点:识别并更新用户偏好
def detect_preference(state: MemoryAgentState) -> dict:
    """从用户消息中识别偏好,并写入 State"""
    if not state.get("messages"):
        return {"user_preference": "无已知偏好"}

    last_msg = state["messages"][-1]
    content = last_msg.content.lower()

    preference = state.get("user_preference", "")

    # 简单的偏好识别逻辑
    if "中文" in content or "chinese" in content:
        preference = "用户偏好使用中文进行交流"
    elif "简短" in content or "brief" in content:
        preference = "用户偏好简短精炼的回答"
    elif "详细" in content or "detail" in content:
        preference = "用户偏好详细全面的回答"
    elif "代码" in content or "code" in content:
        preference = "用户偏好包含代码示例的回答"

    return {"user_preference": preference or state.get("user_preference", "无已知偏好")}


# 节点:根据偏好生成回答
def generate_preference_aware_response(state: MemoryAgentState) -> dict:
    """基于用户偏好生成不同风格的响应"""
    preference = state.get("user_preference", "无已知偏好")
    messages = state.get("messages", [])

    if not messages:
        return {"response": "请问有什么可以帮您的?"}

    last_content = messages[-1].content

    # 根据偏好调整回答风格
    if "简短" in preference:
        response = f"[精简模式] 关于 '{last_content[:20]}...',答案是:可以。"
    elif "详细" in preference:
        response = (
            f"[详细模式] 关于您的问题 '{last_content}',这里做一个全面分析:\n"
            f"1) 首先,根据您之前的偏好({preference}),我将详细展开...\n"
            f"2) 其次,结合上下文,我会提供多个方案供您选择..."
        )
    elif "代码" in preference:
        response = (
            f"[代码模式] 针对 '{last_content}',以下是相关代码示例:\n"
            f"```python\n# 这是一个示例代码片段\ndef hello():\n    print('Hello, World!')\n```"
        )
    else:
        response = f"[默认模式] 关于 '{last_content}',这是标准回答。当前已知偏好:{preference}"

    return {"response": response}

6.2 构建 Memory-Aware 图

python 复制代码
def build_memory_graph():
    builder = StateGraph(MemoryAgentState)

    builder.add_node("detect_preference", detect_preference)
    builder.add_node("generate_response", generate_preference_aware_response)

    builder.add_edge(START, "detect_preference")
    builder.add_edge("detect_preference", "generate_response")
    builder.add_edge("generate_response", END)

    return builder


# 使用 SQLite 持久化,确保跨会话保留 Memory
with SqliteSaver.from_conn_string("memory_demo.db") as checkpointer:
    memory_builder = build_memory_graph()
    memory_graph = memory_builder.compile(checkpointer=checkpointer)

    # 场景 1:用户告知偏好
    config_user_a = {"configurable": {"thread_id": "user-a"}}
    result1 = memory_graph.invoke(
        {"messages": [HumanMessage(content="你好,我希望以后都用简短的方式回答我")]},
        config_user_a,
    )
    print(f"[偏好识别] {result1['user_preference']}")
    print(f"[回答] {result1['response']}")
    print()

    # 场景 2:同一个用户继续提问------偏好被记住
    result2 = memory_graph.invoke(
        {"messages": [HumanMessage(content="LangGraph 是什么?")]},
        config_user_a,
    )
    print(f"[偏好保留] {result2['user_preference']}")
    print(f"[回答] {result2['response']}")
    print()

    # 场景 3:另一个用户------独立的偏好空间
    config_user_b = {"configurable": {"thread_id": "user-b"}}
    result3 = memory_graph.invoke(
        {"messages": [HumanMessage(content="请详细解释一下什么是状态图")]},
        config_user_b,
    )
    print(f"[用户B 偏好] {result3['user_preference']}")
    print(f"[回答] {result3['response']}")

    # 验证隔离性:用户A 的偏好不应影响用户B
    print(f"\n用户A 偏好仍为: {memory_graph.get_state(config_user_a).values.get('user_preference')}")
    print(f"用户B 偏好: {memory_graph.get_state(config_user_b).values.get('user_preference')}")

输出将清晰地展示:用户 A 的"简短"偏好在其后续对话中被持续记住,而用户 B 拥有完全独立的状态空间。不同 thread_id 之间的 Memory 是完全隔离的。


图6:不同 thread_id 的 Checkpoint 空间隔离
#mermaid-svg-qZlDDn9VMLUxfJHY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qZlDDn9VMLUxfJHY .error-icon{fill:#552222;}#mermaid-svg-qZlDDn9VMLUxfJHY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qZlDDn9VMLUxfJHY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qZlDDn9VMLUxfJHY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qZlDDn9VMLUxfJHY .marker.cross{stroke:#333333;}#mermaid-svg-qZlDDn9VMLUxfJHY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qZlDDn9VMLUxfJHY p{margin:0;}#mermaid-svg-qZlDDn9VMLUxfJHY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qZlDDn9VMLUxfJHY .cluster-label text{fill:#333;}#mermaid-svg-qZlDDn9VMLUxfJHY .cluster-label span{color:#333;}#mermaid-svg-qZlDDn9VMLUxfJHY .cluster-label span p{background-color:transparent;}#mermaid-svg-qZlDDn9VMLUxfJHY .label text,#mermaid-svg-qZlDDn9VMLUxfJHY span{fill:#333;color:#333;}#mermaid-svg-qZlDDn9VMLUxfJHY .node rect,#mermaid-svg-qZlDDn9VMLUxfJHY .node circle,#mermaid-svg-qZlDDn9VMLUxfJHY .node ellipse,#mermaid-svg-qZlDDn9VMLUxfJHY .node polygon,#mermaid-svg-qZlDDn9VMLUxfJHY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qZlDDn9VMLUxfJHY .rough-node .label text,#mermaid-svg-qZlDDn9VMLUxfJHY .node .label text,#mermaid-svg-qZlDDn9VMLUxfJHY .image-shape .label,#mermaid-svg-qZlDDn9VMLUxfJHY .icon-shape .label{text-anchor:middle;}#mermaid-svg-qZlDDn9VMLUxfJHY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qZlDDn9VMLUxfJHY .rough-node .label,#mermaid-svg-qZlDDn9VMLUxfJHY .node .label,#mermaid-svg-qZlDDn9VMLUxfJHY .image-shape .label,#mermaid-svg-qZlDDn9VMLUxfJHY .icon-shape .label{text-align:center;}#mermaid-svg-qZlDDn9VMLUxfJHY .node.clickable{cursor:pointer;}#mermaid-svg-qZlDDn9VMLUxfJHY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qZlDDn9VMLUxfJHY .arrowheadPath{fill:#333333;}#mermaid-svg-qZlDDn9VMLUxfJHY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qZlDDn9VMLUxfJHY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qZlDDn9VMLUxfJHY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qZlDDn9VMLUxfJHY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qZlDDn9VMLUxfJHY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qZlDDn9VMLUxfJHY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qZlDDn9VMLUxfJHY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qZlDDn9VMLUxfJHY .cluster text{fill:#333;}#mermaid-svg-qZlDDn9VMLUxfJHY .cluster span{color:#333;}#mermaid-svg-qZlDDn9VMLUxfJHY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qZlDDn9VMLUxfJHY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qZlDDn9VMLUxfJHY rect.text{fill:none;stroke-width:0;}#mermaid-svg-qZlDDn9VMLUxfJHY .icon-shape,#mermaid-svg-qZlDDn9VMLUxfJHY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qZlDDn9VMLUxfJHY .icon-shape p,#mermaid-svg-qZlDDn9VMLUxfJHY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qZlDDn9VMLUxfJHY .icon-shape .label rect,#mermaid-svg-qZlDDn9VMLUxfJHY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qZlDDn9VMLUxfJHY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qZlDDn9VMLUxfJHY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qZlDDn9VMLUxfJHY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} thread_id: user-session-B
Checkpoint 1

preference=默认
Checkpoint 2

preference=默认
thread_id: user-session-A
Checkpoint 1

preference=简短
Checkpoint 2

preference=简短
Checkpoint 3

preference=简短
不同 thread_id 的状态

完全隔离,互不影响

七、Memory 的进阶实践

7.1 结合 Store 实现跨会话的长期 Memory

LangGraph 的 Store 接口允许将 Memory 提升到跨线程的层级。这对于需要跨会话全局记忆的场景(如用户档案、全局配置)非常关键。

python 复制代码
from langgraph.store.memory import InMemoryStore

# 创建一个跨会话的全局 Store
user_profile_store = InMemoryStore()

# 在节点中读写 Store
def load_user_profile(state: MemoryAgentState, config, *, store):
    """从 Store 中加载用户长期记忆"""
    user_id = config.get("configurable", {}).get("thread_id", "default")
    profile = store.get(("profiles", user_id))
    if profile:
        return {"user_preference": profile.value.get("preference", "")}
    return {}


def save_user_profile(state: MemoryAgentState, config, *, store):
    """将用户偏好持久化到 Store"""
    user_id = config.get("configurable", {}).get("thread_id", "default")
    preference = state.get("user_preference", "")
    store.put(("profiles", user_id), {"preference": preference})
    return {}

这样一来,同一个用户即使开启了新的 thread,只要 thread_id 不变,其长期偏好就可以从 Store 中恢复。

7.2 Checkpoint 的命名空间隔离

在复杂的多 Agent 系统(如 Supervisor-Worker 模式)中,不同的子图可以声明各自的 Checkpoint 命名空间,避免状态冲突。

python 复制代码
from langgraph.graph import StateGraph
from langgraph.checkpoint.memory import InMemorySaver

# 子图 A ------ 使用独立的命名空间
sub_graph_a = StateGraph(SomeState)
# ... 添加节点和边 ...
compiled_a = sub_graph_a.compile(checkpointer=InMemorySaver())

# 在主图中调用子图时指定命名空间
# 在 add_subgraph 或 add_node 时可以通过 checkpointer 的命名空间机制隔离

合理使用命名空间,可以让复杂应用的状态管理更加清晰和可维护。


图7:InMemorySaver vs SqliteSaver vs PostgresSaver 对比

特性 MemorySaver SqliteSaver PostgresSaver
持久化 ❌ 进程重启丢失 ✅ 文件持久化 ✅ 数据库持久化
并发支持 ❌ 单线程 ⚠️ 有限 ✅ 多实例并发
性能 最快 中等 高并发优秀
部署复杂度 零配置 单文件 需数据库服务
适用场景 开发测试 原型/轻量生产 生产环境

八、生产环境中的 Checkpoint 最佳实践

当你准备将 LangGraph 应用从开发环境推向生产时,Checkpoint 相关的决策会直接影响系统的可靠性和成本。以下是一些关键实践。

1. 选择合适的持久化后端。 开发阶段使用 InMemorySaver 进行快速迭代。原型和轻量生产使用 SqliteSaver。高并发、多人共享场景建议使用 PostgresSaver(LangGraph 提供了官方支持)。

2. 控制 Checkpoint 的数据量。 不要在 State 中存放大量非结构化数据(如完整的文档列表、图片 Base64 等)。每次 Checkpoint 写入都会序列化整个 State,过大的 State 会导致明显的性能下降。将大对象放在外部存储中,State 中仅保留引用 ID。

3. 设置合理的保留策略。 长时间运行的系统会产生大量历史 Checkpoint。可以通过定期清理旧 Checkpoint 来控制存储成本,同时保留关键节点(如输入点、中断点)的快照用于审计。

4. 利用 get_state() 读取特定 Checkpoint。 不要每次都从头执行整个图来获取历史状态。使用 graph.get_state(config) 可以高效地读取任意时间点的 State 快照。

5. 中断与恢复的配合。 在需要人工介入的节点调用 interrupt(),LangGraph 会自动暂存当前 Checkpoint 并暂停执行。人工处理后,使用相同的 config 调用 graph.invoke() 即可无缝恢复。


总结

LangGraph 的 Memory 与 Checkpoint 机制为构建生产级 AI Agent 提供了坚实的基础设施支持。以下是本文的核心要点:

  • Checkpoint 是执行状态的自动快照,在每个超步完成后由 LangGraph 自动写入,支持中断恢复、历史回溯和状态审计。
  • Memory 是建立在 Checkpoint 之上的高层抽象,解决短期上下文保持和长期偏好记忆的问题,让 Agent 真正"有记忆"。
  • SQLite 持久化让 Checkpoint 进程重启不丢失,适合原型和轻量生产;高并发场景可升级为 Postgres 后端。
  • 不同 thread_id 的状态完全隔离 ,确保多用户场景下的安全性和正确性;可通过 Store 实现跨线程的全局记忆。
  • 合理的 State 设计和 Checkpoint 保留策略是生产化过程中不可忽视的环节,直接影响系统性能和运维成本。

掌握了这些机制,你的 Agent 将从"一次性执行"进化为"可恢复、可记忆、可审计"的生产级应用。