225 lines
4.9 KiB
Markdown
225 lines
4.9 KiB
Markdown
# Checkpointer
|
|
|
|
Implementation details for saving and restoring state.
|
|
|
|
## Overview
|
|
|
|
Checkpointer implements the `BaseCheckpointSaver` interface and is responsible for state persistence.
|
|
|
|
## Checkpointer Implementations
|
|
|
|
### 1. MemorySaver (For Experimentation & Testing)
|
|
|
|
Saves checkpoints in memory:
|
|
|
|
```python
|
|
from langgraph.checkpoint.memory import MemorySaver
|
|
|
|
checkpointer = MemorySaver()
|
|
graph = builder.compile(checkpointer=checkpointer)
|
|
|
|
# All data is lost when the process terminates
|
|
```
|
|
|
|
**Use Case**: Local testing, prototyping
|
|
|
|
### 2. SqliteSaver (For Local Development)
|
|
|
|
Saves to SQLite database:
|
|
|
|
```python
|
|
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
|
|
# File-based
|
|
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")
|
|
|
|
# Or from connection object
|
|
import sqlite3
|
|
conn = sqlite3.connect("checkpoints.db")
|
|
checkpointer = SqliteSaver(conn)
|
|
|
|
graph = builder.compile(checkpointer=checkpointer)
|
|
```
|
|
|
|
**Use Case**: Local development, single-user applications
|
|
|
|
### 3. PostgresSaver (For Production)
|
|
|
|
Saves to PostgreSQL database:
|
|
|
|
```python
|
|
from langgraph.checkpoint.postgres import PostgresSaver
|
|
from psycopg_pool import ConnectionPool
|
|
|
|
# Connection pool
|
|
pool = ConnectionPool(
|
|
conninfo="postgresql://user:password@localhost:5432/db"
|
|
)
|
|
|
|
checkpointer = PostgresSaver(pool)
|
|
graph = builder.compile(checkpointer=checkpointer)
|
|
```
|
|
|
|
**Use Case**: Production environments, multi-user applications
|
|
|
|
## BaseCheckpointSaver Interface
|
|
|
|
All checkpointers implement the following methods:
|
|
|
|
```python
|
|
class BaseCheckpointSaver:
|
|
def put(
|
|
self,
|
|
config: RunnableConfig,
|
|
checkpoint: Checkpoint,
|
|
metadata: dict
|
|
) -> RunnableConfig:
|
|
"""Save a checkpoint"""
|
|
|
|
def get_tuple(
|
|
self,
|
|
config: RunnableConfig
|
|
) -> CheckpointTuple | None:
|
|
"""Retrieve a checkpoint"""
|
|
|
|
def list(
|
|
self,
|
|
config: RunnableConfig,
|
|
*,
|
|
before: RunnableConfig | None = None,
|
|
limit: int | None = None
|
|
) -> Iterator[CheckpointTuple]:
|
|
"""Get list of checkpoints"""
|
|
```
|
|
|
|
## Custom Checkpointer
|
|
|
|
Implement your own persistence logic:
|
|
|
|
```python
|
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
|
|
class RedisCheckpointer(BaseCheckpointSaver):
|
|
def __init__(self, redis_client):
|
|
self.redis = redis_client
|
|
|
|
def put(self, config, checkpoint, metadata):
|
|
thread_id = config["configurable"]["thread_id"]
|
|
checkpoint_id = checkpoint["id"]
|
|
|
|
key = f"checkpoint:{thread_id}:{checkpoint_id}"
|
|
self.redis.set(key, serialize(checkpoint))
|
|
|
|
return config
|
|
|
|
def get_tuple(self, config):
|
|
thread_id = config["configurable"]["thread_id"]
|
|
# Retrieve the latest checkpoint
|
|
# ...
|
|
|
|
def list(self, config, before=None, limit=None):
|
|
# Return list of checkpoints
|
|
# ...
|
|
```
|
|
|
|
## Checkpointer Configuration
|
|
|
|
### Namespaces
|
|
|
|
Share the same checkpointer across multiple graphs:
|
|
|
|
```python
|
|
checkpointer = MemorySaver()
|
|
|
|
graph1 = builder1.compile(
|
|
checkpointer=checkpointer,
|
|
name="graph1" # Namespace
|
|
)
|
|
|
|
graph2 = builder2.compile(
|
|
checkpointer=checkpointer,
|
|
name="graph2" # Different namespace
|
|
)
|
|
```
|
|
|
|
### Automatic Propagation
|
|
|
|
Parent graph's checkpointer automatically propagates to subgraphs:
|
|
|
|
```python
|
|
# Set only on parent graph
|
|
parent_graph = parent_builder.compile(checkpointer=checkpointer)
|
|
|
|
# Automatically propagates to child graphs
|
|
```
|
|
|
|
## Checkpoint Management
|
|
|
|
### Deleting Old Checkpoints
|
|
|
|
```python
|
|
# Delete after a certain period (implementation-dependent)
|
|
import datetime
|
|
|
|
cutoff = datetime.datetime.now() - datetime.timedelta(days=30)
|
|
|
|
# Implementation example (SQLite)
|
|
checkpointer.conn.execute(
|
|
"DELETE FROM checkpoints WHERE created_at < ?",
|
|
(cutoff,)
|
|
)
|
|
```
|
|
|
|
### Optimizing Checkpoint Size
|
|
|
|
```python
|
|
class State(TypedDict):
|
|
# Avoid large data
|
|
messages: Annotated[list, add_messages]
|
|
|
|
# Store references only
|
|
large_data_id: str # Actual data in separate storage
|
|
|
|
def node(state: State):
|
|
# Retrieve large data from external source
|
|
large_data = fetch_from_storage(state["large_data_id"])
|
|
# ...
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### Connection Pool (PostgreSQL)
|
|
|
|
```python
|
|
from psycopg_pool import ConnectionPool
|
|
|
|
pool = ConnectionPool(
|
|
conninfo=conn_string,
|
|
min_size=5,
|
|
max_size=20
|
|
)
|
|
|
|
checkpointer = PostgresSaver(pool)
|
|
```
|
|
|
|
### Async Checkpointer
|
|
|
|
```python
|
|
from langgraph.checkpoint.postgres import AsyncPostgresSaver
|
|
|
|
async_checkpointer = AsyncPostgresSaver(async_pool)
|
|
|
|
# Async execution
|
|
async for chunk in graph.astream(input, config):
|
|
print(chunk)
|
|
```
|
|
|
|
## Summary
|
|
|
|
Checkpointer determines how state is persisted. It's important to choose the appropriate implementation for your use case.
|
|
|
|
## Related Pages
|
|
|
|
- [03_memory_management_persistence.md](03_memory_management_persistence.md) - How to use persistence
|
|
- [03_memory_management_store.md](03_memory_management_store.md) - Differences from long-term memory
|