Spaces:
Sleeping
Sleeping
| """Conversation memory: a LangGraph SQLite checkpointer keyed by thread id. | |
| The checkpoint DB is deliberately NOT inkference.db. deploy_all_books.sh copies | |
| inkference.db into the PUBLIC HF seed dataset, and conversation history must never | |
| ship with the corpus. It also keeps langgraph's checkpoint-schema churn away from | |
| the corpus schema, and makes the whole thing safe to delete. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import sqlite3 | |
| import threading | |
| from ..config import agent as agent_cfg | |
| logger = logging.getLogger("inkference.agent") | |
| _checkpointer = None | |
| _lock = threading.Lock() | |
| def get_checkpointer(): | |
| """Process-wide checkpointer. Degrades to in-memory rather than failing to boot.""" | |
| global _checkpointer | |
| if _checkpointer is not None: | |
| return _checkpointer | |
| with _lock: | |
| if _checkpointer is not None: | |
| return _checkpointer | |
| try: | |
| from langgraph.checkpoint.sqlite import SqliteSaver | |
| path = agent_cfg.checkpoint_path | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| # One shared connection guarded by langgraph's own lock. This departs | |
| # from store.py's fresh-connection-per-op convention on purpose: | |
| # SqliteSaver owns its connection for the process lifetime, and FastAPI | |
| # runs sync endpoints in a threadpool, hence check_same_thread=False. | |
| conn = sqlite3.connect(str(path), check_same_thread=False) | |
| conn.execute("PRAGMA journal_mode=WAL") | |
| _checkpointer = SqliteSaver(conn) | |
| logger.info("agent: checkpointer=sqlite at %s", path) | |
| except Exception as exc: # missing dep, read-only fs, locked db | |
| from langgraph.checkpoint.memory import MemorySaver | |
| logger.warning( | |
| "agent: sqlite checkpointer unavailable (%s); " | |
| "falling back to in-memory (history lost on restart)", exc, | |
| ) | |
| _checkpointer = MemorySaver() | |
| return _checkpointer | |
| def kind() -> str: | |
| """"sqlite" | "memory" — surfaced in GET /api/health.""" | |
| return type(get_checkpointer()).__name__.replace("Saver", "").lower() | |
| def delete_thread(thread_id: str) -> bool: | |
| """Forget one conversation ("New conversation" in the UI).""" | |
| saver = get_checkpointer() | |
| try: | |
| saver.delete_thread(thread_id) | |
| return True | |
| except Exception as exc: | |
| logger.warning("agent: could not delete thread %r: %s", thread_id, exc) | |
| return False | |