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 objects with all None properties converted to empty dict internally instead of dict with None values #2538

Open
5 tasks done
majorgilles opened this issue Nov 26, 2024 · 1 comment

Comments

@majorgilles
Copy link

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangGraph/LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangGraph/LangChain rather than my code.
  • I am sure this is better as an issue rather than a GitHub discussion, since this is a LangGraph bug and not a design question.

Example Code

from pydantic import BaseModel
from langgraph.graph import StateGraph, START, END
from langchain_core.output_parsers import PydanticOutputParser


# Define the state schema with an optional string key "foo"
class OverallState(BaseModel):
    foo: str | None  # Optional string

class InputState(BaseModel):
    bar: str | None  # Optional string

class OutputState(BaseModel):
    baz: str | None  # Optional string

# Define the node function that sets "foo" to None
def set_foo_to_none(state: InputState) -> OverallState:
    return OverallState(foo=None)

def set_baz(state: OverallState) -> OutputState:
    return OutputState(baz=state.foo)

# Build the state graph
builder = StateGraph(OverallState, input=InputState, output=OutputState)  # Initialize the graph
builder.add_node("set_foo_to_none", set_foo_to_none)  # Add the node
builder.add_node("set_baz", set_baz)  # Add the node

builder.add_edge(START, "set_foo_to_none")  # Start the graph with the node
builder.add_edge("set_foo_to_none", "set_baz")  # Connect the nodes
builder.add_edge("set_baz", END)  # End the graph after the node

# Compile the graph
graph = builder.compile()


if __name__ == "__main__":
    # Invoke the graph with an initial state
    response = graph.invoke({"bar": "initial value bar"})
    print(f"Response: " + str(response))

Error Message and Stack Trace (if applicable)

Traceback (most recent call last):
  File "/Users/gillesmajor/dev/lizy-ai/projects/picture_categorization/graph_v1.py", line 38, in <module>
    response = graph.invoke({"bar": "initial value bar"})
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1884, in invoke
    for chunk in self.stream(
                 ^^^^^^^^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/langgraph/pregel/__init__.py", line 1611, in stream
    while loop.tick(input_keys=self.input_channels):
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/langgraph/pregel/loop.py", line 421, in tick
    self.tasks = prepare_next_tasks(
                 ^^^^^^^^^^^^^^^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/langgraph/pregel/algo.py", line 361, in prepare_next_tasks
    if task := prepare_single_task(
               ^^^^^^^^^^^^^^^^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/langgraph/pregel/algo.py", line 631, in prepare_single_task
    val = next(
          ^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/langgraph/pregel/algo.py", line 769, in _proc_input
    val = proc.mapper(val)
          ^^^^^^^^^^^^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/langgraph/graph/state.py", line 814, in _coerce_state
    return schema(**input)
           ^^^^^^^^^^^^^^^
  File "/Users/gillesmajor/Library/Caches/pypoetry/virtualenvs/lizy-ai-4pk-LIdT-py3.12/lib/python3.12/site-packages/pydantic/main.py", line 212, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for OverallState
foo
  Field required [type=missing, input_value={}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing

Description

This bug has been confirmed by Isaac Herhenson.

Basically, internally, langgraph is converting a CertainPydanticObjetct(prop1=None, prop2=None) to '{}' (empty dict) when passing states around nodes. Instead of passing {"prop1": None, "Prop2": None}, it passes {} to the next node, leading to various internal errors in various contexts. Making nodes return a dict with pydantic_object.model_dump() solves the issue temporarily.

System Info

[tool.poetry.dependencies]
python = ">=3.12,<3.13"
awscli = "^1.36.4" # required as a normal dependency to install common at runtime
boto3 = "==1.35.63"
botocore = "==1.35.63"
bs4 = "==0.0.2"
chromadb = "==0.5.18"
langgraph = "==0.2.50"
langsmith = "==0.1.143"
langchain-community = "==0.3.7"
langchain-chroma = "==0.1.4"
langchain-core = "==0.3.19"
langchain-openai = "==0.2.8"
google-cloud-documentai = "==3.0.1"
html2text = "==2024.2.26"
phonenumberslite = "==8.12.48" # leave at this version or common will not install dynamically
pydantic = { extras = ["email"], version = "==2.9.2" }
sentry-sdk = "==2.14.0" # leave at this version or common will not install dynamically
redis = "==5.2.0"
ring = "==0.10.1"
requests = "==2.32.3"
requests-aws4auth = "==1.3.1"
setuptools = "==74.1.1" # leave at this version or common will not install dynamically
ssm-cache = "==2.10"
vininfo = "==1.8.0"

[tool.poetry.group.dev.dependencies]
boto3-stubs = "==1.35.45"
coveralls = "==4.0.1"
coverage = "==7.6.1"
mypy = "==1.12.1"
pre-commit = "==3.8.0"
pytest = "==7.4.4"
pytest-asyncio = "==0.23.7"
pytest-cov = "==5.0.0"
pytest-mock = "==3.12.0"
pytest-socket = "==0.7.0"
pytest-xdist = "==3.5.0"
types-requests = "==2.31.0.6"
types-python-dateutil = "^2.9.0.20241003"
types-pytz = "==2024.2.0.20240913"
ruff = "==0.7.0"
vulture = "==2.11"

@gbaian10
Copy link
Contributor

I guess this might be one of the sub-issues of #1977 #1978 ?
LangGraph does not handle many edge conditions well when using pydantic as the base class of state, which leads to many problems.

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

2 participants