| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- """
- 共享的 LangGraph checkpointer 工厂。
- 原实现使用 MemorySaver(纯内存),服务重启即丢失所有对话状态。
- 这里改用 SQLite 持久化(AsyncSqliteSaver),支持异步调用,对话状态落盘,重启不丢。
- 说明:LangGraph 官方 RedisSaver 依赖 RedisSearch 模块(FT.* 命令),
- 当前 Redis 未安装该模块,故采用 SQLite 持久化作为务实替代。
- 重要:AsyncSqliteSaver 内部绑定 asyncio.Lock + 当前事件循环(get_running_loop),
- 必须在 FastAPI 的 running loop 内初始化(app startup 时调用 init_checkpointer),
- 不能在临时 loop 中创建,否则后续 ainvoke 会报 "attached to a different loop"。
- """
- import logging
- import os
- import aiosqlite
- from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
- logger = logging.getLogger(__name__)
- # SQLite 数据库文件路径:
- # - Docker 容器内 /data 为持久化卷(与 Chroma 同级),但需检查写权限
- # - 本地开发 fallback 到项目 data/ 目录
- # - 可用环境变量 CHECKPOINT_DB_PATH 覆盖
- _DEFAULT_DB_PATH = os.path.join(
- os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
- "data",
- "checkpoints.db",
- )
- # 全局单例(由 init_checkpointer 在 startup 时填充)
- _saver: AsyncSqliteSaver | None = None
- def _resolve_db_path() -> str:
- """解析数据库路径:优先环境变量,其次有写权限的目录。"""
- # 1. 环境变量覆盖
- env_path = os.environ.get("CHECKPOINT_DB_PATH")
- if env_path:
- return env_path
- # 2. 尝试 /data(容器卷),需检查写权限
- if os.path.isdir("/data") and os.access("/data", os.W_OK):
- return "/data/checkpoints.db"
- # 3. fallback 到项目 data 目录
- return _DEFAULT_DB_PATH
- async def init_checkpointer() -> AsyncSqliteSaver:
- """在运行中的事件循环内初始化 AsyncSqliteSaver(app startup 时调用)。"""
- global _saver
- if _saver is not None:
- return _saver
- db_path = _resolve_db_path()
- os.makedirs(os.path.dirname(db_path), exist_ok=True)
- conn = await aiosqlite.connect(db_path)
- saver = AsyncSqliteSaver(conn)
- await saver.setup()
- _saver = saver
- logger.info("LangGraph AsyncSqliteSaver 已初始化: %s", db_path)
- return _saver
- def get_checkpointer() -> AsyncSqliteSaver:
- """获取全局 AsyncSqliteSaver(须已通过 init_checkpointer 在 startup 初始化)。"""
- if _saver is None:
- raise RuntimeError(
- "checkpointer 未初始化,请确保 app startup 已调用 init_checkpointer()"
- )
- return _saver
|