Unit 02.01: A node does one job to the state
The single most useful discipline in graph design is keeping each node to one job. It costs a few extra nodes and saves every debugging session afterwards.
One job, one testable function
A node that counts words is a function you can test with a string. A node that trims, counts and flags is three behaviours behind one name, and when the flag is wrong you have three candidates.
The code shows both, and tests the single-job versions with plain dictionaries.
from typing import TypedDict
class State(TypedDict):
text: str
words: int
flagged: bool
# One job each. Each is testable on its own, with no graph involved.
def count(state: State):
return {"words": len(state["text"].split())}
def flag(state: State):
return {"flagged": state["words"] > 5}
# A node that does three jobs. Which line failed when `flagged` is wrong?
def count_and_flag_and_trim(state: State):
text = state["text"].strip()
words = len(text.split())
return {"text": text, "words": words, "flagged": words > 5}
s = {"text": " a graph node should do one job ", "words": 0, "flagged": False}
print("count:", count(s))
print("flag :", flag({**s, "words": 7}))
print("\ntesting a single-job node needs no graph, no mocks, no checkpointer.")
count and flag are each three lines and need no graph, no mocks and no checkpointer to test. count_and_flag_and_trim needs all three inputs set up correctly to test any one of its behaviours.
The graph does get longer. That is the trade: more nodes, each trivially verifiable, against fewer nodes that each need reasoning about. In a system that will be debugged from a trace, more nodes is the better side of it - the trace names the node, so a narrow node names the bug.
The mistake this prevents
The mistake is merging nodes to reduce the diagram's size. The diagram is read once when someone joins the project; the trace is read every time something breaks. Optimise for the trace.
Takeaway
One job per node. The node name should describe exactly what changed in the state, and a test should be a dictionary in and a dictionary out.
