Chapter 87 min read

Chapter 8: Tool Calling

Up until now, our Large Language Models have been isolated brains floating in a void. They can read the State, reason about it, and extract structured data, but they cannot interact with the world.

If you ask an LLM for the current stock price of Apple, it will hallucinate because its training data is static. It cannot browse the internet, it cannot query your live database, and it cannot execute mathematical equations reliably.

In this chapter, we will give the LLM arms and legs. We will explore how to define tools, how to bind them to a model, and how to execute those tools inside a LangGraph node.


8.1 Why Agents Need Tools

An Agent is simply an LLM equipped with tools and Control Flow.

When an LLM is given tools, the fundamental way it interacts with you changes. Instead of immediately returning a string answer, the LLM can pause, analyze the tools it has available, and return a "Tool Call Request."

It is saying: "I don't know the answer yet, but if you execute this specific tool with these specific arguments and give me the result, I can figure it out."


8.2 Defining Tools

A tool in LangChain is just a Python function wrapped in a schema. The easiest way to create a tool is using the @tool decorator.

from langchain_core.tools import tool

# The docstring and type hints are MANDATORY. 
@tool
def multiply(a: int, b: int) -> int:
    """Multiply two integers together."""
    return a * b

tools = [multiply]

What Does The LLM Actually Receive?

The LLM does not execute your Python code, and it cannot read your source code. When you bind a tool, the LLM simply receives a JSON schema describing the tool.

Conceptually, the LLM sees this:

{
  "name": "multiply",
  "description": "Multiply two integers together.",
  "parameters": {
    "a": "integer",
    "b": "integer"
  }
}

This is why explicit docstrings and strict type hints are non-negotiable. The LLM relies entirely on that schema to understand what the tool does and how to request it.


8.3 bind_tools()

Once you have defined your tools, you must explicitly bind them to your model so it receives the schemas.

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")

# Bind the tools!
llm_with_tools = llm.bind_tools(tools)

8.4 Understanding AIMessage.tool_calls

Let's see what happens when we invoke the bound model with a question that requires a tool.

response = llm_with_tools.invoke("What is 453 multiplied by 34?")

# Notice that the content string is EMPTY!
print("Content:", response.content) # Output: ""

# Instead, the model returns a structured payload!
print("Tool Calls:", response.tool_calls)
# Output:
# [{'name': 'multiply', 'args': {'a': 453, 'b': 34}, 'id': 'call_abc123'}]

The LLM did not do the math. It returned a structured payload instructing us to do the math.

Multiple Tool Calls: Modern models are highly intelligent. If you ask, "What is the weather in Paris and Tokyo?", the model can return an array containing two separate tool calls in a single response.

8.4.5 The ToolMessage

To complete the conversation loop, we must execute the requested Python function and hand the result back to the LLM. We do this by wrapping the result in a ToolMessage.

This completes the message hierarchy we learned in Chapter 6:

  1. HumanMessage: User speaks
  2. AIMessage (with tool_calls): Model reasons and requests a tool
  3. ToolMessage: Application executes tool and returns the result
  4. AIMessage (with content): Model reads the result and gives the final answer.

8.5 The ToolNode

In a basic Python script, you would write a manual for loop to extract the tool calls, run the functions, create the ToolMessages, and invoke the LLM again.

LangGraph automates this entirely using the pre-built ToolNode.

When the Graph routes to the ToolNode, it automatically inspects the latest model response, extracts any tool calls, executes the corresponding Python functions, and automatically appends the ToolMessage results back into the State.

from langgraph.prebuilt import ToolNode

# Create the node
tool_node = ToolNode(tools=[multiply])

8.6 Structured Output vs Tool Calling

Before we build our graph, we must establish a critical distinction. Beginners frequently confuse .with_structured_output() (Chapter 7) with .bind_tools().

Structured Output (with_structured_output)

  • Goal: Return Data.
  • Example: Extracting a customer's name from an email.
  • Result: A fully instantiated Pydantic object (e.g., Customer(name="Sarah")).

Tool Calling (bind_tools)

  • Goal: Take Action.
  • Example: Sending an email, searching the web, or querying a database.
  • Result: A tool_calls payload requesting the application to do work.

8.7 Building a Web Search & Calculator Agent

Let's build a functional multi-tool agent graph. We will give the agent the ability to multiply numbers and search the web (using a mock function).

Step 1: Tools and State

from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage

class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

@tool
def search_web(query: str) -> str:
    """Search the web for real-time information."""
    return "The capital of France is Paris."

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

tools = [search_web, multiply]
llm = ChatOpenAI(model="gpt-4o-mini").bind_tools(tools)

Step 2: The Nodes

from langgraph.prebuilt import ToolNode

# The LLM Node
def call_model(state: AgentState):
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

# The Tool Node
tool_node = ToolNode(tools=tools)

Step 3: The Conditional Router

We need an edge that decides if the LLM requested a tool, or if it generated a final text answer.

def should_continue(state: AgentState):
    last_message = state["messages"][-1]
    
    # If the LLM payload contains tool_calls, route to the tools!
    if last_message.tool_calls:
        return "tools"
        
    # Otherwise, stop!
    return END

Step 4: Build the Topology

from langgraph.graph import StateGraph, START, END

builder = StateGraph(AgentState)

builder.add_node("agent", call_model)
builder.add_node("tools", tool_node)

builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue)

# AFTER the tools execute, loop BACK to the agent so it can read the results!
builder.add_edge("tools", "agent")

app = builder.compile()

Step 5: Execute

from langchain_core.messages import HumanMessage

result = app.invoke({
    "messages": [HumanMessage(content="What is 12 multiplied by 5?")]
})

print("\n--- Final Messages ---")
for msg in result["messages"]:
    msg_type = msg.__class__.__name__
    print(f"[{msg_type}]: {msg.content}")

Trace Output:

  1. [HumanMessage]: What is 12 multiplied by 5?
  2. [AIMessage]: (Empty string, but requests 'multiply(12, 5)')
  3. [ToolMessage]: 60
  4. [AIMessage]: 12 multiplied by 5 is 60.

8.8 Common Tool Failures

As you begin building Agents with tools, watch out for these failures:

  1. Hallucinated Arguments: The LLM might try to call search_web(query="cats", engine="google"), but your Python function only accepts query. It will crash. Ensure your docstrings are perfectly clear.
  2. Missing Tool Implementations: If the LLM requests a tool named "search_wikipedia", but you forgot to pass it to the ToolNode, the node will throw an error.
  3. Tool Output Too Large: If a tool returns a 50,000-word Wikipedia article inside a ToolMessage, it will blow out the LLM's context window on the next loop.

8.9 Summary

By binding tools to the LLM and routing execution through LangGraph's cyclic topology, the LLM is no longer just answering questions—it is independently formulating plans, taking actions, observing results, and synthesizing final conclusions.

However, you still do not fully understand why the loop works, or how to handle multi-step reasoning failures. In the next chapter, we will master Agent Loops and ReAct Workflows.

    Chapter 8: Tool Calling — Mastering LangGraph: From State Machines to Production AI Agents | Krishna Tiwari