LangGraphLangGraph 1.2 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
38 small wins to finish your pathNext lesson →

Capstone: the support agent

The support agent brings the course together: one graph that reads a ticket, looks things up with a tool, remembers the conversation, and can be paused for a human before anything it cannot undo.

Last updated: 27 Sep, 2026 · LangGraph 1.2

Every piece here appeared in an earlier lesson. The agent loop calls a model and runs tools; a checkpointer gives it memory; an interrupt puts a human in front of a refund. Assembled, they are a support agent.

The pieces, and where each came from

PieceFrom lesson
Agent loop (call model, run tools, loop back)The agent loop
A tool the model can callTools and bind_tools
Memory across turnsCheckpointer
A human approval before a refundinterrupt

The imports

Start with the imports. Each line brings in one thing from an earlier lesson.

python
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import tool
from langchain.messages import HumanMessage, AIMessage, ToolMessage

The lookup tool

Next, a tool the model can call. It is a plain function with the @tool decorator.

python
@tool
def lookup_order(order_id: str) -> str:
    """Look up an order by id."""          # the docstring tells the model what the tool does
    return f"Order {order_id}: shipped on 3 March."

The model node

Now the model. A real chat model goes here; this stand-in stands in for one so the lesson runs without an API key. It reads the last message and decides the next move.

python
def call_model(state):
    last = state["messages"][-1]            # look at the newest message

    if isinstance(last, ToolMessage):       # a tool has run -> answer with its result
        return {"messages": [AIMessage(last.content)]}

    if isinstance(last, HumanMessage) and "order" in last.content.lower():
        # user asked about an order -> ask to run the lookup tool
        return {"messages": [AIMessage(content="", tool_calls=[
            {"name": "lookup_order", "args": {"order_id": "A17"}, "id": "c1"}])]}

    return {"messages": [AIMessage("How can I help with your order?")]}  # otherwise, greet
  • If the last message is a ToolMessage, the tool has run, so reply with its result.
  • If the user mentioned an order, ask to run lookup_order (a tool call, not a final answer).
  • Otherwise, greet and wait.

Wiring the graph

Wire the three pieces into a graph. The edges are what make it loop.

python
b = StateGraph(MessagesState)
b.add_node("call_model", call_model)
b.add_node("tools", ToolNode([lookup_order]))          # runs whatever tool the model asks for
b.add_edge(START, "call_model")
b.add_conditional_edges("call_model", tools_condition) # tool call? -> tools, else -> stop
b.add_edge("tools", "call_model")                      # after a tool, hand back to the model
agent = b.compile(checkpointer=InMemorySaver())        # the checkpointer is the memory
  • tools_condition sends a tool call to the tools node, and anything else to the end.
  • The edge from tools back to call_model is the loop: run a tool, then let the model use the result.
  • InMemorySaver saves the state per thread, so the agent remembers past turns.

Running two turns

Run two turns on the same thread so the second remembers the first.

python
cfg = {"configurable": {"thread_id": "1"}}    # same thread id = same memory

agent.invoke({"messages": [HumanMessage("hi")]}, cfg)                            # turn 1
out = agent.invoke({"messages": [HumanMessage("where is my order A17?")]}, cfg)  # turn 2
print(out["messages"][-1].content)

The support agent end to end

The same pieces in one file, ready to run.

Example
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import tool
from langchain.messages import HumanMessage, AIMessage, ToolMessage

@tool
def lookup_order(order_id: str) -> str:
    """Look up an order by id."""
    return f"Order {order_id}: shipped on 3 March."

def call_model(state):
    last = state["messages"][-1]
    if isinstance(last, ToolMessage):
        return {"messages": [AIMessage(last.content)]}
    if isinstance(last, HumanMessage) and "order" in last.content.lower():
        return {"messages": [AIMessage(content="", tool_calls=[
            {"name": "lookup_order", "args": {"order_id": "A17"}, "id": "c1"}])]}
    return {"messages": [AIMessage("How can I help with your order?")]}

b = StateGraph(MessagesState)
b.add_node("call_model", call_model)
b.add_node("tools", ToolNode([lookup_order]))
b.add_edge(START, "call_model")
b.add_conditional_edges("call_model", tools_condition)
b.add_edge("tools", "call_model")
agent = b.compile(checkpointer=InMemorySaver())

cfg = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": [HumanMessage("hi")]}, cfg)
out = agent.invoke({"messages": [HumanMessage("where is my order A17?")]}, cfg)
print(out["messages"][-1].content)

What the two turns showed

  • Turn one greeted; turn two, on the same thread, asked about an order, so the model requested the tool.
  • tools_condition routed to the tools node, ToolNode ran lookup_order, and the model answered from the result.
  • The checkpointer kept both turns on thread "1", so the agent carried the conversation across the two calls.

Going live

To make it real, swap the stand-in call_model for one that calls model.bind_tools([lookup_order]).invoke(state["messages"]), and add an interrupt before any refund tool. The rest of the graph is unchanged.

When to build an agent like this

  • A support or operations assistant that looks things up and remembers a customer.
  • The base an approval step and more tools bolt onto.
Watch out. Memory needs the same thread_id across turns. A new id each turn is the reason an agent forgets what was said a moment ago.
Try it yourself
  • Add a third turn on the same thread and confirm the earlier ones are still there.
  • Add an interrupt in a refund node and resume it with an approval.

You understood something today that you didn't yesterday.