Remembering the conversation
Run your agent twice and the second run knows nothing about the first. Everything it learned went in the bin the moment invoke returned.
That is not a bug. Every graph so far starts from the dictionary you hand it and forgets when it finishes. For a conversation that is useless, so LangGraph can save the state as it goes.
The graph, exactly as in lesson 15
from pretend_model import PretendModel
from langgraph.graph import StateGraph, START, MessagesState
model = PretendModel()
def call_model(state):
return {"messages": [model.invoke(state["messages"])]}One new argument
Everything in this lesson comes from this single word.
from langgraph.checkpoint.memory import InMemorySaver
builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_edge(START, "call_model")
graph = builder.compile(checkpointer=InMemorySaver())Give compile a checkpointer and LangGraph writes the state down after every step instead of letting it go. InMemorySaver is the simplest one, keeping everything in a Python dictionary for as long as your program runs.
And one new setting when you run it
A saved state has to be saved under a name, or a second user would walk into the first one's conversation.
config = {"configurable": {"thread_id": "asha"}}The thread id is the name of this conversation. The same id means carry on, and a different id starts a fresh one with nothing remembered.
You met that second argument to invoke in lesson 8, where it carried the recursion limit. It is the same argument, for anything belonging to this run rather than to the graph.
Two calls, one conversation
from langchain_core.messages import HumanMessage
graph.invoke({"messages": [HumanMessage("Hi, I am Asha")]}, config)
result = graph.invoke({"messages": [HumanMessage("Where is order A17?")]}, config)
for msg in result["messages"]:
print(f"{msg.type:<6} {msg.content}")Four messages, from two separate calls. The second invoke was handed one message and got back a conversation with all four in it, because the first two were waiting where the last run left them.
Looking at what was saved
The saved state is not hidden away. get_state hands it to you whenever you ask.
saved = graph.get_state(config)
print("messages saved:", len(saved.values["messages"]))
print("next to run: ", saved.next)values is the state itself, exactly as the last run left it. next is what was about to happen, and it is empty because the run finished. In lesson 23 you stop a run halfway through and that field stops being a curiosity.
Why a thread and not a variable
A real application has many people talking at once. If the conversation lived in a Python variable, everybody would share one.
The thread id keeps them apart. The same compiled graph serves one user or a million, and the only difference between them is a string.
InMemorySaver forgets when your program exits. It is for learning and for tests. Real use wants SqliteSaver on one machine or PostgresSaver on a server, and swapping to either is one line with nothing else touched.- Change the thread id on the second call and watch the memory disappear.
- Add a third turn and count the messages.
- Print
saved.valuesin full and see the whole state, not only the messages.
Every expert started right here.