Agent开发第9步:持久化与 Checkpoint

第1步:抽象模型接口

第2步:定义工具抽象

第3步:实现 Agent Loop

第4步:定义 RunEvent

第5步:实现 Run Store

第6步:实现 SSE 编解码

第7步:创建订阅与打断接口

第8步:开发简易客户端

第9步:持久化与 Checkpoint

第10步:异常处理与测试

进入到"生产化(Production)"阶段,我们在第5步中编写的 InMemoryRunStore 显然是不够用的:只要服务器一重启,所有的对话历史和执行状态就会灰飞烟灭。

为了让 Agent 能够稳定运行并支持长线任务,我们需要接入真正的数据库,并引入**Checkpoint(检查点)**机制。

1. 为什么需要 Checkpoint?

在长程推理任务(如写代码、做深度研究)中,Agent Loop 可能会循环几十次。如果在这期间,服务器由于 OOM(内存溢出)被系统杀掉,或者发生了停机更新,会导致整个任务失败。

Checkpoint 机制 是指:在 Agent Loop 的每一个关键步骤(例如思考结束、工具调用完成),将当前的状态快照序列化并保存到持久化存储(数据库)中。

如果进程崩溃,重启后调度器可以读取最新的 Checkpoint,从断点处继续执行
#mermaid-svg-jYR7gzWEQTSheeC1{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-jYR7gzWEQTSheeC1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jYR7gzWEQTSheeC1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jYR7gzWEQTSheeC1 .error-icon{fill:#552222;}#mermaid-svg-jYR7gzWEQTSheeC1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jYR7gzWEQTSheeC1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jYR7gzWEQTSheeC1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jYR7gzWEQTSheeC1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jYR7gzWEQTSheeC1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jYR7gzWEQTSheeC1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jYR7gzWEQTSheeC1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jYR7gzWEQTSheeC1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jYR7gzWEQTSheeC1 .marker.cross{stroke:#333333;}#mermaid-svg-jYR7gzWEQTSheeC1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jYR7gzWEQTSheeC1 p{margin:0;}#mermaid-svg-jYR7gzWEQTSheeC1 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-jYR7gzWEQTSheeC1 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-jYR7gzWEQTSheeC1 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-jYR7gzWEQTSheeC1 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-jYR7gzWEQTSheeC1 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-jYR7gzWEQTSheeC1 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-jYR7gzWEQTSheeC1 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-jYR7gzWEQTSheeC1 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-jYR7gzWEQTSheeC1 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-jYR7gzWEQTSheeC1 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jYR7gzWEQTSheeC1 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-jYR7gzWEQTSheeC1 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-jYR7gzWEQTSheeC1 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-jYR7gzWEQTSheeC1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jYR7gzWEQTSheeC1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jYR7gzWEQTSheeC1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jYR7gzWEQTSheeC1 .edgeLabel .label text{fill:#333;}#mermaid-svg-jYR7gzWEQTSheeC1 .label div .edgeLabel{color:#333;}#mermaid-svg-jYR7gzWEQTSheeC1 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-jYR7gzWEQTSheeC1 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-jYR7gzWEQTSheeC1 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-jYR7gzWEQTSheeC1 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-jYR7gzWEQTSheeC1 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-jYR7gzWEQTSheeC1 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jYR7gzWEQTSheeC1 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jYR7gzWEQTSheeC1 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jYR7gzWEQTSheeC1 .cluster-label,#mermaid-svg-jYR7gzWEQTSheeC1 .nodeLabel{color:#131300;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-jYR7gzWEQTSheeC1 .note-edge{stroke-dasharray:5;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-note text{fill:black;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagram .edgeLabel{color:red;}#mermaid-svg-jYR7gzWEQTSheeC1 #dependencyStart,#mermaid-svg-jYR7gzWEQTSheeC1 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-jYR7gzWEQTSheeC1 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jYR7gzWEQTSheeC1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进程突然崩溃 ❌
从断点恢复
Start
Save_Checkpoint_1
Think
Call_Tool
Save_Checkpoint_2
运维重启服务
Restore_Checkpoint_2
Return_Tool_Result
Think_Again
Final_Answer

2. 数据库选型与架构

持久化存储通常需要保存两类数据:

  1. 结构化数据:如 Run 的状态(pending/running/completed)、时间戳等。适合关系型数据库(如 PostgreSQL)。
  2. 半结构化/文档数据:如对话历史 (messages)、事件流序列 (events)、状态快照。适合文档型或 NoSQL 数据库(如 MongoDB, Redis, 或者是 PostgreSQL 的 JSONB 字段)。

在这里,我们以一个抽象的 DatabaseStore 和 PostgreSQL/JSONB 的思路来进行演示。

3. Python 代码实现:持久化 Store

python 复制代码
import json
from typing import Optional
# 假设我们使用了 SQLAlchemy 等 ORM
# from sqlalchemy.orm import Session
# from models import RunRecord

