Repository: langchain-ai/langgraph
Issue category: Bug Report

Issue:
msgpack deserialization with strictmap_key=False
### Checked other resources

- [X] This is a bug, not a usage question. For questions, please use GitHub Discussions.
- [X] I added a clear and detailed title that summarizes the issue.
- [X] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
- [X] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

### Example Code

```python
import asyncio
from typing import Annotated, TypedDict

from langgraph.constants import END
from langgraph.graph import StateGraph
from pydantic import BaseModel

from app.config.settings import get_settings
from app.utils.redis_checkpointer import AsyncRedisSaver

class CitationBroker(BaseModel):
    map_idx_to_utt: dict[int, int]

class AgentState(TypedDict):
    citation_brokers: list[CitationBroker]


def f1(state):
    cit_b = CitationBroker(
    map_idx_to_utt={
        1: 1,
        2: 2,
        3: 3
    })
    """
    With 
    map_idx_to_utt={
        '1': 1,
        '2': 2,
        '3': 3
    })
    and 
    
    class CitationBroker(BaseModel):
    map_idx_to_utt: dict[str, int]
    
    works
    """
    print(str(cit_b)) # not None
    return {
        "citation_brokers": state.get('citation_brokers', []) + [cit_b],
    }

def ask_human_node(state):
    print("get user input")

builder = StateGraph(AgentState)
builder.add_node("node_1", f1)
builder.add_node("ask_human_node", ask_human_node)
builder.set_entry_point("node_1")
builder.add_edge("ask_human_node", "node_1")
builder.add_edge("node_1", "ask_human_node")

settings = get_settings()
async def main():
    async with AsyncRedisSaver.from_url(settings.CACHE_REDIS_ENDPOINT) as memory:
        graph = builder.compile(checkpointer=memory, interrupt_before=["ask_human_node"])
        thread = {
            "configurable": {
                "thread_id": "1"
            }
        }

        async for event in graph.astream_events({
            "citation_brokers": [],
        }, config=thread, version="v2"):
            pass

        snapshot = await graph.aget_state(thread)
        print(str(snapshot.values['citation_brokers'][0]))  # None

asyncio.run(main())
```


### Error Message and Stack Trace (if applicable)

```shell
File "msgpack\\_unpacker.pyx", line 194, in msgpack._cmsgpack.unpackb ValueError: int is not allowed for map key when strict_map_key=True
```


### Description

I upgraded libraries from **langgraph** 0.2.19 to **0.2.58** and **langgraph-checkpoint** from 1.0.9 to **2.0.8**.
I'm using a REDIS checkpointer as detailed in [the official guide](https://langchain-ai.github.io/langgraph/how-tos/persistence_redis/).
I'm serializing a TypedDict which contains Pydantic V2 objects as values (keys are strings). Each of this Pydantic V2 objects contains a simple Python dict() (whose keys are _numeric_). 

When I try to deserialize the Pydantic object I get the following error:

> File "msgpack\\_unpacker.pyx", line 194, in msgpack._cmsgpack.unpackb ValueError: int is not allowed for map key when strict_map_key=True

Setting `strict_map_key=False` inside _jsonplus.py_ solves the issue, but this implies cloning _jsonplus.py_ just to set `strict_map_key=False`.

Indeed at line 210 of _jsonplus.py_ I find:

```
elif type_ == "msgpack":
            return msgpack.unpackb(
                data_, ext_hook=_msgpack_ext_hook, strict_map_key=False
            )
```

but at line 482 of _jsonplus.py_:

```
elif code == EXT_PYDANTIC_V2:
        try:
            tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook) # lacks of strict_map_key=False
            # module, name, kwargs, method
            cls = getattr(importlib.import_module(tup[0]), tup[1])
            try:
                return cls(**tup[2])
            except Exception:
                return cls.model_construct(**tup[2])
        except Exception:
            return
```

Any advice on how should I fix the problem? In the meantime I reverted to previous version of libraries (which solves the issue).
Thanks in advance.

### System Info

Python 3.12.7 (tags/v3.12.7:0b05ead, Oct  1 2024, 03:06:41) [MSC v.1941 64 bit (AMD64)] on win32


Hints:

Patch:
diff --git a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py
index f8d280b96..ff5a91f5d 100644
--- a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py
+++ b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py
@@ -438,28 +438,36 @@ def _msgpack_default(obj: Any) -> Union[str, msgpack.ExtType]:
 def _msgpack_ext_hook(code: int, data: bytes) -> Any:
     if code == EXT_CONSTRUCTOR_SINGLE_ARG:
         try:
-            tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
+            tup = msgpack.unpackb(
+                data, ext_hook=_msgpack_ext_hook, strict_map_key=False
+            )
             # module, name, arg
             return getattr(importlib.import_module(tup[0]), tup[1])(tup[2])
         except Exception:
             return
     elif code == EXT_CONSTRUCTOR_POS_ARGS:
         try:
-            tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
+            tup = msgpack.unpackb(
+                data, ext_hook=_msgpack_ext_hook, strict_map_key=False
+            )
             # module, name, args
             return getattr(importlib.import_module(tup[0]), tup[1])(*tup[2])
         except Exception:
             return
     elif code == EXT_CONSTRUCTOR_KW_ARGS:
         try:
-            tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
+            tup = msgpack.unpackb(
+                data, ext_hook=_msgpack_ext_hook, strict_map_key=False
+            )
             # module, name, args
             return getattr(importlib.import_module(tup[0]), tup[1])(**tup[2])
         except Exception:
             return
     elif code == EXT_METHOD_SINGLE_ARG:
         try:
-            tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
+            tup = msgpack.unpackb(
+                data, ext_hook=_msgpack_ext_hook, strict_map_key=False
+            )
             # module, name, arg, method
             return getattr(getattr(importlib.import_module(tup[0]), tup[1]), tup[3])(
                 tup[2]
@@ -468,7 +476,9 @@ def _msgpack_ext_hook(code: int, data: bytes) -> Any:
             return
     elif code == EXT_PYDANTIC_V1:
         try:
-            tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
+            tup = msgpack.unpackb(
+                data, ext_hook=_msgpack_ext_hook, strict_map_key=False
+            )
             # module, name, kwargs
             cls = getattr(importlib.import_module(tup[0]), tup[1])
             try:
@@ -479,7 +489,9 @@ def _msgpack_ext_hook(code: int, data: bytes) -> Any:
             return
     elif code == EXT_PYDANTIC_V2:
         try:
-            tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
+            tup = msgpack.unpackb(
+                data, ext_hook=_msgpack_ext_hook, strict_map_key=False
+            )
             # module, name, kwargs, method
             cls = getattr(importlib.import_module(tup[0]), tup[1])
             try:


Test patch: