What we're building
One graph that deliberately exercises everything from this tier: a multi-field state with a reducer, a straight run of nodes, a conditional branch, a loop, and a streamed run. The pipeline: clean input text, classify it as either "short" or "long," branch to a different transform node depending on which, and if the "long" branch's result is still too long after transforming, loop back and shorten it again until it fits, then format a final combined output.
START -> clean -> classify -+-> expand_transform ---+-> combine -> END | | +-> shrink_transform <-loop-+ (loops back to itself until short enough)Wiring it up
builder.add_edge(START, "clean")builder.add_edge("clean", "classify")builder.add_conditional_edges("classify", route_by_classification)builder.add_conditional_edges("shrink_transform", is_short_enough)builder.add_edge("expand_transform", "combine")builder.add_edge("combine", END)Nothing here is a new concept, every piece was covered in Lessons 1 through 11, this lesson's job is combining them in one graph instead of introducing anything further:
- State with a reducer (Lesson 2): a
stepsfield,Annotated[list[str], operator.add], every node appends a short note of what it did, so the final state carries a full trail of what happened during the run. - A straight run of nodes (Lesson 3):
cleanruns unconditionally first,combineruns unconditionally last. - A conditional branch (Lesson 4):
route_by_classificationsends short text toexpand_transformand long text toshrink_transform. - A loop (Lesson 5):
shrink_transformroutes back to itself viais_short_enoughuntil the text is under a target length, then continues on tocombine. - Streaming (Lesson 8):
main()runs the graph withapp.stream(..., stream_mode="updates")instead of a plain.invoke(), so you can watch each node's contribution as it happens, including every pass through the loop.
Why no model call in this one
This checkpoint project deliberately stays in pure Python, no ChatGoogleGenerativeAI call, so that what's being tested is graph mechanics (state, reducers, branching, looping, streaming) on their own, without a network call's latency or variability making the run harder to follow. Lessons 6, 7, 9, and 11 already covered wiring a model into a node and streaming its tokens, that skill combines with everything here the moment you need it, nothing about adding a model call here would exercise new graph mechanics.
Checkpoint: the whole beginner tier
- state and reducers (Lessons 1-2): a node returns a partial dict that's merged into state, plain fields use last-write-wins,
Annotated[type, reducer]fields (likeoperator.addfor lists) combine instead of overwrite. - wiring nodes (Lesson 3):
add_noderegisters a function under a name,add_edgeconnects node names,START/ENDmark the boundaries,.compile()then.invoke()runs it. - branching (Lesson 4):
add_conditional_edgeswith a routing function (state in, node name out) picks the next node at runtime. - cycles (Lesson 5): a routing function can route back to a node already visited, something a straight-line LCEL chain cannot express, this is what makes retry and refine loops possible.
- messages and models in nodes (Lessons 6-7):
MessagesStateandadd_messagesfor conversations,ToolNodeandtools_conditionfor the think/act loop, both built from the same node/edge/cycle primitives as everything else. - streaming (Lessons 8-9):
stream_mode="values"(full snapshots),"updates"(per-node diffs), and"messages"(token-by-token from a model call inside a node) are three different granularities of watching a run happen instead of waiting for it to finish. - config and safety (Lesson 10): the
configdict carries run settings separate from state;recursion_limitstops a runaway cycle withGraphRecursionErrorinstead of looping forever. - visualizing (Lesson 11):
app.get_graph().draw_mermaid()prints the compiled shape, useful for checking a graph matches what you intended once it's too big to picture from code alone.
You now have every primitive LangGraph gives you before memory enters the picture. Lesson 13, starting the intermediate tier, adds InMemorySaver and thread_id so a graph can remember previous runs, the beginning of giving these graphs real memory across separate .invoke() calls.
If anything here still feels unclear, ask before moving to Lesson 13.