fix(runtime): build_checkpointer asíncrono con AsyncSqliteSaver
Los nodos del grafo son async, así que se invoca con `await graph.ainvoke`, que el SqliteSaver síncrono no soporta (NotImplementedError). Se cambia a AsyncSqliteSaver expuesto como context manager async. Requiere aiosqlite<0.21 (la 0.21 eliminó Connection.is_alive(), usado por langgraph-checkpoint-sqlite 2.x). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,9 @@ structlog>=24.4,<25.0
|
|||||||
httpx>=0.27,<0.28
|
httpx>=0.27,<0.28
|
||||||
langgraph>=0.2.50,<0.3
|
langgraph>=0.2.50,<0.3
|
||||||
langgraph-checkpoint-sqlite>=2.0,<3.0
|
langgraph-checkpoint-sqlite>=2.0,<3.0
|
||||||
|
# AsyncSqliteSaver (langgraph-checkpoint-sqlite 2.x) usa Connection.is_alive(),
|
||||||
|
# eliminado en aiosqlite 0.21. Fijado a la última serie compatible.
|
||||||
|
aiosqlite>=0.20,<0.21
|
||||||
guardrails-ai>=0.5,<0.6
|
guardrails-ai>=0.5,<0.6
|
||||||
presidio-analyzer>=2.2,<3.0
|
presidio-analyzer>=2.2,<3.0
|
||||||
presidio-anonymizer>=2.2,<3.0
|
presidio-anonymizer>=2.2,<3.0
|
||||||
|
|||||||
@@ -1,20 +1,34 @@
|
|||||||
"""Wrapper sobre SqliteSaver de LangGraph. Centraliza el path y el ciclo de vida."""
|
"""Wrapper sobre ``AsyncSqliteSaver`` de LangGraph. Centraliza el path y el ciclo de vida.
|
||||||
|
|
||||||
|
El grafo se invoca con ``await graph.ainvoke(...)`` (los nodos son ``async def``), así
|
||||||
|
que necesitamos el checkpointer **asíncrono** de LangGraph. ``AsyncSqliteSaver`` liga su
|
||||||
|
conexión ``aiosqlite`` al event loop activo, por lo que se expone como context manager
|
||||||
|
asíncrono::
|
||||||
|
|
||||||
|
async with build_checkpointer(data_dir) as cp:
|
||||||
|
graph = build_graph(..., checkpointer=cp)
|
||||||
|
await graph.ainvoke(...)
|
||||||
|
|
||||||
|
La conexión se abre al entrar y se cierra al salir; LangGraph crea las tablas
|
||||||
|
(``setup()``) de forma perezosa en el primer acceso. El estado se persiste en
|
||||||
|
``data_dir/checkpoints.sqlite``, por lo que un ``awaiting_approval`` sobrevive a un
|
||||||
|
reinicio del proceso.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sqlite3
|
from contextlib import AbstractAsyncContextManager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
|
||||||
|
|
||||||
|
|
||||||
def build_checkpointer(data_dir: Path) -> SqliteSaver:
|
def checkpointer_path(data_dir: Path) -> Path:
|
||||||
"""Devuelve un SqliteSaver apuntando a ``data_dir/checkpoints.sqlite``.
|
"""Ruta al fichero SQLite de checkpoints, creando ``data_dir`` si no existe."""
|
||||||
|
|
||||||
La base se crea si no existe. La conexión usa ``check_same_thread=False``
|
|
||||||
porque LangGraph ejecuta nodos en hilos del executor.
|
|
||||||
"""
|
|
||||||
data_dir.mkdir(parents=True, exist_ok=True)
|
data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
db_path = data_dir / "checkpoints.sqlite"
|
return data_dir / "checkpoints.sqlite"
|
||||||
conn = sqlite3.connect(str(db_path), check_same_thread=False)
|
|
||||||
return SqliteSaver(conn)
|
|
||||||
|
def build_checkpointer(data_dir: Path) -> AbstractAsyncContextManager[AsyncSqliteSaver]:
|
||||||
|
"""Context manager asíncrono con un ``AsyncSqliteSaver`` sobre ``data_dir/checkpoints.sqlite``."""
|
||||||
|
return AsyncSqliteSaver.from_conn_string(str(checkpointer_path(data_dir)))
|
||||||
|
|||||||
Reference in New Issue
Block a user