Python之DeepAgents基于mongodb的检查点

简介: 本文介绍了基于MongoDB实现LangGraph检查点的完整方案:提供同步/异步的CheckpointSaver类,支持线程隔离、索引优化与序列化;包含连接配置、实例化及DeepAgent集成示例,并附带按thread_id查询历史对话消息的实用工具函数。

MongoDB检查点

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Iterator, AsyncIterator

from bson import Binary
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import (
    BaseCheckpointSaver,
    Checkpoint,
    CheckpointMetadata,
    CheckpointTuple,
    ChannelVersions,
)
from motor.motor_asyncio import AsyncIOMotorClient
from pymongo import MongoClient


class MongoDBCheckpointSaver(BaseCheckpointSaver):
    def __init__(
        self,
        uri: str = "mongodb://localhost:27017/",
        db_name: str = "langgraph",
        collection_name: str = "checkpoints"
    ):
        super().__init__()
        self.client = MongoClient(uri)
        self.db = self.client[db_name]
        self.collection = self.db[collection_name]

        self.async_client = AsyncIOMotorClient(uri)
        self.async_db = self.async_client[db_name]
        self.async_collection = self.async_db[collection_name]

        self.collection.create_index("thread_id")
        self.collection.create_index([("thread_id", 1), ("checkpoint_id", 1)], unique=True)

    # -------------------------- 真正源码级正确 --------------------------
    def _serialize(self, obj: Any) -> dict:
        # dumps_typed 返回 (type: str, data: bytes)
        typ, data = self.serde.dumps_typed(obj)
        return {"type": typ, "data": Binary(data)}

    def _deserialize(self, data: dict) -> Any:
        return self.serde.loads_typed((data["type"], data["data"]))

    # ------------------------------ 同步方法 ------------------------------
    def get_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
        thread_id = config["configurable"]["thread_id"]
        doc = self.collection.find_one({"thread_id": thread_id}, sort=[("checkpoint_id", -1)])
        if not doc:
            return None

        return CheckpointTuple(
            config=config,
            checkpoint=self._deserialize(doc["checkpoint"]),
            metadata=self._deserialize(doc["metadata"]),
            parent_config=None,
        )

    def list(
        self,
        config: RunnableConfig | None,
        **kwargs: Any
    ) -> Iterator[CheckpointTuple]:
        query = {}
        if config and "configurable" in config:
            thread_id = config["configurable"].get("thread_id")
            if thread_id:
                query["thread_id"] = thread_id

        for doc in self.collection.find(query, sort=[("checkpoint_id", -1)]):
            yield CheckpointTuple(
                config={"configurable": {"thread_id": doc["thread_id"]}},
                checkpoint=self._deserialize(doc["checkpoint"]),
                metadata=self._deserialize(doc["metadata"]),
            )

    def put(
        self,
        config: RunnableConfig,
        checkpoint: Checkpoint,
        metadata: CheckpointMetadata,
        new_versions: ChannelVersions,
    ) -> RunnableConfig:
        thread_id = config["configurable"]["thread_id"]
        checkpoint_id = checkpoint["id"]

        self.collection.update_one(
            {"thread_id": thread_id, "checkpoint_id": checkpoint_id},
            {
                "$set": {
                    "thread_id": thread_id,
                    "checkpoint_id": checkpoint_id,
                    "checkpoint": self._serialize(checkpoint),
                    "metadata": self._serialize(metadata),
                    "updated_at": datetime.now(timezone.utc),
                }
            },
            upsert=True,
        )
        return config

    def put_writes(self, *args, **kwargs):
        pass

    # ------------------------------ 异步方法 ------------------------------
    async def aget_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
        thread_id = config["configurable"]["thread_id"]
        doc = await self.async_collection.find_one(
            {"thread_id": thread_id}, sort=[("checkpoint_id", -1)]
        )
        if not doc:
            return None

        return CheckpointTuple(
            config=config,
            checkpoint=self._deserialize(doc["checkpoint"]),
            metadata=self._deserialize(doc["metadata"]),
        )

    async def alist(
        self, config: RunnableConfig | None, **kwargs: Any
    ) -> AsyncIterator[CheckpointTuple]:
        query = {}
        if config and "configurable" in config:
            thread_id = config["configurable"].get("thread_id")
            if thread_id:
                query["thread_id"] = thread_id

        cursor = self.async_collection.find(query).sort("checkpoint_id", -1)
        async for doc in cursor:
            yield CheckpointTuple(
                config={"configurable": {"thread_id": doc["thread_id"]}},
                checkpoint=self._deserialize(doc["checkpoint"]),
                metadata=self._deserialize(doc["metadata"]),
            )

    async def aput(
        self,
        config: RunnableConfig,
        checkpoint: Checkpoint,
        metadata: CheckpointMetadata,
        new_versions: ChannelVersions,
    ) -> RunnableConfig:
        thread_id = config["configurable"]["thread_id"]
        checkpoint_id = checkpoint["id"]

        await self.async_collection.update_one(
            {"thread_id": thread_id, "checkpoint_id": checkpoint_id},
            {
                "$set": {
                    "thread_id": thread_id,
                    "checkpoint_id": checkpoint_id,
                    "checkpoint": self._serialize(checkpoint),
                    "metadata": self._serialize(metadata),
                    "updated_at": datetime.now(timezone.utc),
                }
            },
            upsert=True,
        )
        return config

    async def aput_writes(self, *args, **kwargs):
        pass

