"""持久化装配:按配置选择 MongoDB 或进程内存,统一返回会话存储、检查点和关闭函数""" from collections.abc import Awaitable, Callable from dataclasses import dataclass from typing import Any from langgraph.checkpoint.base import BaseCheckpointSaver from loguru import logger from app.config import settings from app.services.conversation import ConversationStore, InMemoryConversationStore @dataclass class Persistence: store: ConversationStore # 为空时由图使用默认的内存检查点 checkpointer: BaseCheckpointSaver | None close: Callable[[], Awaitable[None]] # 启用 MongoDB 时的数据库,供物料等其他集合复用同一连接 db: Any = None async def _close_nothing() -> None: return None async def open_persistence() -> Persistence: if not settings.mongodb_uri: logger.warning( "未配置 MONGODB_URI,会话记录和检查点仅保存在进程内存,重启后丢失" ) return Persistence(InMemoryConversationStore(), None, _close_nothing) # 依赖较重且只在启用 MongoDB 时使用,放到函数内导入 from langgraph.checkpoint.mongodb import MongoDBSaver from pymongo import AsyncMongoClient, MongoClient from app.processor.graph import build_checkpoint_serde from app.services.conversation_mongo import MongoConversationStore # 会话记录使用异步客户端;官方检查点基于同步客户端,异步方法由其放入线程池执行 async_client = AsyncMongoClient(settings.mongodb_uri, serverSelectionTimeoutMS=5000) sync_client = MongoClient(settings.mongodb_uri, serverSelectionTimeoutMS=5000) async def close() -> None: try: await async_client.close() finally: sync_client.close() try: # 启动时探测连接,配置错误尽早暴露;异常信息不包含连接串,避免泄露账号密码 try: await async_client.admin.command("ping") except Exception as exc: raise RuntimeError(f"无法连接 MongoDB:{type(exc).__name__}") from None store = MongoConversationStore(async_client[settings.mongodb_db]) await store.ensure_indexes() # 与内存检查点共用同一份类型白名单,保证读回后仍是业务模型对象 checkpointer = MongoDBSaver( sync_client, settings.mongodb_db, ttl=settings.mongodb_checkpoint_ttl or None, serde=build_checkpoint_serde(), ) except BaseException: await close() raise logger.info("已启用 MongoDB 持久化,库名 {}", settings.mongodb_db) return Persistence(store, checkpointer, close, async_client[settings.mongodb_db])