Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pydantic graph state no longer deserializes UUIDs correctly #4184

Open
4 tasks done
optimalstrategy opened this issue Apr 6, 2025 · 4 comments
Open
4 tasks done

Pydantic graph state no longer deserializes UUIDs correctly #4184

optimalstrategy opened this issue Apr 6, 2025 · 4 comments

Comments

@optimalstrategy
Copy link

Checked other resources

  • This is a bug, not a usage question. For questions, please use GitHub Discussions.
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • 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

from typing import Any, Dict
from uuid import UUID

from pydantic import BaseModel
from langgraph.graph import StateGraph


class State(BaseModel):
    id: UUID


def my_node(state: State) -> Dict[str, Any]:
    assert isinstance(state.id, UUID), type(state.id)  # assertion error
    print(state)

    return {"id": state.id}


workflow = StateGraph(State)
workflow.add_node("my_node", my_node)
workflow.add_edge("__start__", "my_node")
graph = workflow.compile()

graph.invoke({"id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca"})

Error Message and Stack Trace (if applicable)

/main.py:463: UserWarning: Pydantic serializer warnings:
  PydanticSerializationUnexpectedValue(Expected `uuid` - serialized value may not be as expected [input_value='fe096781-5601-53d2-b2f6-0d3403f7e9ca', input_type=str])
  return self.__pydantic_serializer__.to_python(

Description

  • I'm storing UUIDs in my Pydantic graph state
  • I expect UUIDs to be deserialized into UUID objects (as per normal Pydantic behavior and like they used to in previous LangGraph versions)
  • The UUIDs are actually incorrectly deserialized into str objects

System Info

System Information

OS: Linux
OS Version: #1 SMP PREEMPT_DYNAMIC Mon, 10 Mar 2025 01:49:31 +0000
Python Version: 3.12.8 (main, Dec 19 2024, 14:33:20) [Clang 18.1.8 ]

Package Information

langchain_core: 0.3.51
langsmith: 0.3.24
langgraph_api: 0.0.46
langgraph_cli: 0.1.89
langgraph_license: Installed. No version info available.
langgraph_sdk: 0.1.61
langgraph_storage: Installed. No version info available.

Optional packages not installed

langserve

Other Dependencies

blockbuster: 1.5.24
click: 8.1.8
cloudpickle: 3.1.1
cryptography: 44.0.2
httpx: 0.28.1
jsonpatch<2.0,>=1.33: Installed. No version info available.
jsonschema-rs: 0.29.1
langgraph: 0.3.25
langgraph-checkpoint: 2.0.24
langsmith-pyo3: Installed. No version info available.
langsmith<0.4,>=0.1.125: Installed. No version info available.
openai-agents: Installed. No version info available.
opentelemetry-api: Installed. No version info available.
opentelemetry-exporter-otlp-proto-http: Installed. No version info available.
opentelemetry-sdk: Installed. No version info available.
orjson: 3.10.16
packaging: 24.2
packaging<25,>=23.2: Installed. No version info available.
pydantic: 2.11.2
pydantic<3.0.0,>=2.5.2;: Installed. No version info available.
pydantic<3.0.0,>=2.7.4;: Installed. No version info available.
pyjwt: 2.10.1
pytest: Installed. No version info available.
python-dotenv: 1.1.0
PyYAML>=5.3: Installed. No version info available.
requests: 2.32.3
requests-toolbelt: 1.0.0
rich: Installed. No version info available.
sse-starlette: 2.1.3
starlette: 0.46.1
structlog: 25.2.0
tenacity: 9.1.2
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
typing-extensions>=4.7: Installed. No version info available.
uvicorn: 0.34.0
watchfiles: 1.0.4
zstandard: 0.23.0

@gbaian10
Copy link
Contributor

gbaian10 commented Apr 6, 2025

I think this is the same issue as #4074.

langgraph directly passes the raw input data internally and only uses BaseModel to validate if the data conforms, without performing any internal conversion.

import uuid
from typing import Any
from uuid import UUID

from langgraph.graph import StateGraph
from pydantic import BaseModel
from rich import get_console


class State(BaseModel):
    id: UUID


def my_node(state: State) -> dict[str, Any]:
    get_console().print(state)
    assert isinstance(state.id, UUID), type(state.id)  # assertion error

    return {"id": state.id}


workflow = StateGraph(State)
workflow.add_node("my_node", my_node)
workflow.add_edge("__start__", "my_node")
graph = workflow.compile()

# graph.invoke({"id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca"})
graph.invoke({"id": uuid.uuid4()})

If we pass in a UUID object directly, it works fine.
However, if we pass in a UUID string instead of the UUID object, the result may differ from what the user expects.

@optimalstrategy
Copy link
Author

@gbaian10 Thanks for the quick response.

Indeed, looks like the root cause is the same. That said, is this something you'd consider changing? It seems odd to allow using Pydantic models to specify State without actually coercing inputs according to what the models specify. As a workaround, I'm currently forced to-reparse the state in certain nodes:

def my_entrypoint_node(state: State):
    state = State.model_validate(state.model_dump())
    ...

@gbaian10
Copy link
Contributor

gbaian10 commented Apr 6, 2025

Currently, I'm using TypedDict in production for data passing, so I'm not sure what issues might arise with BaseModel.

I'm unsure if the following approach is good: using the entire State object for input and output in the Node function to read, modify, and pass data. Instead of using a dict for partial updates.

import uuid

from langgraph.graph import StateGraph
from pydantic import BaseModel
from rich import get_console

console = get_console()


class State(BaseModel):
    id: uuid.UUID
    name: str


def my_node(state: State) -> State:
    console.print(state)
    state.name = "Bob"
    return state


graph = StateGraph(State).set_entry_point(my_node.__name__).add_node(my_node).compile()


input_data = State(id=uuid.uuid4(), name="Alice")
output_data = graph.invoke(input_data)

print(type(output_data))
console.print(State.model_validate(output_data))

@nfcampos
Copy link
Contributor

nfcampos commented Apr 6, 2025

We do have logic to coerce inputs to the declared types, see https://github.com/langchain-ai/langgraph/blob/main/libs/langgraph/langgraph/graph/schema_utils.py
We'll try and reproduce your issue tomorrow

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

No branches or pull requests

3 participants