When two nodes write the same key
Both checks want to add a note to the same list. Run it and the program does not quietly get it wrong. It refuses.
Two nodes, one list
from typing_extensions import TypedDict
class State(TypedDict):
notes: listTwo checks, each adding one note. Exactly the fan-out from the last lesson, except that this time they write to the same key.
def check_account(state):
return {"notes": ["account is active"]}
def check_payments(state):
return {"notes": ["two charges on 3 March"]}from langgraph.graph import StateGraph, START
builder = StateGraph(State)
builder.add_node("check_account", check_account)
builder.add_node("check_payments", check_payments)
builder.add_edge(START, "check_account")
builder.add_edge(START, "check_payments")Now run it
from langgraph.errors import InvalidUpdateError
try:
print(builder.compile().invoke({"notes": []}))
except InvalidUpdateError as e:
print("refused:", e)By default, a key a node returns replaces whatever was there. For a category or a reply that is right. Two nodes finishing in the same step both want to replace the same key, and there is no sensible answer to which one wins, so LangGraph raises InvalidUpdateError rather than picking for you.
The message tells you the fix. It wants an annotated key, which means telling LangGraph how to combine two values instead of replacing.
The fix is one line
Nothing about the nodes or the edges changes. Only the state.
import operator
from typing import Annotated
from typing_extensions import TypedDict
class State(TypedDict):
notes: Annotated[list, operator.add]Annotated is plain Python, like TypedDict. It pins an extra piece of information to a type without changing the type. Here that extra piece is operator.add, the function LangGraph should use to put the old value and the new one together. On two lists, adding them joins them.
That second thing has a name. A reducer is the function that combines what is already in the state with what a node returned. No reducer means replace. A reducer means combine, however you say.
from langgraph.graph import StateGraph, START
def check_account(state):
return {"notes": ["account is active"]}
def check_payments(state):
return {"notes": ["two charges on 3 March"]}builder = StateGraph(State)
builder.add_node("check_account", check_account)
builder.add_node("check_payments", check_payments)
builder.add_edge(START, "check_account")
builder.add_edge(START, "check_payments")print(builder.compile().invoke({"notes": []})["notes"])Both notes, in the order the nodes were added. The refusal is gone because there is now an answer to what happens when two writers meet.
You can write your own
A reducer is an ordinary function of two arguments: what is there, and what came back.
def keep_longest(current, new):
return new if len(new) > len(current) else current
class State(TypedDict):
best_answer: Annotated[str, keep_longest]- Take
Annotatedback off and confirm the refusal returns. - Add a third check and see all three notes arrive.
- Swap
operator.addfor a reducer that keeps only the newest note.
Every expert started right here.