LangGraph Module 2: State Schemas and Reducers
Edited by Dev Kilaru, Senior Data Analyst. How we make our content
Introduction
State is the heart of a graph, and how updates are combined is controlled by reducers. By default an update overwrites the old value. With a reducer you can append instead, which is what you need for message histories and for parallel branches. This module explains the options.
Prerequisites
- Module 1 completed
Step-by-Step Guide
Step 1: Understand the default: overwrite
With no reducer, a node's returned value replaces the existing value for that key. That is fine for single values such as a counter or a final answer.
Step 2: Add a reducer to append instead
Annotate a key with a reducer function. A reducer receives the current value and the update, and returns the merged value. operator.add on lists appends the new items to the existing ones.
from operator import add
from typing import Annotated
from typing_extensions import TypedDict
class State(TypedDict):
count: int
log: Annotated[list[str], add]
def step_one(state: State) -> dict:
return {"count": state["count"] + 1, "log": ["step_one ran"]}
def step_two(state: State) -> dict:
return {"count": state["count"] * 10, "log": ["step_two ran"]}Step 3: Run it and compare the keys
The count key was overwritten at each step, while the log key accumulated entries from both nodes.
from langgraph.graph import StateGraph, START, END
builder = StateGraph(State)
builder.add_node(step_one)
builder.add_node(step_two)
builder.add_edge(START, "step_one")
builder.add_edge("step_one", "step_two")
builder.add_edge("step_two", END)
graph = builder.compile()
print(graph.invoke({"count": 1, "log": []}))
# {'count': 20, 'log': ['step_one ran', 'step_two ran']}Step 4: Use message state for chat
Conversation state is so common that LangGraph ships MessagesState, which holds a messages list with the add_messages reducer. It appends new messages and accepts plain dictionaries such as role and content pairs, converting them into message objects, so inside nodes use attribute access like state["messages"][-1].content. Subclass it to add your own fields.
from langgraph.graph import MessagesState
class State(MessagesState):
documents: list[str]Step 5: Separate input and output schemas
A graph can accept a narrow input, hold richer internal state, and return a narrow output. Pass input_schema and output_schema when creating the StateGraph. To clear a list that uses an append reducer, wrap the new value in Overwrite from langgraph.types, because an empty list would simply append nothing.
class InputState(TypedDict):
question: str
class OutputState(TypedDict):
answer: str
class OverallState(InputState, OutputState):
notes: str
builder = StateGraph(
OverallState, input_schema=InputState, output_schema=OutputState
)FAQs
- Why do parallel branches need reducers?
- If two nodes update the same key in the same step, LangGraph needs a rule for combining the updates. A reducer such as add provides it. Without one, concurrent writes to the same key raise an error.
- Can I mutate the state inside a node?
- Don't. Return a dictionary of updates instead. LangGraph applies the reducers and records the change, which is what makes checkpointing and replay work.
Summary
Overwrite is the default merge. Use a reducer like add to accumulate values, MessagesState for chat history, and input and output schemas to keep a graph's interface small.
Comments
Log in or create a free account to join the discussion.