mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-05 17:27:47 +02:00
fix(channels): address code review feedback on DeltaChannel
delta.py — value consistency:
- `__init__` now starts with `value=MISSING` (was `[]`); both fresh
construction and clones are consistently uninitialised until
`from_checkpoint()` or `copy()` sets the real value
- `_clone_empty` drops `__new__` in favour of the normal constructor;
`typ` and `key` are restored explicitly afterwards (`typ` may differ
from `list` when set via Annotated injection; `key` is injected by
the graph builder after construction)
delta.py — snapshot cadence:
- `is_snapshot_step` now guards `step > 0`; snapshots fire at steps N,
2N, 3N, … instead of also at step 0 where `0 % N == 0` always held
_checkpoint.py — runtime guards:
- replace both `assert isinstance(spec, DeltaChannel)` with proper
`if not isinstance: raise TypeError`; `assert` is stripped by `-O`
and is wrong for production invariant checks
binop.py — avoid unnecessary allocation:
- `_get_overwrite`: replace `set(value.keys()) == {OVERWRITE}` with
`len(value) == 1 and OVERWRITE in value` to avoid allocating a
throwaway set on every call
checkpoint-postgres — typed rows:
- add `_DeltaCombinedRow(TypedDict, total=False)` documenting the nine
columns emitted by `SELECT_DELTA_COMBINED_SQL`'s UNION ALL; change
`_build_delta_channel_writes_history` parameter from `Sequence[Any]`
to `Sequence[_DeltaCombinedRow]`; call sites cast the psycopg
`DictRow` result accordingly
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
039729a70d
commit
7cd2e3fe1a
@@ -4,7 +4,7 @@ import threading
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterator, Sequence
|
||||
from contextlib import contextmanager
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.checkpoint.base import (
|
||||
@@ -28,6 +28,7 @@ from langgraph.checkpoint.postgres import _internal
|
||||
from langgraph.checkpoint.postgres.base import (
|
||||
SELECT_DELTA_COMBINED_SQL,
|
||||
BasePostgresSaver,
|
||||
_DeltaCombinedRow,
|
||||
)
|
||||
from langgraph.checkpoint.postgres.shallow import ShallowPostgresSaver
|
||||
|
||||
@@ -475,7 +476,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
return self._build_delta_channel_writes_history(
|
||||
channel=channel,
|
||||
target_id=checkpoint_id,
|
||||
rows=rows,
|
||||
rows=cast("list[_DeltaCombinedRow]", rows),
|
||||
)
|
||||
|
||||
def _load_checkpoint_tuple(self, value: DictRow) -> CheckpointTuple:
|
||||
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
from collections import defaultdict
|
||||
from collections.abc import AsyncIterator, Iterator, Sequence
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.checkpoint.base import (
|
||||
@@ -28,6 +28,7 @@ from langgraph.checkpoint.postgres import _ainternal
|
||||
from langgraph.checkpoint.postgres.base import (
|
||||
SELECT_DELTA_COMBINED_SQL,
|
||||
BasePostgresSaver,
|
||||
_DeltaCombinedRow,
|
||||
)
|
||||
from langgraph.checkpoint.postgres.shallow import AsyncShallowPostgresSaver
|
||||
|
||||
@@ -434,7 +435,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
return self._build_delta_channel_writes_history(
|
||||
channel=channel,
|
||||
target_id=checkpoint_id,
|
||||
rows=rows,
|
||||
rows=cast("list[_DeltaCombinedRow]", rows),
|
||||
)
|
||||
|
||||
async def _load_checkpoint_tuple(self, value: DictRow) -> CheckpointTuple:
|
||||
|
||||
@@ -4,7 +4,7 @@ import random
|
||||
import warnings
|
||||
from collections.abc import Sequence
|
||||
from importlib.metadata import version as get_version
|
||||
from typing import Any, cast
|
||||
from typing import Any, TypedDict, cast
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.checkpoint.base import (
|
||||
@@ -155,6 +155,29 @@ INSERT_CHECKPOINT_WRITES_SQL = """
|
||||
ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO NOTHING
|
||||
"""
|
||||
|
||||
|
||||
class _DeltaCombinedRow(TypedDict, total=False):
|
||||
"""One row from `SELECT_DELTA_COMBINED_SQL` (a UNION ALL of three tables).
|
||||
|
||||
Every row carries `_kind` ("p" / "w" / "b") plus whichever columns are
|
||||
relevant for that kind; irrelevant columns are NULL and typed as `None`.
|
||||
"""
|
||||
|
||||
_kind: str # always present: "p", "w", or "b"
|
||||
# checkpoint row ("p")
|
||||
checkpoint_id: str | None
|
||||
parent_checkpoint_id: str | None
|
||||
ver: str | None
|
||||
# write / blob rows ("w", "b")
|
||||
type: str | None
|
||||
blob: bytes | None
|
||||
# write row only ("w")
|
||||
task_id: str | None
|
||||
idx: int | None
|
||||
# blob row only ("b")
|
||||
version: str | None
|
||||
|
||||
|
||||
# DeltaChannel reconstruction: one combined CTE+UNION ALL query per channel.
|
||||
# Bench (notes/delta_channel_query_bench.md) showed the prior recursive CTE
|
||||
# carried a hidden O(ancestors x blobs_in_thread) join; plain SELECTs are
|
||||
@@ -241,7 +264,7 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
|
||||
*,
|
||||
channel: str,
|
||||
target_id: str,
|
||||
rows: Sequence[Any],
|
||||
rows: Sequence[_DeltaCombinedRow],
|
||||
) -> _ChannelWritesHistory:
|
||||
"""Reconstruct one delta channel's history from the combined UNION ALL rows.
|
||||
|
||||
@@ -264,16 +287,21 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
|
||||
for r in rows:
|
||||
kind = r["_kind"]
|
||||
if kind == "p":
|
||||
cid = r["checkpoint_id"]
|
||||
cid = cast(str, r["checkpoint_id"])
|
||||
parent_of[cid] = r["parent_checkpoint_id"]
|
||||
ver_of[cid] = r["ver"]
|
||||
elif kind == "w":
|
||||
cid = r["checkpoint_id"]
|
||||
cid = cast(str, r["checkpoint_id"])
|
||||
writes_by_cid.setdefault(cid, []).append(
|
||||
(r["type"], r["blob"], r["task_id"], r["idx"])
|
||||
cast(
|
||||
"tuple[str, bytes, str, int]",
|
||||
(r["type"], r["blob"], r["task_id"], r["idx"]),
|
||||
)
|
||||
)
|
||||
else: # kind == "b"
|
||||
blob_by_ver[r["version"]] = (r["type"], r["blob"])
|
||||
blob_by_ver[cast(str, r["version"])] = cast(
|
||||
"tuple[str, bytes]", (r["type"], r["blob"])
|
||||
)
|
||||
|
||||
# Sort writes within each checkpoint (task_id DESC, idx DESC) to match
|
||||
# the prior CTE ordering — newest write first per ancestor.
|
||||
@@ -281,10 +309,10 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
|
||||
ws.sort(key=lambda w: (w[2], w[3]), reverse=True)
|
||||
|
||||
ancestors: list[str] = []
|
||||
cid = parent_of.get(target_id)
|
||||
while cid is not None:
|
||||
ancestors.append(cid)
|
||||
cid = parent_of.get(cid)
|
||||
cur_cid: str | None = parent_of.get(target_id)
|
||||
while cur_cid is not None:
|
||||
ancestors.append(cur_cid)
|
||||
cur_cid = parent_of.get(cur_cid)
|
||||
if not ancestors:
|
||||
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=[])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user