diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index ce2f898d0..849ff6b47 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -764,14 +764,14 @@ class CompiledStateGraph(CompiledGraph): updates.extend(_get_updates(i) or ()) return updates elif (t := type(input)) and get_type_hints(t): - # Pydantic v2 + # Pydantic v1 if isinstance(input, BaseModelV1): keep: Optional[set[str]] = input.__fields_set__ defaults = {k: v.default for k, v in t.__fields__.items()} + # Pydantic v2 elif isinstance(input, BaseModel): keep = input.model_fields_set defaults = {k: v.default for k, v in input.model_fields.items()} - # Pydantic v1 else: keep = None defaults = {} diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 9ce1c2c66..a2a83b55d 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -20,6 +20,8 @@ from typing import ( ) from langchain_core.runnables import Runnable, RunnableConfig +from pydantic import BaseModel +from pydantic.v1 import BaseModel as BaseModelV1 from typing_extensions import Self from langgraph.checkpoint.base import BaseCheckpointSaver, CheckpointMetadata @@ -69,6 +71,11 @@ else: _DC_KWARGS = {"frozen": True} +# NOTE: this is redefined here separately from langgraph.constants +# to avoid a circular import +MISSING = object() + + def default_retry_on(exc: Exception) -> bool: import httpx import requests @@ -318,7 +325,28 @@ class Command(Generic[N], ToolOutputMixin): ): return self.update elif hints := get_type_hints(type(self.update)): - return [(k, getattr(self.update, k)) for k in hints] + # Pydantic v1 + if isinstance(self.update, BaseModelV1): + keep: Optional[set[str]] = self.update.__fields_set__ + defaults = {k: v.default for k, v in self.update.__fields__.items()} + # Pydantic v2 + elif isinstance(self.update, BaseModel): + keep = self.update.model_fields_set + defaults = {k: v.default for k, v in self.update.model_fields.items()} + else: + keep = None + defaults = {} + + return [ + (k, value) + for k in hints + if (value := getattr(self.update, k, MISSING)) is not MISSING + and ( + value is not None + or defaults.get(k, MISSING) is not None + or (keep is not None and k in keep) + ) + ] elif self.update is not None: return [("__root__", self.update)] else: