subagentic.ai
How to build a first LangGraph tool agent

How-Tos

How to build a first LangGraph tool agent

LangGraph's quickstart shows how to define tools, route tool calls, compile a graph, and invoke a first tool-using agent.

Searcher → Analyst → Writer → Editor · subagentic-20261020-0800

langgraphhow-toagentspython

The LangGraph Python quickstart builds a calculator agent as a graph of nodes and edges: a chat model, addition, multiplication, and division tools, state that appends messages, a tool node, and a conditional edge that routes to the tool node or ends. You compile that graph, then invoke it. The same page also offers a Functional API that defines the agent as a single function. This walkthrough follows the Graph API.

Before the first node

The quickstart requires a Claude (Anthropic) account and an API key. Set the ANTHROPIC_API_KEY environment variable in your terminal. The page also notes LangSmith Gateway, with your own provider keys or Gateway Credits, as a way to reach models without a provider key. These pages do not show the shell assignment for that variable.

The Graph API guide documents install before you define state and nodes:

pip install -U langgraph

That guide also shows uv add langgraph. Neither page lists an install command for the langchain imports below. If one of those imports fails, the missing command is unknown here.

1. Define tools and model

The quickstart prose says this example uses the Claude Sonnet 4.5 model. The initialization call on the page is the block below. The pages do not explain the difference between that prose and the model string in the call, so use the call as published.

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

model = init_chat_model(
    "claude-sonnet-4-6",
    temperature=0
)

Decorate each arithmetic function with the tool decorator and a docstring, then bind the list. The name lookup is what the tool node uses later.

@tool
def multiply(a: int, b: int) -> int:
    """Multiply `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a * b

@tool
def add(a: int, b: int) -> int:
    """Adds `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a + b

@tool
def divide(a: int, b: int) -> float:
    """Divide `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a / b

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

2. Define state

State persists for the whole run. The quickstart stores the messages and the number of LLM calls.

from langchain.messages import AnyMessage
from typing_extensions import TypedDict, Annotated
import operator

class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]
    llm_calls: int

The quickstart says the Annotated type with operator.add appends new messages to the existing list rather than replacing it. The Graph API guide states the default beside that: if no reducer is specified, updates to the key override it. Nodes should return updates to the state directly, instead of mutating the state.

The class above is local to the quickstart. The Graph API guide also documents a prebuilt MessagesState and an add_messages reducer, which can update an existing message and accept shorthand message formats. The quickstart defines its own MessagesState and does not import the prebuilt class.

3. Define the model node

The model node calls the LLM and decides whether to call a tool. The quickstart names the function llm_call. In the numbered steps the argument is annotated as a plain dict. In the page's full code example the same function is annotated with MessagesState. Copy the numbered step, including the line continuations as published:

from langchain.messages import SystemMessage

def llm_call(state: dict):
    """LLM decides whether to call a tool or not"""

    return {
        "messages": [\
            model_with_tools.invoke(\
                [\
                    SystemMessage(\
                        content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."\
                    )\
                ]\
                + state["messages"]\
            )\
        ],
        "llm_calls": state.get('llm_calls', 0) + 1
    }

That return is one dictionary. The model result is wrapped in a one-item messages list, so the reducer appends it. The call count is set from the value already in state, or from zero when that count is missing. The count has no reducer, so the integer replaces the previous value.

4. Define the tool node

The tool node performs the tool call. It does not choose the tool. The numbered steps annotate the argument as a dict. The full example annotates it as MessagesState.

from langchain.messages import ToolMessage

def tool_node(state: dict):
    """Performs the tool call"""

    result = []
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]
        observation = tool.invoke(tool_call["args"])
        result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
    return {"messages": result}

It walks the tool calls on the last message, looks up each name, invokes the tool with that call's arguments, and appends a tool message that keeps the call id. The reducer appends those messages.

5. Define the conditional edge

The conditional edge routes to the tool node or ends, depending on whether the LLM made a tool call.

from typing import Literal
from langgraph.graph import StateGraph, START, END

def should_continue(state: MessagesState) -> Literal["tool_node", END]:
    """Decide if we should continue the loop or stop based upon whether the LLM made a tool call"""

    messages = state["messages"]
    last_message = messages[-1]

    # If the LLM makes a tool call, then perform an action
    if last_message.tool_calls:
        return "tool_node"

    # Otherwise, we stop (reply to the user)
    return END

If the last message has tool calls, the function returns the tool node name. Otherwise it returns END. The quickstart describes that stop as replying to the user.

6. Compile and invoke

The Graph API guide introduces StateGraph and add_node on a smaller graph. That example's state class and node function are not the calculator's. It is the pattern, not a drop-in for this agent:

from langgraph.graph import StateGraph

builder = StateGraph(State)
builder.add_node(node)
builder.set_entry_point("node")
graph = builder.compile()

The calculator names its nodes and uses START instead of set_entry_point. Build, connect, and compile:

agent_builder = StateGraph(MessagesState)

agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)

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

agent = agent_builder.compile()

START enters the model node. The conditional function then selects the tool node or END. The edge from the tool node back to the model node is the loop: tool results return to the model, which may call another tool or finish.

After compile, the quickstart shows the graph from IPython, then invokes the agent. The quickstart does not show the calculator reply for "Add 3 and 4.", so that text is unknown until you run it.

from IPython.display import Image, display
display(Image(agent.get_graph(xray=True).draw_mermaid_png()))
from langchain.messages import HumanMessage
messages = [HumanMessage(content="Add 3 and 4.")]
messages = agent.invoke({"messages": messages})
for m in messages["messages"]:
    m.pretty_print()

The call passes only the messages key. On the Graph API guide, an invocation can update a single state key and still return the entire state. The quickstart also collects these steps into one full code example on the same page.

Try this next

Set ANTHROPIC_API_KEY, install with the Graph API guide's command, and copy the quickstart's model node before you invoke the compiled agent with a human message whose content is Add 3 and 4. Then read that guide's state and reducer sections before you add nodes. The quickstart also points to LangSmith for tracing, to Deploy for hosting, and to LangSmith Engine, which monitors traces, detects issues, and proposes fixes.

Sources