MongoDB连接配置

import urllib.parse

from util.ConfigManager import config


mongo_ip = config.get("MONGODB").get("IP")
mongo_port = config.get("MONGODB").get("PORT")
mongo_user = config.get("MONGODB").get("USER")
mongo_pwd = config.get("MONGODB").get("PWD")
mongo_db = config.get("MONGODB").get("DB")
mongo_collection = config.get("MONGODB").get("COLLECTION")


def build_connection_string() -> str:
    if mongo_user and mongo_pwd:
        username = urllib.parse.quote_plus(mongo_user)
        password = urllib.parse.quote_plus(mongo_pwd)
        print(f"mongodb://{username}:{password}@{mongo_ip}:{mongo_port}")
        return f"mongodb://{username}:{password}@{mongo_ip}:{mongo_port}"
    else:
        return f"mongodb://{mongo_ip}:{mongo_port}"

检查点实例化

# 实例化一个全局的 MongoDBCheckpointSaver
mongo_db_checkpoint_saver = MongoDBCheckpointSaver(
    uri=build_connection_string(),
    db_name=mongo_db,
    collection_name=mongo_collection
)

创建DeepAgents

session_key = f"{tenant_id}_{session_id}"

if session_key in cls._instances:
    return cls._instances[session_key]

# 设置权限
permissions = [
    FilesystemPermission(
        operations=["read", "write"],
        paths=[f"/workspace/{tenant_id}/{session_id}/**"],
        mode="allow"
    )
]
# 设置后端存储,按租户和会话隔离
backend = CompositeBackend(
    default=StateBackend(),
    routes={
        f"/memories/{tenant_id}/{session_id}/": FilesystemBackend(
            root_dir=f"./memories/{tenant_id}/{session_id}",
            virtual_mode=True
        ),
        f"/workspace/{tenant_id}/{session_id}/": FilesystemBackend(
            root_dir=f"./workspace/{tenant_id}/{session_id}",
            virtual_mode=True
        )
    }
)

# 使用默认系统提示词或自定义提示词
sys_prompt = system_prompt or "You are a research assistant."

# 合并工具列表
tools = custom_tools if custom_tools else [current_data_quality]

# 创建DeepAgent实例
agent = create_deep_agent(
    model=open_ai_client,
    system_prompt=sys_prompt,
    middleware=[
        log_tool_calls
    ],
    tools=tools,
    permissions=permissions,
    backend=backend,
    checkpointer=mongo_db_checkpoint_saver
)

根据回话ID检索对话列表

from bson import Binary
from pymongo import MongoClient
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer

from util.MongoPool import build_connection_string, mongo_db

# 固定序列化器
_SERDE = JsonPlusSerializer()


def decode_typed_item(item: dict) -> dict:
    typ = item["type"]
    data = item["data"]
    return _SERDE.loads_typed((typ, data))


def query_chat_messages(
    thread_id: str,
    mongo_uri="mongodb://localhost:27017/",
    db_name="langgraph",
    collection_name="checkpoints"
):
    client = MongoClient(mongo_uri)
    coll = client[db_name][collection_name]

    # 查最新 checkpoint
    doc = coll.find_one({"thread_id": thread_id}, sort=[("checkpoint_id", -1)])
    if not doc:
        return []

    # 解析
    checkpoint = decode_typed_item(doc["checkpoint"])
    messages = checkpoint.get("channel_values", {}).get("messages", [])

    # 格式化:把 LangChain 消息对象 转成 普通字典
    result = []
    for msg in messages:
        try:
            # 消息对象 → 字典
            msg_dict = msg.model_dump()
            # print("-------------------------------------------------")
            # print(msg_dict)
            # print("-------------------------------------------------")
            role = msg_dict.get("type", "ai")
            content = msg_dict.get("content", "")
            result.append({"role": role, "content": content})
        except Exception as e:
            continue

    return result


