|
|
LangGraph State and TypedDict Reducers
Author: Venkata Sudhakar
In LangGraph every node reads from and writes to a shared State object. The State is a TypedDict - a Python typed dictionary - and its schema defines exactly what data flows through the agent. When a node returns a dict, LangGraph merges it into the current state. By default a returned value replaces the existing field value. But for fields like message history where you want to append rather than replace, you use an Annotated type with a reducer function to control the merge. The most common reducer is operator.add, which concatenates lists. When you declare Annotated[list, operator.add], every node that returns that field has its value appended to the existing list rather than replacing it. This is how LangGraph handles message history - each node adds its messages to the growing conversation. You can also write custom reducer functions for deduplication or priority-based merging. Understanding reducers is essential for building correct multi-node agents. The below example shows a two-node graph demonstrating the difference between the default replace behaviour and the list-append reducer.
Running the graph to see reducer behaviour,
It gives the following output,
node_a sees 1 messages, status = initial
node_b sees 2 messages, status = node_a_done
Final state:
messages count: 3
messages: ["Start the pipeline", "Response from node A", "Response from node B"]
status: node_b_done <- replaced each time, only last value survives
steps_taken: ["node_a", "node_b"] <- appended by both nodes, full history kept
# Without operator.add on messages:
# node_b would only see node_a return value, not the original human message
# The full conversation history would be lost
Custom reducer ensures no URL is recorded twice,
# node_a returns: {"visited_urls": ["https://site.com/page1", "https://site.com/page2"]}
# node_b returns: {"visited_urls": ["https://site.com/page2", "https://site.com/page3"]}
# dedupe_union result: ["https://site.com/page1", "https://site.com/page2", "https://site.com/page3"]
# page2 is not duplicated even though both nodes returned it
Reducer selection guide: use operator.add for message history, log lists, and any field that should accumulate across nodes. Use no reducer (default replace) for the current task, current status, or any single-value field that each node updates to a new value. Use a custom reducer when you need deduplication, max/min selection, or merging complex objects. Getting reducers right is the most important design decision in a multi-node LangGraph agent.
|
|