Skip to content

Conversation

@bsbodden
Copy link
Contributor

@bsbodden bsbodden commented Nov 17, 2025

Upgrade to LangGraph 1.0 and Checkpoint 3.0

This PR completes the migration from LangGraph 0.4.x/Checkpoint 2.x to LangGraph 1.0.3/Checkpoint 3.0, addressing breaking API changes and ensuring full compatibility with the latest versions. Also ensures proper library version passed to RedisPY

Breaking Changes

Python Version Requirement

  • Minimum Python version increased from 3.9 to 3.10 (Python 3.9 has reached EOL)

LangGraph 1.0 API Changes

  • Interrupt class structure changed: Simplified from 4 fields to 2 fields
    • Removed: resumable, ns, when
    • Retained: value, id
  • Updated serializer to handle new Interrupt(value=..., id=...) signature

Checkpoint 3.0 API Changes

  • Serializer signature change: dumps_typed/loads_typed now use tuple[str, bytes] instead of tuple[str, str]
  • Blob encoding: All blobs now stored as base64-encoded strings in Redis JSON documents
  • Metadata serialization: Updated to handle null values and new checkpoint structure

Dependency Updates

  • langgraph: 0.4.9 → 1.0.3
  • langchain-core: 0.3.74 → 1.0.5
  • langgraph-checkpoint: 2.x → 3.x
  • langgraph-prebuilt: 0.2.2 → 1.0.4
  • redisvl: Added >=0.11.0 constraint (addresses CVE-2025-64439)

Implementation Changes

Serialization Layer

  • Updated JsonPlusRedisSerializer for checkpoint 3.0 API compliance
  • Changed dumps_typed to return bytes instead of strings
  • Added orjson default handler for LangChain object serialization
  • Updated Interrupt object deserialization for LangGraph 1.0 structure

Blob Storage

  • Implemented base64 encoding for all blob data stored in Redis
  • Updated blob encoding in put_writes across sync, async, and shallow implementations
  • Fixed blob decoding in _load_pending_writes with proper fallback handling
  • Added msgpack fallback for nested structures containing bytes/bytearray

Metadata Handling

  • Updated metadata serialization to handle null sentinel values
  • Fixed _dump_metadata to use storage-safe sentinel values from checkpoint 3.0

Test Suite

  • Updated all Interrupt tests to use new 2-field API
  • Fixed serializer test helpers for checkpoint 3.0 signatures
  • Updated blob handling tests for base64 encoding
  • Fixed search_writes tests to expect base64-encoded blobs

Documentation & Examples

  • Fixed redisvl version across Dockerfile and README (0.5.1 → 0.11.0)
  • Updated notebook documentation to reflect Redis implementations (not in-memory)
  • Fixed create-react-agent-hitl.ipynb to use UUID for unique thread IDs
  • Corrected directory references in .gitignore and README

Migration Notes

Users upgrading to this version should be aware:

  1. Python 3.10+ is now required
  2. Interrupt objects in checkpoints will use the new 2-field structure
  3. All blobs are base64-encoded in Redis (transparent to users)
  4. Full compatibility with LangGraph 1.0 and Checkpoint 3.0 APIs

Related Issues

  • Addresses LangGraph 1.0 Interrupt API changes
  • Completes checkpoint 3.0 migration started in commit 3c148ae
  • Fixes security vulnerability via redisvl >=0.11.0 (CVE-2025-64439)

