Просмотр исходного кода

feat(langgraph): 对话 checkpointer 从 MemorySaver 改为 SQLite 持久化,重启不丢会话

Xiaogang Liao 1 месяц назад
Родитель
Сommit
bf028687b1

+ 50 - 0
cfc-langgraph/app/checkpointer.py

@@ -0,0 +1,50 @@
+"""
+共享的 LangGraph checkpointer 工厂。
+
+原实现使用 MemorySaver(纯内存),服务重启即丢失所有对话状态。
+这里改用 SQLite 持久化(SqliteSaver),对话状态落盘,重启不丢。
+
+说明:LangGraph 官方 RedisSaver 依赖 RedisSearch 模块(FT.* 命令),
+当前 Redis 未安装该模块,故采用 SQLite 持久化作为务实替代。
+"""
+import logging
+import os
+import sqlite3
+import threading
+
+from langgraph.checkpoint.sqlite import SqliteSaver
+
+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",
+)
+
+# 线程本地存储:每个线程维护独立的 SqliteSaver 实例
+# check_same_thread=False 允许跨线程共享 sqlite3 连接
+# SqliteSaver 内部有自己的 threading.Lock() 保证序列化写入
+_local = threading.local()
+
+
+def get_checkpointer() -> SqliteSaver:
+    """获取当前线程的 SqliteSaver 实例(线程安全)。"""
+    db_path = os.environ.get(
+        "CHECKPOINT_DB_PATH",
+        "/data/checkpoints.db" if os.path.isdir("/data") else _DEFAULT_DB_PATH,
+    )
+    os.makedirs(os.path.dirname(db_path), exist_ok=True)
+
+    if not hasattr(_local, "saver"):
+        conn = sqlite3.connect(db_path, check_same_thread=False)
+        saver = SqliteSaver(conn)
+        saver.setup()
+        _local.saver = saver
+        logger.info("LangGraph checkpointer 已初始化: %s", db_path)
+
+    return _local.saver

+ 2 - 2
cfc-langgraph/app/graphs/chat_graph.py

@@ -1,6 +1,6 @@
 from typing import TypedDict, Literal
 from langgraph.graph import StateGraph, START, END
-from langgraph.checkpoint.memory import MemorySaver
+from app.checkpointer import get_checkpointer
 from langchain_openai import ChatOpenAI
 from langchain_core.messages import SystemMessage, HumanMessage
 from app.agents.intent_classifier import IntentClassifier, Intent
@@ -213,7 +213,7 @@ def create_chat_graph():
     builder.add_edge("llm_call", "save_memory")
     builder.add_edge("save_memory", END)
 
-    checkpointer = MemorySaver()
+    checkpointer = get_checkpointer()
     graph = builder.compile(checkpointer=checkpointer)
 
     return graph

+ 2 - 2
cfc-langgraph/app/graphs/health_coach_graph.py

@@ -4,7 +4,7 @@ Uses RAG retrieval from health knowledge base.
 """
 from typing import TypedDict
 from langgraph.graph import StateGraph, START, END
-from langgraph.checkpoint.memory import MemorySaver
+from app.checkpointer import get_checkpointer
 from langchain_openai import ChatOpenAI
 from langchain_core.messages import SystemMessage, HumanMessage
 from app.rag.retriever import RagRetriever
@@ -187,7 +187,7 @@ def create_health_coach_graph():
     builder.add_edge("generate_answer", "save_memory")
     builder.add_edge("save_memory", END)
 
-    checkpointer = MemorySaver()
+    checkpointer = get_checkpointer()
     graph = builder.compile(checkpointer=checkpointer)
 
     return graph

+ 1 - 0
cfc-langgraph/requirements.txt

@@ -1,6 +1,7 @@
 fastapi>=0.110,<1.0
 uvicorn[standard]>=0.29,<1.0
 langgraph>=0.2,<1.0
+langgraph-checkpoint-sqlite==3.0.3
 langchain-openai>=0.2,<1.0
 langchain-core>=0.3,<1.0
 chromadb==0.5.23