class PostgresRunStore:
    def __init__(self, db_session):
        self.db = db_session

    def create_run(self, run_id: str, initial_message: str):
        """在数据库中插入一条新的运行记录"""
        # record = RunRecord(
        #     id=run_id, 
        #     status="pending",
        #     messages=json.dumps([{"role": "user", "content": initial_message}]),
        #     events="[]"
        # )
        # self.db.add(record)
        # self.db.commit()
        pass

    def save_checkpoint(self, run_id: str, messages: list, current_state: dict):
        """
        保存检查点
        更新数据库中的 messages 列表和当前的内部状态
        """
        # record = self.db.query(RunRecord).filter_by(id=run_id).first()
        # record.messages = json.dumps(messages)
        # record.state_snapshot = json.dumps(current_state)
        # self.db.commit()
        print(f"💾 [DB] 已保存 Run {run_id} 的 Checkpoint。")

    def load_checkpoint(self, run_id: str) -> Optional[dict]:
        """加载最新的检查点用于恢复"""
        # record = self.db.query(RunRecord).filter_by(id=run_id).first()
        # if record and record.status != "completed":
        #     return {
        #         "messages": json.loads(record.messages),
        #         "state_snapshot": json.loads(record.state_snapshot)
        #     }
        return None

4. 将 Checkpoint 接入 Agent Loop

在 Agent 的循环中,我们在每一次 ThinkObserve 之后调用 save_checkpoint

python 复制代码
class ResilientAgent:
    def __init__(self, model, tools, store: PostgresRunStore):
        self.model = model
        self.tools = tools
        self.store = store

    def resume_or_run(self, run_id: str):
        # 1. 尝试加载 Checkpoint
        checkpoint = self.store.load_checkpoint(run_id)
        if checkpoint:
            print(f"🔄 正在从 Checkpoint 恢复任务: {run_id}")
            messages = checkpoint["messages"]
            # 恢复其他内部状态...
        else:
            print(f"🚀 开始新任务: {run_id}")
            messages = [{"role": "system", "content": "..."}] # 初始化

        # 2. Agent Loop
        for step in range(5):
            # ... 思考 ...
            response = self.model.chat_with_tools(messages, self.tools.declarations)
            
            # 思考结束,保存快照
            messages.append(response.message_dict)
            self.store.save_checkpoint(run_id, messages, {"step": step, "phase": "thought"})
            
            if response.tool_calls:
                for tool_call in response.tool_calls:
                    # ... 执行工具 ...
                    result = self.tools.execute(tool_call)
                    
                    messages.append({"role": "tool", "content": result})
                    # 工具执行完毕,保存快照
                    self.store.save_checkpoint(run_id, messages, {"step": step, "phase": "tool_executed"})
                continue
                
            # 完成任务
            self.store.update_status(run_id, "completed")
            break

5. 分布式调度(拓展)

结合 Checkpoint,如果你有多个后端节点集群,还可以引入类似于 Celery 或 RabbitMQ 的任务队列。

如果 Node A 在处理任务时宕机,任务队列在超时后会将任务重新分配给 Node B。Node B 接手后,直接从数据库拉取最新的 Checkpoint 即可继续执行,实现真正的高可用 (High Availability)

总结

持久化与 Checkpoint 是区分"玩具项目"与"工业级框架"的分水岭。通过将状态落盘,Agent 具备了穿越重启与宕机的生命力。

但这还不够,在复杂的现实世界里,大模型会胡言乱语,第三方 API 会超时报错。在最后一篇中,我们将探讨异常处理与测试,为 Agent 加上坚固的护甲。

相关推荐
维克兜率天1 小时前
【维克】一个极端值,毁掉了整个因子
大数据·人工智能·python·算法·机器学习
找方案1 小时前
AI+电商:AI导购和AIGC商品图如何改变在线购物
人工智能·aigc
代码方舟1 小时前
零信任架构实战:基于天远企业四要素验证构建自动化B2B供应链金融网关
人工智能·金融·架构·自动化
深圳雨林凯AI1 小时前
图案裂变的品类适配:画面密度、构图焦点与开孔避让的量化拆解
人工智能
海盗12341 小时前
AI 新闻日报 2026-09-11:SWE-2 逼近前沿、宇树开源具身基座、中国自动驾驶欧洲载客
人工智能·开源·自动驾驶
天远API2 小时前
零信任架构实战:基于天远企业四要素验证构建自动化供应商准入网关
运维·人工智能·架构·自动化
估值探索者2 小时前
【AI+量化实战 #05】财报季的信息洪流:用LLM给业绩预告分类+算事件窗口收益
java·c语言·c++·人工智能·python·分类·数据挖掘
yyywxk4 小时前
ICML 2026 目标检测(object detection)方向上接收论文总结
人工智能·目标检测·计算机视觉
vibecoding779 小时前
一文搞懂企业级 API 网关选型:12 个维度、4 类企业、7 步落地
人工智能·大模型·ai编程