From 9e40dee07f3937b165b5dfd3580fe3f223827d7a Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Fri, 17 Apr 2026 14:21:40 -0400 Subject: [PATCH] feat(checkpoint/serde): serialize DiffDelta as 'diff' type tag Add serde support for DiffDelta by implementing dump/load for the "diff" type tag. This allows the checkpoint system to efficiently store delta objects by serializing them as msgpack-encoded dicts with {"d": delta, "p": prev_version} structure. The implementation uses runtime type checking to avoid circular imports and leverages the existing msgpack ext hooks for proper deserialization of complex types like LangChain messages. Co-Authored-By: Claude Sonnet 4.5 --- .../langgraph/checkpoint/serde/jsonplus.py | 10 ++++++ libs/checkpoint/tests/test_jsonplus.py | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py index 4ee654df8..eb1d5dce5 100644 --- a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py +++ b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py @@ -239,6 +239,12 @@ class JsonPlusSerializer(SerializerProtocol): return "bytes", obj elif isinstance(obj, bytearray): return "bytearray", obj + elif ( + type(obj).__name__ == "DiffDelta" + and hasattr(obj, "delta") + and hasattr(obj, "prev_version") + ): + return "diff", _msgpack_enc({"d": obj.delta, "p": obj.prev_version}) else: try: return "msgpack", _msgpack_enc(obj) @@ -261,6 +267,10 @@ class JsonPlusSerializer(SerializerProtocol): return ormsgpack.unpackb( data_, ext_hook=self._unpack_ext_hook, option=ormsgpack.OPT_NON_STR_KEYS ) + elif type_ == "diff": + return ormsgpack.unpackb( + data_, ext_hook=self._unpack_ext_hook, option=ormsgpack.OPT_NON_STR_KEYS + ) elif self.pickle_fallback and type_ == "pickle": return pickle.loads(data_) else: diff --git a/libs/checkpoint/tests/test_jsonplus.py b/libs/checkpoint/tests/test_jsonplus.py index 9242c59bf..71bac1fe9 100644 --- a/libs/checkpoint/tests/test_jsonplus.py +++ b/libs/checkpoint/tests/test_jsonplus.py @@ -983,3 +983,35 @@ def test_msgpack_nested_pydantic_serializes_as_dict( # No blocking should occur - inner is serialized as dict, not ext assert "blocked" not in caplog.text.lower() assert result == obj + + +def test_diff_delta_serde_round_trip() -> None: + from langgraph.checkpoint.base import DiffDelta + + serde = JsonPlusSerializer() + prev = "00000000000000000000000000000001.1234567890123456" + delta = DiffDelta( + delta=[HumanMessage(content="hello", id="msg-1")], + prev_version=prev, + ) + type_tag, blob = serde.dumps_typed(delta) + assert type_tag == "diff" + + result = serde.loads_typed(("diff", blob)) + assert isinstance(result, dict) + assert result["p"] == prev + assert len(result["d"]) == 1 + assert result["d"][0].content == "hello" + + +def test_diff_delta_serde_root_blob() -> None: + from langgraph.checkpoint.base import DiffDelta + + serde = JsonPlusSerializer() + delta = DiffDelta(delta=[], prev_version=None) + type_tag, blob = serde.dumps_typed(delta) + assert type_tag == "diff" + + result = serde.loads_typed(("diff", blob)) + assert result["p"] is None + assert result["d"] == []