State beyond messages
An agent's state is more than its messages. Add keys of your own with state_schema, and tools can read and update them as the conversation goes on.
The shop wants to count how many orders a customer looked up in one conversation. That count belongs to the thread, like the messages, so it goes in the state: the dictionary the checkpointer saves after each step.
from langchain.agents import AgentState
class DeskState(AgentState):
lookups: intAgentState is the default state, with its messages key. Subclassing it adds keys, and state_schema=DeskState gives the agent the bigger shape.
A tool that updates the state
from langchain.messages import ToolMessage
from langchain.tools import ToolRuntime, tool
from langgraph.types import Command
ORDERS = {"A17": "shipped on 3 March", "C40": "waiting for stock"}
@tool
def lookup_order(order_id: str, runtime: ToolRuntime) -> Command:
"""Look up an order's shipping status by its id, such as A17."""
count = runtime.state.get("lookups", 0) + 1
text = f"{order_id} {ORDERS.get(order_id, 'is not an order we have')}."
reply = ToolMessage(text, tool_call_id=runtime.tool_call_id)
return Command(update={"lookups": count, "messages": [reply]})runtime.state is the current state. To change it, the tool returns a Command with an update: the new count, and the tool message it would otherwise have returned. The tool message has to be there, tagged with the call's id, because every tool call needs its result.
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from shop_model import ShopModel
agent = create_agent(ShopModel(), tools=[lookup_order], state_schema=DeskState,
checkpointer=InMemorySaver())
thread = {"configurable": {"thread_id": "ravi-1"}}agent.invoke({"messages": [{"role": "user", "content": "Where is A17?"}]}, thread)
result = agent.invoke({"messages": [{"role": "user", "content": "And C40?"}]}, thread)
print(result["lookups"])
print(result["messages"][-1].text)The count is 2 after two lookups in two calls. It came back with the conversation because it lives in the same state, saved by the same checkpointer.
Setting a key when you invoke
result = agent.invoke({"messages": [{"role": "user", "content": "Where is A17?"}], "lookups": 10},
{"configurable": {"thread_id": "fresh"}})
print(result["lookups"])Keys other than messages can be passed in with the input. This thread started at 10, and one lookup made it 11. Lesson 9's context is fixed for one call and never saved; state is saved and can change as the conversation goes on.
- Ask about A17 and C40 in one message and read the error when both calls update
lookupsin the same step. - Add a key
last_order: strtoDeskStateand set it in the tool's update. - Print
agent.get_state(thread).values.keys().
Every expert started right here.