mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-30 19:59:40 +02:00
## More robust Pydantic support for v2 streaming
When using `stream_version="v2"`, stream data and invoke results now
respect the graph's output/state schema types (Pydantic models,
dataclasses, etc.) instead of always returning raw dicts. This makes
working with typed state much more natural — no more manual
`Model(**chunk)` calls scattered through your code.
### Values stream coercion
`values` stream parts coerce data through the graph's output schema
mapper, so you get Pydantic models (or dataclasses) back directly:
```python
class MyState(BaseModel):
value: str
items: Annotated[list[str], operator.add]
graph = StateGraph(MyState).compile()
# v1: you get raw dicts back, have to reconstruct manually
for chunk in graph.stream(inputs, stream_mode="values"):
state = MyState(**chunk) # manual, error-prone
# v2: data is already a MyState instance
for part in graph.stream(inputs, stream_mode="values", stream_version="v2"):
assert isinstance(part["data"], MyState) # just works
print(part["data"].value) # attribute access, IDE autocomplete
```
This also works for dataclass-based state schemas. TypedDict state stays
as plain dicts (no change needed).
### Interrupts on stream parts
`values` stream parts now carry an `interrupts` field directly, removing
the need to cross-reference the `updates` stream:
```python
for part in graph.stream(inputs, config, stream_mode="values", stream_version="v2"):
if part["interrupts"]:
# handle interrupts inline — no need to check updates stream
for intr in part["interrupts"]:
print(intr.value)
```
### Checkpoint/debug coercion
Checkpoint and debug stream payloads also coerce their `values` through
the state schema mapper, so `stream_mode="checkpoints"` and
`stream_mode="debug"` return typed state too.
### Generic stream types
`StreamPart`, `ValuesStreamPart`, `CheckpointPayload`, etc. are now
generic over `StateT`/`OutputT`, enabling better static type checking
across the board.
### `GraphOutput` wrapper
This adds a new return type to `invoke()` which is a meaningful API
surface change.
`invoke(stream_version="v2")` returns a `GraphOutput[OutputT]` dataclass
with `.value` and `.interrupts` fields:
```python
result = graph.invoke({"value": "x", "items": []}, stream_version="v2")
# typed access
assert isinstance(result, GraphOutput)
assert isinstance(result.value, MyState) # coerced to schema type
assert result.interrupts == () # always available
# backward compat dict access still works
assert result["value"] == "x_a"
```
The concern: this changes the return type of `invoke()` in a way that
existing code patterns like `result["key"]` still work (via
`__getitem__`), but `isinstance(result, dict)` checks would break. Worth
discussing whether the ergonomic benefit justifies the migration cost.