# The Tool-Calling Agent as a Graph — LangGraph Agents & Multi-Agent Systems

Source: https://www.geekswithgeeks.com/en/langgraph-agents/a-loop

> See the model-and-tools cycle as two nodes and a conditional edge.

## The ReAct-style loop

The classic agent is a small graph: a **model node** looks at the messages and either answers or requests a tool; a conditional edge checks whether a tool was requested; a **tools node** executes it and appends the result; an edge goes back to the model. The loop ends when the model answers without requesting a tool, or a limit is reached. In real use the model node calls a chat model bound to tool schemas, and the messages are `AIMessage` objects with `tool_calls` and `ToolMessage` results. LangGraph and LangChain ship prebuilt helpers (such as `ToolNode` and ready-made agent constructors) so you rarely write this by hand, but building it once makes the framework's behaviour clear.

## Model, tools, loop

A tool-calling agent is a two-node cycle: the model decides, the tool node acts, and the result returns to the model.

![Four pieces: model, tools, loop, limits.](assets/figures/langgraph-agents/section-3-map.svg) — Figure 3.1 — Model, tools, loop and limits.

## An agent loop from scratch, run

I ran this offline with langgraph 1.2.12 and langchain-core 1.6.6 in a Python virtual environment. No API key or model is needed because plain Python functions stand in for the model, so the output is repeatable. A scripted function plays the model: it asks for `add`, then `mul`, then answers. The tools node validates the tool name against an allow-list before executing. The final message list shows every step of the loop.

```python
import operator
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END

TOOLS = {"add": lambda a, b: a + b, "mul": lambda a, b: a * b}

class State(TypedDict):
    messages: Annotated[list, operator.add]

script = iter([   # stands in for a model that asks for tools, then answers
    {"tool": "add", "args": {"a": 2, "b": 3}},
    {"tool": "mul", "args": {"a": 5, "b": 4}},
    {"final": "2+3 is 5 and 5*4 is 20."},
])

def model(state):
    return {"messages": [next(script)]}

def run_tool(state):
    call = state["messages"][-1]
    allowed = call["tool"] in TOOLS                      # validate before executing
    result = TOOLS[call["tool"]](**call["args"]) if allowed else "error: unknown tool"
    return {"messages": [{"tool_result": result}]}

def after_model(state):
    return END if "final" in state["messages"][-1] else "tools"

g = StateGraph(State)
g.add_node("model", model); g.add_node("tools", run_tool)
g.add_edge(START, "model")
g.add_conditional_edges("model", after_model, {END: END, "tools": "tools"})
g.add_edge("tools", "model")
out = g.compile().invoke({"messages": [{"user": "what is 2+3, then 5*4?"}]}, {"recursion_limit": 10})
for m in out["messages"]:
    print(m)

```

Output:

```
{'user': 'what is 2+3, then 5*4?'}
{'tool': 'add', 'args': {'a': 2, 'b': 3}}
{'tool_result': 5}
{'tool': 'mul', 'args': {'a': 5, 'b': 4}}
{'tool_result': 20}
{'final': '2+3 is 5 and 5*4 is 20.'}
```

**Quiz:** When does the agent loop end?

- [x] When the model answers without requesting a tool, or a limit is hit
- [ ] After exactly one tool call
- [ ] Never
- [ ] When the GPU is idle

*Answer:* When the model answers without requesting a tool, or a limit is hit. The conditional edge routes to END once no tool is requested.
