fix(langgraph): check langgraph-api version for DeltaChannel support

Replace the checkpointer-capability heuristic with a direct `langgraph-api`
version check: `DeltaChannel` reconstruction needs server-side support added
in `langgraph-api>0.9.0`. `StateGraph.compile` now raises a `RuntimeError`
when a delta-channel graph is compiled under an older API server, and is a
no-op when `langgraph-api` is not installed (local execution).

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Sydney Runkle
2026-06-11 21:15:18 +00:00
co-authored by open-swe[bot] <open-swe@users.noreply.github.com>
parent 4e4bf1a204
commit 9752282073
3 changed files with 113 additions and 107 deletions
+24 -23
View File
@@ -9,6 +9,7 @@ from collections.abc import Awaitable, Callable, Hashable, Mapping, Sequence
from dataclasses import dataclass, is_dataclass
from datetime import timedelta
from functools import partial
from importlib import metadata
from inspect import isclass, isfunction, ismethod, signature
from types import FunctionType
from types import NoneType as NoneType
@@ -27,8 +28,9 @@ from typing import (
from langchain_core.runnables import Runnable, RunnableConfig
from langgraph.cache.base import BaseCache
from langgraph.checkpoint.base import BaseCheckpointSaver, Checkpoint
from langgraph.checkpoint.base import Checkpoint
from langgraph.store.base import BaseStore
from packaging.version import Version
from pydantic import BaseModel, TypeAdapter
from typing_extensions import NotRequired, Required, Self, Unpack, is_typeddict
@@ -120,32 +122,31 @@ def _warn_invalid_state_schema(schema: type[Any] | Any) -> None:
)
def _warn_if_checkpointer_lacks_delta_support(
channels: Mapping[str, Any], checkpointer: Checkpointer
) -> None:
"""Warn when a `DeltaChannel` graph has a checkpointer that can't reconstruct it.
_MIN_DELTA_CHANNEL_API_VERSION = Version("0.9.0")
`DeltaChannel` stores only a sentinel in checkpoint blobs and rebuilds
state by replaying ancestor writes via `BaseCheckpointSaver`'s delta
history API (`get_delta_channel_history`, added in
`langgraph-checkpoint>=4.1.0`). A saver from an older `langgraph-checkpoint`
lacks that method, so reconstruction fails at runtime. Warn at compile
time so users know to upgrade.
def _check_delta_channel_api_support(channels: Mapping[str, Any]) -> None:
"""Raise if a `DeltaChannel` graph runs under an API server too old to support it.
`DeltaChannel` reconstruction depends on server-side support added in
`langgraph-api>0.9.0`. When running under an older API server, delta
channels fail at runtime, so we raise at compile time with an upgrade
hint. The check is skipped when `langgraph-api` is not installed (e.g.
local execution), since there is no API server to be incompatible.
"""
if not isinstance(checkpointer, BaseCheckpointSaver):
return
if not any(isinstance(c, DeltaChannel) for c in channels.values()):
return
if hasattr(checkpointer, "get_delta_channel_history"):
try:
api_version = metadata.version("langgraph-api")
except metadata.PackageNotFoundError:
return
warnings.warn(
f"The configured checkpointer ({type(checkpointer).__name__}) does not "
"support `DeltaChannel` state reconstruction, which requires the "
"`get_delta_channel_history` API added in `langgraph-checkpoint>=4.1.0`. "
"State reconstruction will fail at runtime. Upgrade `langgraph-checkpoint` "
"(e.g. `pip install -U langgraph-checkpoint`) to enable `DeltaChannel` support.",
UserWarning,
stacklevel=4,
if Version(api_version) > _MIN_DELTA_CHANNEL_API_VERSION:
return
raise RuntimeError(
f"`DeltaChannel` requires `langgraph-api>0.9.0`, but the installed "
f"version is {api_version}. Upgrade `langgraph-api` "
"(e.g. `pip install -U langgraph-api`) to use graphs that rely on "
"`DeltaChannel`."
)
@@ -1245,7 +1246,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
CompiledStateGraph: The compiled `StateGraph`.
"""
checkpointer = ensure_valid_checkpointer(checkpointer)
_warn_if_checkpointer_lacks_delta_support(self.channels, checkpointer)
_check_delta_channel_api_support(self.channels)
serde_allowlist: set[tuple[str, ...]] | None = None
if _serde.STRICT_MSGPACK_ENABLED:
@@ -0,0 +1,89 @@
"""Compile-time check that `DeltaChannel` graphs run under a supported API server.
`DeltaChannel` reconstruction relies on server-side support added in
`langgraph-api>0.9.0`. When running under an older API server, delta channels
fail at runtime, so `StateGraph.compile` raises with an upgrade hint. The check
is skipped when `langgraph-api` is not installed (local execution).
"""
from __future__ import annotations
import warnings
from typing import Annotated
import pytest
from typing_extensions import TypedDict
from langgraph.channels.delta import DeltaChannel
from langgraph.constants import START
from langgraph.graph import StateGraph
from langgraph.graph import state as state_module
from langgraph.graph.message import _messages_delta_reducer
class _DeltaState(TypedDict):
messages: Annotated[
list, DeltaChannel(_messages_delta_reducer, snapshot_frequency=50)
]
class _PlainState(TypedDict):
values: Annotated[list, lambda a, b: (a or []) + (b or [])]
def _delta_graph() -> StateGraph:
return (
StateGraph(_DeltaState)
.add_node("n", lambda s: {"messages": []})
.add_edge(START, "n")
)
@pytest.fixture
def api_version(monkeypatch: pytest.MonkeyPatch):
"""Override the reported `langgraph-api` version (or simulate absence)."""
def _set(version: str | None) -> None:
def fake_version(name: str) -> str:
if name == "langgraph-api":
if version is None:
raise state_module.metadata.PackageNotFoundError(name)
return version
return state_module.metadata.version(name)
monkeypatch.setattr(state_module.metadata, "version", fake_version)
return _set
def test_delta_graph_raises_for_old_api(api_version) -> None:
api_version("0.9.0")
with pytest.raises(RuntimeError, match="requires `langgraph-api>0.9.0`"):
_delta_graph().compile()
def test_delta_graph_ok_for_new_api(api_version) -> None:
api_version("0.9.1")
_delta_graph().compile()
def test_delta_graph_ok_for_much_newer_api(api_version) -> None:
api_version("0.11.2")
_delta_graph().compile()
def test_delta_graph_ok_when_api_not_installed(api_version) -> None:
api_version(None)
_delta_graph().compile()
def test_non_delta_graph_never_raises(api_version) -> None:
api_version("0.1.0")
graph = (
StateGraph(_PlainState)
.add_node("n", lambda s: {"values": []})
.add_edge(START, "n")
)
with warnings.catch_warnings():
warnings.simplefilter("error")
graph.compile()
@@ -1,84 +0,0 @@
"""Compile-time warning when a checkpointer can't reconstruct `DeltaChannel`.
`DeltaChannel` reconstructs state via `BaseCheckpointSaver`'s delta history
API (`get_delta_channel_history`, added in `langgraph-checkpoint>=4.1.0`). A
saver from an older package lacks that method, so reconstruction fails at
runtime. `StateGraph.compile` warns at compile time so users upgrade.
"""
from __future__ import annotations
import warnings
from typing import Annotated
import pytest
from langgraph.checkpoint.base import BaseCheckpointSaver
from langgraph.checkpoint.memory import InMemorySaver
from typing_extensions import TypedDict
from langgraph.channels.delta import DeltaChannel
from langgraph.constants import START
from langgraph.graph import StateGraph
from langgraph.graph.message import _messages_delta_reducer
class _DeltaState(TypedDict):
messages: Annotated[
list, DeltaChannel(_messages_delta_reducer, snapshot_frequency=50)
]
class _PlainState(TypedDict):
values: Annotated[list, lambda a, b: (a or []) + (b or [])]
def _delta_graph() -> StateGraph:
return (
StateGraph(_DeltaState)
.add_node("n", lambda s: {"messages": []})
.add_edge(START, "n")
)
@pytest.fixture
def saver_without_delta_api(monkeypatch: pytest.MonkeyPatch) -> InMemorySaver:
"""A saver whose class tree lacks the delta history API (old package)."""
for cls in (InMemorySaver, BaseCheckpointSaver):
for name in ("get_delta_channel_history", "aget_delta_channel_history"):
if name in cls.__dict__:
monkeypatch.delattr(cls, name)
saver = InMemorySaver()
assert not hasattr(saver, "get_delta_channel_history")
return saver
def test_delta_graph_warns_for_unsupported_checkpointer(
saver_without_delta_api: InMemorySaver,
) -> None:
with pytest.warns(UserWarning, match="does not support `DeltaChannel`"):
_delta_graph().compile(checkpointer=saver_without_delta_api)
def test_delta_graph_no_warning_for_supported_checkpointer() -> None:
with warnings.catch_warnings():
warnings.simplefilter("error")
_delta_graph().compile(checkpointer=InMemorySaver())
def test_delta_graph_no_warning_without_checkpointer() -> None:
with warnings.catch_warnings():
warnings.simplefilter("error")
_delta_graph().compile()
def test_non_delta_graph_no_warning_with_unsupported_checkpointer(
saver_without_delta_api: InMemorySaver,
) -> None:
graph = (
StateGraph(_PlainState)
.add_node("n", lambda s: {"values": []})
.add_edge(START, "n")
)
with warnings.catch_warnings():
warnings.simplefilter("error")
graph.compile(checkpointer=saver_without_delta_api)