Changes since 1.0.10
- release(langgraph): 1.1 (#7102)
- fix: replay behavior for parent + subgraphs! (#7038)
- feat: type safe stream/invoke w/ proper output type coercion (#6961)
Type-Safe Streaming & Invoke
LangGraph 1.1 introduces version="v2" — a new opt-in streaming format that brings full type safety to stream(), astream(), invoke(), and ainvoke().
What's changing
v1 (default, unchanged): stream() yields bare tuples like (stream_mode, data) or just data. invoke() returns a plain dict. Interrupts are mixed into the output dict under "__interrupt__".
v2 (opt-in): stream() yields strongly-typed StreamPart dicts with type, ns, data, and (for values) interrupts fields. invoke() returns a GraphOutput object with .value and .interrupts attributes. When your state schema is a Pydantic model or dataclass, outputs are automatically coerced to the correct type.
invoke() / ainvoke() with version="v2"
from langgraph.types import GraphOutput
result = graph.invoke({"input": "hello"}, version="v2")
# result is a GraphOutput, not a dict
assert isinstance(result, GraphOutput)
result.value # your output — dict, Pydantic model, or dataclass
result.interrupts # tuple[Interrupt, ...], empty if none occurredWith a non-"values" stream mode, invoke(..., stream_mode="updates", version="v2") returns list[StreamPart] instead of list[tuple].
stream() / astream() with version="v2"
for part in graph.stream({"input": "hello"}, version="v2"):
if part["type"] == "values":
part["data"] # OutputT — full state
part["interrupts"] # tuple[Interrupt, ...]
elif part["type"] == "updates":
part["data"] # dict[str, Any]
elif part["type"] == "messages":
part["data"] # tuple[BaseMessage, dict]
elif part["type"] == "custom":
part["data"] # Any
elif part["type"] == "tasks":
part["data"] # TaskPayload | TaskResultPayload
elif part["type"] == "debug":
part["data"] # DebugPayloadEach stream mode has its own TypedDict — ValuesStreamPart, UpdatesStreamPart, MessagesStreamPart, CustomStreamPart, CheckpointStreamPart, TasksStreamPart, DebugStreamPart — all importable from langgraph.types. The union type StreamPart is a discriminated union on part["type"], enabling full type narrowing in editors and type checkers.
Pydantic & dataclass output coercion
When your graph's state schema is a Pydantic model or dataclass, version="v2" automatically coerces outputs to the declared type:
from pydantic import BaseModel
class MyState(BaseModel):
answer: str
count: int
graph = StateGraph(MyState)
# ... build graph ...
compiled = graph.compile()
result = compiled.invoke({"answer": "", "count": 0}, version="v2")
assert isinstance(result.value, MyState) # not a dict!Backward compatibility
- Default is still
version="v1"— existing code works without changes. - To make migration easier,
GraphOutputsupports old-style best-effort access to graph values and interrupts. Dict-style access (result["key"],"key" in result,result["__interrupt__"]) still works and delegates toresult.value/result.interruptsunder the hood. However, this is deprecated and emits aLangGraphDeprecatedSinceV11warning. It will be removed in v3.0 — migrate toresult.valueandresult.interruptsat your convenience.
result = graph.invoke({"input": "hello"}, version="v2")
# Old style — still works, but deprecated
result["input"] # delegates to result.value["input"]
result["__interrupt__"] # delegates to result.interrupts
"input" in result # delegates to "input" in result.value
# New style — preferred
result.value["input"]
result.interruptsMigration Guide
- No action required —
version="v1"remains the default. All existing code continues to work. - Adopt v2 incrementally — Add
version="v2"to individualinvoke()/stream()calls to get typed outputs. - Use typed imports — Import
GraphOutput,StreamPart, and individual part types fromlanggraph.typesfor type-safe code.