Revised from direct source code analysis
This commit is contained in:
+158
-56
@@ -7,85 +7,108 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This document details how LangGraph manages state throughout the graph execution lifecycle.
|
||||
This document details how LangGraph manages state, based on direct source code analysis.
|
||||
|
||||
---
|
||||
|
||||
## State Schema
|
||||
## State Definition
|
||||
|
||||
### Typed State
|
||||
### TypedDict Schema
|
||||
|
||||
LangGraph uses Python's `TypedDict` for type-safe state:
|
||||
From `types.py`:
|
||||
|
||||
```python
|
||||
from typing import TypedDict
|
||||
|
||||
class AgentState(TypedDict):
|
||||
messages: list
|
||||
context: dict
|
||||
checkpoint_id: str | None
|
||||
next_action: str
|
||||
```
|
||||
|
||||
### State Flow
|
||||
LangGraph validates state schema at compile time.
|
||||
|
||||
---
|
||||
|
||||
## State Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STATE FLOW IN LANGGRAPH │
|
||||
│ STATE FLOW IN LANGGRAPH │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Input State
|
||||
Input (dict)
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Node A │ ──▶ State Update (via reducer)
|
||||
│ (transform) │
|
||||
└──────────────┘
|
||||
│
|
||||
▼ (messages sent)
|
||||
┌──────────────┐
|
||||
│ Node B │ ──▶ State Update
|
||||
│ (transform) │
|
||||
└──────────────┘
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Superstep N │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Node A │────▶│ Channel │ │
|
||||
│ │ (reads) │◀────│ (update) │ │
|
||||
│ └──────────────┘ └──────────────┘ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ [Checkpoint if enabled] │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Output State
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Superstep N+1 (or return final state) │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reducers
|
||||
|
||||
### What Are Reducers?
|
||||
### How Reducers Work
|
||||
|
||||
Reducers define how state updates are merged when multiple nodes produce updates.
|
||||
Reducers define how multiple updates are merged:
|
||||
|
||||
```python
|
||||
# From graph/state.py
|
||||
def add_messages(left: list, right: list) -> list:
|
||||
return left + right
|
||||
```
|
||||
|
||||
### Built-in Reducers
|
||||
|
||||
| Reducer | Behavior |
|
||||
|---------|----------|
|
||||
| `add_messages` | Append to list |
|
||||
| `operator.or` | Union of sets |
|
||||
| `last` | Last value wins |
|
||||
| Reducer | Function | Behavior |
|
||||
|---------|----------|----------|
|
||||
| `add_messages` | `list + list` | Append |
|
||||
| `last` | `(a, b) => b` | Last wins |
|
||||
| `max` | `max(a, b)` | Maximum |
|
||||
| `min` | `min(a, b)` | Minimum |
|
||||
|
||||
### Custom Reducers
|
||||
|
||||
```python
|
||||
def merge_dicts(left: dict, right: dict) -> dict:
|
||||
"""Merge two dictionaries, with right taking precedence."""
|
||||
result = left.copy()
|
||||
result.update(right)
|
||||
return result
|
||||
from typing import Annotated
|
||||
|
||||
def merge_contexts(a: dict, b: dict) -> dict:
|
||||
return {**a, **b}
|
||||
|
||||
class AgentState(TypedDict):
|
||||
context: Annotated[dict, merge_contexts]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checkpointing
|
||||
|
||||
### How Checkpointing Works
|
||||
### Durability Modes
|
||||
|
||||
1. **Snapshot:** At each checkpoint, serialize full state
|
||||
2. **Store:** Save to backend (SQLite, Postgres, etc.)
|
||||
3. **Resume:** On failure, load from last checkpoint
|
||||
From `types.py`:
|
||||
|
||||
```python
|
||||
Durability = Literal["sync", "async", "exit"]
|
||||
```
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `sync` | Persist before next superstep |
|
||||
| `async` | Persist while next superstep runs |
|
||||
| `exit` | Persist only when graph exits |
|
||||
|
||||
### Checkpoint Metadata
|
||||
|
||||
@@ -93,18 +116,18 @@ def merge_dicts(left: dict, right: dict) -> dict:
|
||||
config = {
|
||||
"configurable": {
|
||||
"thread_id": "user-123",
|
||||
"checkpoint_id": "checkpoint-abc123"
|
||||
"checkpoint_id": "1ef-abc123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Checkpoint Backends
|
||||
|
||||
| Backend | Use Case |
|
||||
|---------|----------|
|
||||
| **Memory** | Testing, short-lived |
|
||||
| **SQLite** | Single machine, local |
|
||||
| **Postgres** | Production, distributed |
|
||||
| Backend | Module | Use Case |
|
||||
|---------|--------|----------|
|
||||
| `InMemorySaver` | `langgraph.checkpoint.memory` | Testing |
|
||||
| `SqliteSaver` | `langgraph.checkpoint.sqlite` | Local dev |
|
||||
| `PostgresSaver` | `langgraph.checkpoint.postgres` | Production |
|
||||
|
||||
---
|
||||
|
||||
@@ -112,32 +135,68 @@ config = {
|
||||
|
||||
### What is a Thread?
|
||||
|
||||
A thread (`thread_id`) represents an isolated conversation or task:
|
||||
A thread (`thread_id`) isolates state:
|
||||
|
||||
```
|
||||
Thread ID: "user-123"
|
||||
├── Checkpoint 1 (checkpoint-001)
|
||||
├── Checkpoint 2 (checkpoint-002)
|
||||
├── Checkpoint 3 (checkpoint-003) ← Current
|
||||
└── State (current)
|
||||
Thread "user-123":
|
||||
├── checkpoint-001 (step 0)
|
||||
├── checkpoint-002 (step 1)
|
||||
├── checkpoint-003 (step 2)
|
||||
└── [current state]
|
||||
```
|
||||
|
||||
### Thread Isolation
|
||||
|
||||
- Each `thread_id` has independent state
|
||||
- Multiple threads can run in parallel
|
||||
- Human-in-the-loop works per-thread
|
||||
- Independent checkpoints per thread
|
||||
- Parallel threads via multiple `thread_id` values
|
||||
- Resume from any checkpoint in a thread
|
||||
|
||||
---
|
||||
|
||||
## Interrupts (Human-in-the-Loop)
|
||||
|
||||
### Interrupt Mechanism
|
||||
|
||||
From `types.py`:
|
||||
|
||||
```python
|
||||
class Interrupt:
|
||||
value: Any
|
||||
when: Literal["during", "after"]
|
||||
```
|
||||
|
||||
### Using Interrupts
|
||||
|
||||
```python
|
||||
from langgraph.types import interrupt
|
||||
|
||||
def human_review(state):
|
||||
# Pause for human input
|
||||
feedback = interrupt({"task": "review", "data": state})
|
||||
return {"feedback": feedback}
|
||||
```
|
||||
|
||||
### Command (Modify State)
|
||||
|
||||
```python
|
||||
from langgraph.types import Command
|
||||
|
||||
def process_with_override(state):
|
||||
return Command(
|
||||
update={"status": "processed"},
|
||||
resume={"feedback": "approved"}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Updates
|
||||
|
||||
### Node Returns
|
||||
|
||||
Nodes return partial state updates:
|
||||
### Node Returns Partial State
|
||||
|
||||
```python
|
||||
def node_a(state):
|
||||
# Return only what this node updates
|
||||
return {"messages": [AIMessage("hello")]}
|
||||
```
|
||||
|
||||
@@ -147,10 +206,53 @@ def node_a(state):
|
||||
Node A returns: {"messages": [msg1], "counter": 1}
|
||||
Node B returns: {"messages": [msg2], "counter": 2}
|
||||
|
||||
After reducer:
|
||||
After reducer (add_messages for messages, last for counter):
|
||||
{"messages": [msg1, msg2], "counter": 2}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Generated for the WE*
|
||||
## Checkpoint Implementation
|
||||
|
||||
### From Source (`pregel/_checkpoint.py`)
|
||||
|
||||
```python
|
||||
def create_checkpoint(
|
||||
channels: dict[str, BaseChannel],
|
||||
versions: dict[str, int],
|
||||
metadata: CheckpointMetadata
|
||||
) -> Checkpoint:
|
||||
"""Create a checkpoint from current channel values."""
|
||||
return {
|
||||
"channel_values": {k: v.checkpoint() for k, v in channels.items()},
|
||||
"channel_versions": versions,
|
||||
"metadata": metadata,
|
||||
}
|
||||
```
|
||||
|
||||
### Resuming from Checkpoint
|
||||
|
||||
```python
|
||||
# Load channels from checkpoint
|
||||
def channels_from_checkpoint(checkpoint: Checkpoint) -> dict:
|
||||
return {
|
||||
k: BaseChannel.from_checkpoint(v)
|
||||
for k, v in checkpoint["channel_values"].items()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Differences from OpenClaw
|
||||
|
||||
| Aspect | LangGraph | OpenClaw |
|
||||
|--------|-----------|----------|
|
||||
| **State Storage** | Channels in memory | Multi-layer memory |
|
||||
| **Persistence** | Checkpoints | Session-memory hook |
|
||||
| **Isolation** | thread_id | Session key |
|
||||
| **Resumption** | checkpoint_id | Session restore |
|
||||
| **Updates** | Reducers | Direct merge |
|
||||
|
||||
---
|
||||
|
||||
*Generated from source code analysis*
|
||||
|
||||
Reference in New Issue
Block a user