Learn LangChain - Tools & Function Calling
Episode 8 of 23

Learn LangChain - Tools & Function Calling

Giving your agent the ability to act: defining tools with the @tool decorator, binding them to a model via bind_tools, and understanding the selection, execution, and error handling loop of tool calling.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 7 you gave your agent memory through chat history and checkpoints. That memory is still passive — the agent only talks. Now it's time for the agent to act. With tools, an agent can calculate, call APIs, read databases, or modify files. This is the bridge from being just a chatbot to being an assistant that genuinely gets work done.

This episode dissects two things: tool definition with the @tool decorator, parameter schema, and docstrings; then the tool calling loop — how the model selects a tool, how its result is executed and returned to the model, plus error handling features like ProviderToolSearchMiddleware, the built-in apply_patch, and error handling strategies.

Creating Tools with the @tool Decorator

The most idiomatic way to define a tool is the @tool decorator. An ordinary Python function given this decorator automatically becomes a StructuredTool whose schema is derived from type hints and the docstring.

PythonFirst tool with @tool
from langchain_core.tools import tool
 
@tool
def hitung_umur(tahun_lahir: int, tahun_sekarang: int) -> int:
    """Hitung umur seseorang berdasarkan tahun lahirnya."""
    return tahun_sekarang - tahun_lahir

Just one decorator. LangChain reads the int parameter types, the int return type, and the docstring as the tool description. All three become the contract translated into a tool schema for the model.

Parameter Schema and Good Docstrings

The quality of a tool's schema determines the quality of the model's tool selection. The docstring isn't just documentation — it's the model's only hint about when this tool should be used. A vague description leads to wrong tool selection.

PythonTool with a detailed schema and description
from langchain_core.tools import tool
 
@tool
def cari_pengguna(nama: str, aktif_saja: bool = False) -> list[dict]:
    """Cari pengguna di database berdasarkan nama.
    Gunakan tool ini ketika user bertanya tentang pengguna.
    Parameter aktif_saja membatasi hasil ke akun yang masih aktif."""
    return [{"nama": nama, "aktif": True}]
 
print(cari_pengguna.name)
print(cari_pengguna.args_schema.schema())

cari_pengguna.args_schema.schema() shows the generated JSON schema — notice how the docstring and parameter types are translated into description, type, and default properties. The more explicitly you write, the more accurately the model calls it.

Info

Write docstrings as imperative sentences, not passive descriptions. Sentences like "Use this tool when..." are proven to help models decide when a tool is relevant.

Binding Tools to a Model with bind_tools

Once a tool is defined, bind it to a model. bind_tools adds the tool definitions to the model's context so the model can "request" a tool call in its response.

PythonBinding tools to a model
from langchain_openai import ChatOpenAI
 
model = ChatOpenAI(model="gpt-4o-mini")
model_bertools = model.bind_tools([hitung_umur, cari_pengguna])
 
respons = model_bertools.invoke("Umur orang kelahiran 1990 berapa sekarang?")
print(respons.tool_calls)

respons.tool_calls contains the list of calls the model requested — the tool name and its arguments. At this stage the model hasn't executed anything; it has only "submitted a request". Execution is the responsibility of the loop on your side.

The Tool Calling Loop: Selection, Execution, Feedback

A single invoke call usually isn't enough. Tool calling is a loop: the model requests a tool, the application executes it, the result is returned as a ToolMessage, then the model evaluates that result to continue or finish the response. LangGraph automates this entire cycle via create_agent.

PythonAutomatic tool calling loop with create_agent
from langgraph.prebuilt import create_agent
 
agent = create_agent(model=model, tools=[hitung_umur, cari_pengguna])
respons = agent.invoke({
    "messages": [("human", "Umur orang kelahiran 1990, dan cari user bernama Arman")]
})
for pesan in respons["messages"]:
    print(pesan.type, "->", pesan.content[:60] if pesan.content else pesan.tool_calls)

The respons["messages"] trace shows the complete cycle: a HumanMessage comes in, an AIMessage contains two tool calls, two ToolMessages carry the execution results, and a final AIMessage summarizes the answer for the user. This is the core of agentic behavior: the model doesn't just talk, it acts and evaluates the results of its actions.

Handling Tool Errors

Tools can fail — API down, invalid arguments, or empty results. The basic strategy: don't let an exception destroy the loop. Return the error as a tool result so the model can fix it, for example by asking for the correct input.

PythonTool with error fallback
from langchain_core.tools import tool
 
@tool
def bagi(a: float, b: float) -> float:
    """Bagikan a dengan b."""
    try:
        return a / b
    except ZeroDivisionError:
        return float("inf")

By returning a value instead of throwing an exception, the model receives a result and can tell the user in human language, for example "can't divide by zero". Another alternative: give the tool its own validation capability. For unexpected errors, LangGraph forwards the exception as an error ToolMessage so the loop can convey the failure context to the model.

Danger

Don't rely on empty try/except blocks that swallow all errors. The failure context still has to reach the model — otherwise the model will guess at why the tool failed.

ProviderToolSearchMiddleware and apply_patch

Two modern features in the 2026 ecosystem you should get to know. ProviderToolSearchMiddleware makes tools searched and called via the provider's tool search, useful when the tool list is so large that not all tool definitions need to be sent to the model in one request. Meanwhile, apply_patch is a built-in tool that lets an agent edit files using a concise, targeted diff format, ideal for programming tasks.

PythonLoading apply_patch and a tool search middleware
from langchain_core.tools import apply_patch  # example import, name varies per release
from langgraph.prebuilt import create_agent
 
agent = create_agent(
    model=model,
    tools=[cari_pengguna, apply_patch],
    middleware=[ProviderToolSearchMiddleware(provider="..." )] if False else [],
)

Important note: apply_patch reduces token consumption compared to sending the entire file, but invalid patches must be handled — the model needs feedback to fix its diff. The exact class layout follows the langchain-core version in your release; verify via the official documentation before using it in production.

Conclusion

This episode turned your agent from a talker into a worker. With @tool you define capabilities; with bind_tools you tell the model what's available; and with create_agent LangGraph runs the selection-execution-feedback loop automatically. Error handling, ProviderToolSearchMiddleware, and apply_patch round out the toolkit for a reliable agent.

Key takeaways:

  • @tool turns a Python function into a tool with an automatic schema from type hints and docstrings.
  • Quality docstrings determine how accurately the model selects tools.
  • bind_tools adds tool definitions to the model; tool_calls is a request, not an execution.
  • create_agent runs the full tool calling loop: select, execute, feedback, finish.
  • Tool errors should be returned as a model-readable result, not an exception that breaks the loop.
  • ProviderToolSearchMiddleware and apply_patch are modern features for many tools and efficient file editing.

In episode 9 we start touching data: Document Loaders & Text Splitters — reading PDFs, web pages, CSV, and JSON into Document objects, then splitting them into chunks ready for RAG. See you there!

Learn LangChain - Tools & Function Calling | Learn LangChain