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 →

The agent loop

The agent loop is the pattern that makes an agent: call the model, and if it asked for a tool, run the tool and loop back to the model, otherwise stop.

Last updated: 27 Sep, 2026 · LangGraph 1.2

Everything so far comes together here. The one new idea is the edge from the tools node back to the model, so the model can read the tool's result and decide again.

The agent-loop wiring

python
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition

def call_model(state: MessagesState):
    return {"messages": [model_with_tools.invoke(state["messages"])]}

builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "call_model")
builder.add_conditional_edges("call_model", tools_condition)  # -> "tools" or END
builder.add_edge("tools", "call_model")                       # tools back to the model
agent = builder.compile()

To watch the loop run without an API key, a stand-in stands in for the model. It asks for the tool on the first turn, then answers from the tool result on the second. Build the graph one piece at a time.

The imports

Start with the imports: the graph pieces, the tools node and its router, the tool decorator, and the message types.

python
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition   # tools node + ready-made router
from langchain.tools import tool
from langchain.messages import HumanMessage, AIMessage, ToolMessage

The tool

Define the tool the agent can call. It takes an order id and returns a short line about the order.

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

The stand-in model

Now the stand-in model. It reads the last message and decides the next move.

python
def call_model(state):
    last = state["messages"][-1]
    if isinstance(last, ToolMessage):
        return {"messages": [AIMessage(last.content)]}          # answer from the tool result
    return {"messages": [AIMessage(content="", tool_calls=[     # first turn: ask for the tool
        {"name": "lookup_order", "args": {"order_id": "A17"}, "id": "call_1"}])]}
  • If the last message is a ToolMessage, the tool has run, so reply with its content.
  • Otherwise, return a tool call for lookup_order instead of a final answer.

Wiring the loop

Wire the two nodes into a graph. The edges are what make it loop.

python
builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_node("tools", ToolNode([lookup_order]))
builder.add_edge(START, "call_model")
builder.add_conditional_edges("call_model", tools_condition)  # tool call? -> tools, else END
builder.add_edge("tools", "call_model")                       # the loop: back to the model
agent = builder.compile()
  • tools_condition sends a tool call to the tools node, and anything else to END.
  • The edge from tools back to call_model is the loop: run the tool, then let the model read the result.

Running the agent

Run one question through the agent and print the final message.

python
out = agent.invoke({"messages": [HumanMessage("Where is order A17?")]})
print(out["messages"][-1].content)

The agent loop end to end

The same pieces in one file.

Example
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition
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)]}          # answer from the tool result
    return {"messages": [AIMessage(content="", tool_calls=[     # first turn: ask for the tool
        {"name": "lookup_order", "args": {"order_id": "A17"}, "id": "call_1"}])]}

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

out = agent.invoke({"messages": [HumanMessage("Where is order A17?")]})
print(out["messages"][-1].content)

How the run reached its answer

The model asked for lookup_order, tools_condition sent the run to the tools node, ToolNode ran the tool and appended its result, the edge sent it back, and this time the model answered with plain text so the run reached END. Swapping the stand-in for model.bind_tools([lookup_order]) is the only change to make it real.

The four pieces, and one trip round the loop
invoketool_callstool answerno tool callcallsSTARTChat modelPretendModel + toolcall_modelnodetoolsToolNodelookup_order@toolEND
Hover or tap a piece to see what it is and which lesson built it.
Trace a request

Pick one to watch it run, step by step.

How a run flows

  • START → call_model: the model reads the conversation.
  • call_model → tools (via tools_condition) if it asked for a tool; the tool runs and returns a result.
  • tools → call_model: the model reads the result and answers, or asks for another tool.
  • call_model → END when the model replies with no tool call.

The load-bearing edge

The edge tools → call_model is what makes this an agent rather than a one-shot call. Without it the model could ask for a tool but never see the answer. With it, the model can look something up, read the result, and use it in its reply.

Where the agent loop fits

  • Any assistant that looks things up before answering.
  • The base shape every tool-using agent is built on, by hand here and with create_agent in the next lesson.
Watch out. Forget the tools → call_model edge and the run ends after one tool call, so the model never uses the result. It is the single most common agent-loop bug.
Try it yourself
  • Draw the four transitions above on paper before running anything.
  • Remove the tools → call_model edge and describe what breaks.

Every expert started right here.