langgraph: add support for BaseModel updates to Command (#2747)

Simple update that adds support for the `update` attribute of the
`Command` class to support Pydantic `BaseModel` type.

LangGraph already supports [Pydantic models for graph
states](https://langchain-ai.github.io/langgraph/how-tos/state-model/).

Extending support to the `update` attribute allows users to pass custom
BaseModel instances. Additionally, updates defined as `BaseModel` types
are type-validated when created.

https://github.com/langchain-ai/langgraph/issues/2804

---------

Co-authored-by: vbarda <vadym@langchain.dev>
This commit is contained in:
Larsen Weigle
2025-01-23 14:31:44 -05:00
committed by GitHub
co-authored by vbarda
parent 38bbe67469
commit 39552255c8
2 changed files with 33 additions and 0 deletions
+3
View File
@@ -16,6 +16,7 @@ from typing import (
TypeVar,
Union,
cast,
get_type_hints,
)
from langchain_core.runnables import Runnable, RunnableConfig
@@ -289,6 +290,8 @@ class Command(Generic[N], ToolOutputMixin):
for t in self.update
):
return self.update
elif hints := get_type_hints(type(self.update)):
return [(k, getattr(self.update, k)) for k in hints]
elif self.update is not None:
return [("__root__", self.update)]
else:
+30
View File
@@ -9,6 +9,7 @@ import warnings
from collections import Counter, deque
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
from dataclasses import dataclass
from random import randrange
from typing import (
Annotated,
@@ -5134,6 +5135,35 @@ def test_dict_mixed_return() -> None:
assert graph.invoke({"foo": ""}) == {"foo": "ab"}
def test_command_pydantic_dataclass() -> None:
from pydantic import BaseModel
class PydanticState(BaseModel):
foo: str
@dataclass
class DataclassState:
foo: str
for State in (PydanticState, DataclassState):
def node_a(state) -> Command[Literal["node_b"]]:
return Command(
update=State(foo="foo"),
goto="node_b",
)
def node_b(state):
return {"foo": state.foo + "bar"}
builder = StateGraph(State)
builder.add_edge(START, "node_a")
builder.add_node(node_a)
builder.add_node(node_b)
graph = builder.compile()
assert graph.invoke(State(foo="")) == {"foo": "foobar"}
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC)
def test_command_with_static_breakpoints(
request: pytest.FixtureRequest, checkpointer_name: str