Watching it work
A long run should not be a blank screen. Swap invoke for stream and you get each step as it finishes.
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START
class State(TypedDict):
category: str
reply: strbuilder = StateGraph(State)
builder.add_node("categorise", lambda s: {"category": "billing"})
builder.add_node("write_reply", lambda s: {"reply": "We will refund it."})
builder.add_edge(START, "categorise")
builder.add_edge("categorise", "write_reply")
graph = builder.compile()Stream it
for step in graph.stream({"category": "", "reply": ""}):
print(step)Each item is a dictionary with the node's name as the key and what that node changed as the value. That is exactly what an interface needs in order to say which step is running.
Asking for something else
What you just saw is the default, called updates. Ask for values instead and you get the whole state after each step rather than only the change.
for step in graph.stream({"category": "", "reply": ""}, stream_mode="values"):
print(step)Three items this time, not two. The first is the state before anything ran, which updates had nothing to say about.
| Mode | You get | Use it for |
|---|---|---|
updates | What each node changed | A progress log. The default. |
values | The whole state after each step | Redrawing a screen from the current state. |
messages | Words from the model as they arrive | Typing out a reply live. |
custom | Anything a node chooses to send | Progress from inside one long node. |
Words as they arrive
The mode people actually want is messages. It hands you what the model is saying while it is still saying it, which is how a chat window types out an answer.
from pretend_model import PretendModel
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, START, MessagesState
builder = StateGraph(MessagesState)
builder.add_node("call_model", lambda s: {"messages": [PretendModel().invoke(s["messages"])]})
builder.add_edge(START, "call_model")
for chunk, info in builder.compile().stream(
{"messages": [HumanMessage("Where is order A17?")]}, stream_mode="messages"
):
print(info["langgraph_node"], "->", repr(chunk.content))One chunk here, because our stand-in writes its whole answer at once. A real model sends a few characters at a time and this loop runs dozens of times, which is where the effect comes from.
version="v2", where every chunk is a dictionary with type, ns and data keys instead of the shape changing per mode. The older one is still the default and still works. Reach for v2 when you handle several modes at once and want one shape to unpack.- Print only
list(step)[0]in the first example, so it reads as a list of node names. - Pass
stream_mode=["updates", "values"]and look at what comes out. - Stream the agent loop from lesson 18 and watch it go round.
Every expert started right here.