- How to use state to define a graph’s schema
- How to use reducers to control how state updates are processed
Define state
State in LangGraph can be aTypedDict, Pydantic model, or dataclass. The examples below use TypedDict. Prefer that form for most graphs; see Use Pydantic models for graph state when you need runtime validation on inputs.
By default, a graph uses the same schema for input and output, and the state determines that schema. See Define input and output schemas for distinct input and output schemas.
Start with a messages field. That shape fits many LLM applications. For more detail, see Working with messages in graph state.
messages must return the full list.
Update state
Build a graph with a single node. The node is a Python function that reads graph state and returns updates. The first argument is always the state:extra_field.
Define a simple graph with this node. Use StateGraph for a graph that operates on this state, then add_node to populate the graph:
- Invocation started by updating a single key of the state.
- The invocation result includes the entire state.
Process state updates with reducers
Each key in the state can have its own reducer function, which controls how updates from nodes are applied. If no reducer is specified, updates to that key replace the previous value. ForTypedDict state schemas, define reducers by annotating the field with a reducer function.
In the earlier example, the node updated "messages" by returning the full concatenated list. Add a reducer so updates are appended automatically:
MessagesState
In practice, lists of messages need more than plain concatenation:- Update an existing message in the state by ID.
- Accept shorthand message formats, such as
{"role": "user", "content": "..."}.
add_messages that handles these cases:
MessagesState:
Resetting a reducer field
A common source of confusion with reducers is that with a merging reducer, returning an empty value does not clear the field. The reducer merges the right argument into the left one, so an empty update is merged in and previously accumulated values are kept. This pattern matters for error buffers or retry counters that must be cleared between retry attempts:Overwrite:
Bypass reducers with Overwrite
In some cases, you may want to bypass a reducer and directly overwrite a state value. When a node returns a value wrapped with Overwrite, the reducer is bypassed and the channel is set directly to that value.
This is useful when you want to reset or replace accumulated state rather than merge it with existing values.
LangGraph provides the Overwrite type for this purpose.
"__overwrite__":
Define input and output schemas
By default,StateGraph operates with a single schema, and all nodes communicate using that schema. You can also define distinct input and output schemas.
When distinct schemas are specified, an internal schema is still used for communication between nodes. The input schema validates the provided input. The output schema filters the internal data so invoke returns only the fields in the output schema.
Define distinct input and output schemas:
Pass private state between nodes
Nodes sometimes need intermediate data that should not appear in the graph’s main input or output schema. Keep that data private so only the nodes that need it can read it. The following sequential graph has three nodes. Private data passes between the first two steps. The third step only sees the public overall state.Private channels and streaming
Input, output, and private schemas constrain what each node reads and whatinvoke returns. They do not hide channels from streaming.
Values streaming emits all state channels by default, including private ones. That applies to the values projection from event streaming (stream.values on stream_events / streamEvents with version="v3"), and to graph.stream with stream_mode="values" / streamMode: "values". Values streaming uses the full state, not the output schema. A private channel can be missing from invoke output but visible in the stream.
This example uses distinct input, output, overall, and private schemas. invoke returns only the output schema:
View example trace
Open a public LangSmith run for this example.
bar channel:
View example trace
Open a public LangSmith run for this example.
output_keys to graph.stream:
stream_mode="updates" instead. See Streaming.
Use pydantic models for graph state
A StateGraph accepts astate_schema argument that specifies the shape of the state nodes can access and update.
Most examples on this page use a Python-native TypedDict or dataclass for state_schema. state_schema can be any type.
Use a Pydantic BaseModel for state_schema when you need runtime validation on inputs.
Known Limitations
- The output of the graph is not an instance of a Pydantic model.
- Runtime validation only occurs on inputs to the first node in the graph, not on subsequent nodes or outputs.
- The validation error trace from Pydantic does not show which node the error arises in.
- Pydantic’s recursive validation can be slow. For performance-sensitive applications, prefer a
dataclass.
Serialization Behavior
Serialization Behavior
When using Pydantic models as state schemas, serialization matters especially when:
- Passing Pydantic objects as inputs
- Receiving outputs from the graph
- Working with nested Pydantic models
Runtime Type Coercion
Runtime Type Coercion
Pydantic performs runtime type coercion for certain data types. That can help, but it can also produce unexpected values if you do not account for it.
Working with Message Models
Working with Message Models
When working with LangChain message types in a state schema, serialization matters. Use
AnyMessage (rather than BaseMessage) for proper serialization and deserialization when message objects travel over the wire.See also
Connect these docs to your agent of choice via MCP for real-time answers.

