CTCaliberTek
Course: LangGraph: Step by Step (9 Modules) · Lesson 4 of 9Log in to track your progress and earn a certificate when you finish the course.
AI & Machine Learning

LangGraph Module 4: Building an LLM Agent with Tools from Scratch

Published October 7, 2026·Updated October 29, 2026·10 min read
Like SaveLog in to like, save, and track progress.

Introduction

The standard agent loop is simple: the model decides whether to call a tool, a tool runs, and the result goes back to the model until it can answer. Building it by hand in LangGraph shows exactly what create_agent automates and gives you full control to customize it.

Prerequisites

  • Modules 1 to 3 completed
  • pip install langchain "langchain[anthropic]" and an API key

Step-by-Step Guide

Step 1: Set up the model and tools

Tools are defined with the @tool decorator, and bind_tools tells the model about them. The tools_by_name dictionary lets the tool node find a function from the name the model chose.

from langchain.chat_models import init_chat_model
from langchain.tools import tool

model = init_chat_model("anthropic:claude-opus-5-5", max_tokens=16000)

@tool
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

tools = [add, multiply]
tools_by_name = {t.name: t for t in tools}
model_with_tools = model.bind_tools(tools)

Step 2: Write the LLM node

The node calls the model with a system message plus the conversation so far and returns the reply as a state update. MessagesState appends it to the history.

from langchain.messages import SystemMessage, ToolMessage
from langgraph.graph import MessagesState

SYSTEM = SystemMessage(
    content="You do arithmetic. Always use the tools for calculations."
)

def llm_call(state: MessagesState) -> dict:
    reply = model_with_tools.invoke([SYSTEM] + state["messages"])
    return {"messages": [reply]}

Step 3: Write the tool node

If the last message asked for tools, run each requested tool and return a ToolMessage for each, using the matching tool_call_id so the model can connect results to requests.

def tool_node(state: MessagesState) -> dict:
    results = []
    for call in state["messages"][-1].tool_calls:
        observation = tools_by_name[call["name"]].invoke(call["args"])
        results.append(
            ToolMessage(content=str(observation), tool_call_id=call["id"])
        )
    return {"messages": results}

Step 4: Wire the loop

After the LLM node, a conditional edge sends the graph to the tool node if the model requested tools, or to the end if it produced a final answer. The tool node loops back to the LLM node.

from langgraph.graph import StateGraph, START, END

def should_continue(state: MessagesState) -> str:
    if state["messages"][-1].tool_calls:
        return "tool_node"
    return END

builder = StateGraph(MessagesState)
builder.add_node("llm_call", llm_call)
builder.add_node("tool_node", tool_node)

builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
builder.add_edge("tool_node", "llm_call")

agent = builder.compile()

Step 5: Run it and read the trace

Invoke with a message. Printing the history shows each turn: the question, the model's tool requests, the tool results, and the final answer. Compare this loop with create_agent from the LangChain course; they are the same pattern.

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What is (3 + 4) * 5?"}]}
)
for message in result["messages"]:
    message.pretty_print()

FAQs

Why build this by hand if create_agent exists?
To understand it and to customize it: adding an approval step, a planning node, extra state, or different routing is straightforward once you own the loop.
What stops this loop from running forever?
The model eventually answers without requesting a tool. As a safety net, add a step counter to state and route to END past a limit, as shown in Module 3.

Summary

An agent is an LLM node, a tool node, and a conditional edge that loops until no tool is requested. Owning this graph lets you add custom steps wherever you need them.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.