# ============ 直接使用 ============
if __name__ == "__main__":
    mongodb_url = build_connection_string()
    db_name = mongo_db
    # 替换成你的 thread_id
    thread_id = "sdrowero234249jdoisjfoiet93xxxxx"

    msgs = query_chat_messages(
        thread_id=thread_id,
        mongo_uri=mongodb_url,
        db_name=db_name
    )
    for item in msgs:
        print(f"【{item['role']}】\n{item['content']}\n")
相关文章
|
5月前
|
对象存储 Python
Python之DeepAgents自动摘要触发
DeepAgents是基于LangChain的智能体框架,核心特性之一为自动摘要功能:当上下文超长导致模型报错时,自动触发摘要压缩,再重试推理。需显式设置`max_input_tokens`参数启用,支持流式输出摘要日志。
503 3
|
搜索推荐
Gitlab----设置页面语言为简体中文
Gitlab----设置页面语言为简体中文
6780 0
Gitlab----设置页面语言为简体中文
|
6月前
|
Java 调度 开发者
Java AQS:JUC 并发体系的底层同步框架基石
AQS(AbstractQueuedSynchronizer)是Java并发包(JUC)的底层核心,以volatile state + CLH双向队列统一实现同步控制。支持独占(如ReentrantLock)与共享(如Semaphore、CountDownLatch)两种模式,通过模板方法封装排队、阻塞/唤醒等通用逻辑,是理解与定制高性能同步组件的关键基石。(239字)
733 7
|
5月前
|
人工智能 Java 数据库
DeepAgents 人工介入实战|LangGraph 实现 Agent 高危工具人工审批
本文详解基于 LangChain+LangGraph+DeepAgents 实现 Python 智能体人工介入实战,配置高风险工具中断审批、状态检查点保存与恢复,支持同意 / 拒绝 / 参数编辑,对比 Spring AI Alibaba 方案,附完整可运行源码与生产落地建议。
705 0
|
5月前
|
人工智能 机器人 中间件
LangChain 生态里的三层抽象:LangGraph、create_agent、Deep Agents
本文对比LangChain生态中三层智能体方案:`create_agent`(开箱即用、适合单轮工具调用)、Deep Agents(预装记忆/沙箱/子Agent,面向复杂长链路任务)和LangGraph(底层图编排引擎,支持分支、中断、持久化等深度定制)。推荐“从高抽象起步,遇瓶颈再下沉”。
554 6
LangChain 生态里的三层抽象:LangGraph、create_agent、Deep Agents
|
5月前
|
数据安全/隐私保护 Python
Python之MCPClient实现
本示例展示FastMCP客户端集成:通过RSA加密认证连接MCP服务,获取工具列表;再将MCP工具动态转为OpenAI格式,供大模型调用。含完整异步初始化、鉴权、工具发现与流式函数调用实现。(239字)
178 0
|
5月前
|
弹性计算 人工智能 运维
阿里云服务器和轻量应用服务器选择指南:性能、适用场景、使用方法对比与选择参考
许多用户在选择阿里云服务器时,由于是初次选择,可能不知道云服务器ECS和轻量应用服务器的区别。本文对比了两者的产品定位、适用场景、产品优势及使用限制:ECS适合企业级用户,处理高并发、大数据等复杂场景;轻量应用服务器则面向个人开发者、学生及中小企业,适合轻量级应用。结合2026年特惠活动,文章提供了选购策略,帮助用户根据身份、需求、技术能力及长期成本选择合适的云服务器。
|
7月前
|
人工智能 编解码 自然语言处理
2026年OpenClaw/Clawdbot极速部署+本地SD1.5文生图实战,打造自定义AI形象
2026年,OpenClaw(原Clawdbot)的功能边界持续拓展,从高效执行办公任务、量化交易辅助,进化到支持本地文生图自定义形象——通过对接Stable Diffusion 1.5(SD1.5),结合realisticVisionV60B1模型与IP-Adapter-FaceID技术,实现“输入文本+参考图”即可生成风格统一、特征一致的专属图像,全程本地运行无API费用,隐私安全与创作自由兼得。
3519 5
|
8月前
|
人工智能 安全
智能体来了从 0 到 1 :核心挑战,是非技术性的认知与场景重构
本文探讨AI智能体从概念到落地的核心瓶颈:非模型能力,而在业务理解与结构化水平。指出智能体本质是“决策执行体”,其成败取决于能否将模糊业务目标拆解为可执行、可校验、可容错的逻辑结构,强调目标对齐、任务拆解、知识显性化与人机协同评估体系。
362 3
|
12月前
|
机器学习/深度学习 编解码 Python
Python图片上采样工具 - RealESRGANer
Real-ESRGAN基于深度学习实现图像超分辨率放大,有效改善传统PIL缩放的模糊问题。支持多种模型版本,推荐使用魔搭社区提供的预训练模型,适用于将小图高质量放大至大图,放大倍率越低效果越佳。
905 3