@bsbodden bsbodden changed the title Upgrade to LangGraph 1.0 and Checkpoint 3.0 feat: Upgrade to LangGraph 1.0 and Checkpoint 3.0 (#110) Nov 17, 2025
- Upgrade langgraph-checkpoint from >=2.0.21,<3.0.0 to >=3.0.0,<4.0.0
- Upgrade redisvl from >=0.5.1,<1.0.0 to >=0.11.0,<1.0.0
- Update Python requirement from >=3.9,<3.14 to >=3.10,<3.14 (Python 3.9 EOL)
- Remove Python 3.9 from classifiers
- Update Black target-version to remove py39

BREAKING CHANGE: Drops Python 3.9 support, now requires Python 3.10+

Addresses security vulnerability CVE-2025-64439 in langgraph-checkpoint
- Update dumps_typed signature to return tuple[str, bytes] (was tuple[str, str])
- Update loads_typed signature to accept tuple[str, bytes]
- Remove dumps() and loads() methods (not in SerializerProtocol anymore)
- Use orjson with custom _default_handler for LangChain object serialization
- Leverage parent's _encode_constructor_args for LC format encoding
- Use parent's _reviver for security-checked deserialization
- Maintain Interrupt object handling from Issue #113 fix
- Add msgpack backward compatibility for old checkpoints

BREAKING CHANGE: dumps_typed/loads_typed signatures changed for checkpoint 3.0

The SerializerProtocol interface changed in checkpoint 3.0 to use only
dumps_typed and loads_typed (dumps/loads removed), and the signatures
now use bytes instead of strings.

Addresses CVE-2025-64439 security fix in parent class.
- Replace serde.dumps()/serde.loads() with serde.dumps_typed()/serde.loads_typed()
- Update _load_metadata and _dump_metadata to use new API
- Fixes type errors in base.py
- Replace serializer.dumps()/loads() with dumps_typed()/loads_typed()
- Add helper functions in test_jsonplus_serializer_default_handler.py
- Update test_issue_113_interrupt_serialization.py
- Update test_checkpoint_serialization.py
- Update test_issue_85_message_coercion.py

All test files now use the new SerializerProtocol API from checkpoint 3.0
- Add _encode_blob() calls in put_writes() and _dump_writes()
- Blob data from dumps_typed() is now bytes (not string)
- Must base64-encode before storing in Redis JSON
- Fixes TypeError: Object of type bytes is not JSON serializable
- Add _encode_blob() in RedisSaver.put_writes() (__init__.py)
- Add _encode_blob() in AsyncRedisSaver.aput_writes() (aio.py)
- Both sync and async implementations now properly encode blob bytes
- Completes blob encoding fix across all implementations
- Add _encode_blob() in ShallowRedisSaver.put_writes()
- Add _encode_blob() in AsyncShallowRedisSaver.aput_writes()
- Completes blob encoding fix across ALL implementations (regular and shallow)
…allback

Call super()._load_pending_writes() instead of self._load_pending_writes()
in the registry fallback path to avoid infinite recursion between
_load_pending_writes and _load_pending_writes_with_registry_check.
Add static _decode_blob_static() method to handle base64 decoding
of blob data retrieved from Redis before passing to serializer.
This fixes orjson.JSONDecodeError when loading pending writes.
Update blob assertions to decode base64 data before comparing.
Blobs are now base64-encoded in Redis with checkpoint 3.0.
Update dumps_helper/loads_helper to preserve full (type_str, bytes) tuple.
Update test_dumps_typed_with_messages to expect bytes not str.
- Add bytes detection in _default_handler to trigger msgpack serialization
- Update dumps_typed to fallback to parent's msgpack for bytes in dicts
- Update _dump_checkpoint to handle both JSON and msgpack types
- Add __bytes__ marker for bytes in channel_values stored via msgpack
- Update _recursive_deserialize to decode __bytes__ markers
- Fix test assertions to expect bytes instead of str from dumps_typed
Update tests to unpack (type_str, bytes) tuple from dumps_helper
instead of expecting just bytes.
Auto-format code with black and isort for checkpoint 3.0 changes.
Convert all dumps/loads calls to dumps_typed/loads_typed.
Update blob assertion to expect bytes instead of str.
BREAKING CHANGE: Minimum Python version is now 3.10 (was 3.9)

- Upgrade langgraph from 0.4.9 to 1.0.3
- Upgrade langchain-core from 0.3.74 to 1.0.5
- Upgrade langgraph-checkpoint from 2.x to 3.x
- Add redisvl >=0.11.0 dependency (security fix CVE-2025-64439)
BREAKING CHANGE: Interrupt object structure changed in LangGraph 1.0

LangGraph 1.0 simplified the Interrupt class from 4 fields to 2 fields:
- Removed: resumable, ns, when
- Retained: value, id

Updated JsonPlusRedisSerializer._revive_if_needed() to detect and
reconstruct Interrupt objects using the new 2-field structure.

Changes:
- Check for len(obj) == 2 instead of len(obj) == 4
- Check for "id" field instead of "resumable"
- Construct Interrupt(value=..., id=...) instead of old signature
Update all Interrupt object creation and assertions to use the new
LangGraph 1.0 API with only 'value' and 'id' fields.

Changes:
- Replace Interrupt(value=..., resumable=...) with Interrupt(value=..., id=...)
- Remove assertions on removed fields (resumable, ns, when)
- Add assertions for new 'id' field
- Update test documentation to reflect LangGraph 1.0 changes

Files updated:
- tests/test_issue_113_interrupt_serialization.py (3 tests)
- tests/test_jsonplus_redis_serializer_v3.py (3 tests)
- Update redisvl version from >=0.5.1 to >=0.11.0 in Dockerfile and README
- Fix .gitignore comment: /docs -> /examples
- Fix README reference to parent directory mount path
- Fix create-react-agent-hitl.ipynb to use uuid for unique thread IDs
- Update notebook markdown to reference RedisStore instead of InMemoryStore
- Update notebook markdown to reference RedisSaver instead of MemorySaver

These changes ensure:
1. Security fix (CVE-2025-64439) via redisvl >=0.11.0
2. Accurate documentation matching actual implementations
3. Notebooks work correctly with checkpoint state isolation
….9 from CI

- Add MIGRATION_0.2.0.md with detailed migration instructions for:
  * LangGraph 1.0 Interrupt API changes (4 fields → 2 fields)
  * Checkpoint 3.0 serialization changes (str → bytes)
  * Python 3.10+ requirement
  * Data migration strategies and troubleshooting
- Rename MIGRATION.md to MIGRATION_0.1.0.md for version clarity
- Remove Python 3.9 from CI test matrix (EOL and no longer supported)

The migration guide covers:
- Breaking changes with before/after code examples
- Step-by-step migration process
- Data compatibility and migration options
- Comprehensive troubleshooting section
- Testing recommendations
…cking

- Add client_setinfo calls in RedisSaver.configure_client() to register library version with Redis
- Update all set_client_info/aset_client_info methods to use __full_lib_name__ instead of __redisvl_version__
- Ensure graceful error handling with fallback to echo() and silent failure
- Remove redundant test_jsonplus_redis_serializer.py from root directory
- Update all client info tests to expect full library name format
- Add client_setinfo/echo mock methods to BaseMockRedis for cluster mode tests

BREAKING CHANGE: Client info now sends complete version string format:
redis-py(redisvl_v{version};langgraph-checkpoint-redis_v{version})

This provides better visibility in Redis CLIENT LIST for monitoring and debugging,
showing redis-py client, RedisVL version, and checkpoint-redis library version.
@bsbodden bsbodden requested a review from Copilot November 17, 2025 15:15
Copilot finished reviewing on behalf of bsbodden November 17, 2025 15:20

This comment was marked as outdated.

- Move base64 imports to top of test files for better style (test_sync.py, test_async.py)
- Make exception handling more specific in _default_handler (AttributeError, KeyError, ValueError, TypeError instead of bare Exception)
- Fix false positive Interrupt detection by adding __interrupt__ type marker to prevent user data with {value, id} structure from being incorrectly deserialized as Interrupt objects
- Add _preprocess_interrupts helper to recursively tag Interrupt objects before orjson serialization
- Support preprocessing of nested structures (dicts, lists, tuples)

This prevents critical bug where user data like {"value": "data", "id": "user-id"}
would be incorrectly deserialized as Interrupt(value="data", id="user-id").
Add pytest filterwarnings to ignore DeprecationWarning from testcontainers
library's internal use of @wait_container_is_ready decorator.

These warnings come from testcontainers v4.13.3 (latest) and are not
our code to fix. The testcontainers maintainers haven't updated their
own internal code yet despite deprecating the decorator.

Warnings suppressed:
- testcontainers.core.waiting_utils (line 215)
- testcontainers.redis (line 46)

This keeps test output clean while we wait for upstream fix.
- Add missing 'Not needed for LangGraph API users' info box
- Change 'LangGraph Cloud' to 'LangGraph Platform' terminology
- Clear all outputs for clean execution
- Verified against source notebook in langgraph docs

Ready for end-to-end execution with Redis checkpoint implementation.
- Change 'LangGraph Cloud' to 'LangGraph Platform' terminology
- Clear all outputs for clean execution

Ready for end-to-end execution with Redis store and checkpoint implementations.
- Change 'LangGraph Cloud' to 'LangGraph Platform' terminology
- Clear all outputs for clean execution

Ready for end-to-end execution with Redis store and checkpoint implementations.
- Clear all outputs for clean execution
- Ready for end-to-end execution with Redis checkpoint
- Clear all outputs for clean execution on create-react-agent-memory.ipynb
- Clear all outputs for clean execution on create-react-agent-hitl.ipynb
- Ready for end-to-end execution with Redis checkpoint
- Clear all outputs for clean execution on subgraph-persistence.ipynb
- Clear all outputs for clean execution on subgraphs-manage-state.ipynb
- Ready for end-to-end execution with Redis checkpoint
- Clear all outputs for clean execution on all 11 notebooks
- 7 human_in_the_loop notebooks: breakpoints, dynamic_breakpoints, edit-graph-state,
  review-tool-calls-openai, review-tool-calls, time-travel, wait-user-input
- 4 memory notebooks: add-summary-conversation-history, delete-messages,
  manage-conversation-history, semantic-search
- Ready for end-to-end execution with Redis checkpoint
…mem objects

- Add support for serializing/deserializing dataclass objects (e.g., RunningSummary)
- Add special handling for sets in nested structures
- Prevent loss of type information when dataclasses are serialized
- Fix AttributeError when langmem SummarizationNode stores context in state

Changes:
- Preprocess dataclass instances to LangChain constructor format before serialization
- Add _reconstruct_from_constructor() to rebuild objects from constructor format
- Handle sets with special __set_items__ marker to preserve contents
- Update _revive_if_needed() to detect when parent reviver returns unchanged dict

Tests:
- Add comprehensive test suite in test_langmem_serialization.py
- Test roundtrip serialization of RunningSummary objects
- Test nested dataclasses in dicts and lists
- All 382 tests passing

Fixes the error in create-react-agent-manage-message-history.ipynb where
RunningSummary objects were being deserialized as dicts instead of proper
objects, causing AttributeError: 'dict' object has no attribute 'summarized_message_ids'
Add pytest filter to suppress LangGraphDeprecatedSinceV10 warning from
trustcall library (a dependency of langmem). The warning is about trustcall
using deprecated 'from langgraph.constants import Send' instead of
'from langgraph.types import Send'. This is an issue in their library,
not ours.
Add Redis flush command at the start of notebooks to clear old checkpoints
that were created with the buggy serializer (before dataclass support was added).

This is necessary because:
1. Jupyter kernels cache Python modules
2. Old checkpoints in Redis were serialized without dataclass support
3. Deserializing old checkpoints causes AttributeError even with fixed code

The flush ensures notebooks start fresh with the updated serializer.
Replace deprecated claude-3-5-sonnet-20240620 with claude-3-5-sonnet-20241022
to fix NotFoundError when running notebook.

The old model version reached end-of-life and returns 404 errors.
@bsbodden bsbodden force-pushed the bsb/lg3-upgrade branch 2 times, most recently from b3ee2e3 to 3822ea5 Compare November 18, 2025 01:11
Add Python version check to skip langmem serialization tests on Python < 3.11.
The langmem library uses typing.NotRequired which was introduced in Python 3.11,
causing import failures on Python 3.10 in CI.

Tests will:
- Skip on Python 3.10 (379 tests run)
- Run on Python 3.11+ (382 tests run)

Fixes CI test collection failures on Python 3.10.
@bsbodden bsbodden self-assigned this Nov 18, 2025
@bsbodden bsbodden requested a review from abrookins November 18, 2025 01:23
Copy link
Contributor

@abrookins abrookins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 👍

The only thing I wondered about was whether we'd want to try and support old Interrupt data -- it seems like we skip trying to load an Interrupt if the new fields aren't present. I could have misunderstood that (in _revive_if_needed), but even if we did restore an Interrupt saved by an older version, I'm not sure that would actually work from the LangGraph side.

@bsbodden bsbodden merged commit ac683ee into main Nov 19, 2025
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants