Tools, and the loop they drive
A tool is a Python function with type hints and a docstring. That sentence is genuinely the whole API, and everything else in this lesson is about what those two decorations are actually for.
The decorator, and the two things that carry weight
from langchain.tools import tool
@tool
def search_database(query: str, limit: int = 10) -> str:
"""Search the customer database for records matching the query.
Args:
query: Search terms to look for
limit: Maximum number of results to return
"""
return f"Found {limit} results for '{query}'"Type hints are mandatory, because they define the input schema the model has to fill in. The docstring becomes the description, which is what the model reads when choosing. Tool names should be snake_case for provider compatibility.
You can override both explicitly, which is worth knowing because it is how you decouple a tool’s public contract from your function naming.
@tool("web_search", description="Performs web searches for information.")
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"For richer inputs, hand it a Pydantic model through args_schema, which buys you per-field descriptions and literal constraints.
Two reserved names
config is reserved for RunnableConfig and runtime is reserved for ToolRuntime. You cannot use either as an ordinary tool argument, and the collision is silent enough to be worth memorising.
ToolRuntime is how a tool reaches beyond its own arguments:
from langchain.tools import tool, ToolRuntime
@tool
def get_user_message(runtime: ToolRuntime) -> str:
"""Get the most recent message from the user."""
return runtime.state["messages"][-1].contentIt carries state (short-term memory), context (immutable per-run config), store (persistent long-term memory), stream_writer, execution_info, server_info, and tool_call_id.
Four things a tool can return
- A string. The normal case.
- An object or dict. Structured output the model reads back.
- A
Command. This is how a tool writes to state rather than merely reporting. - Anything, with
return_direct=True, which short-circuits the loop: the output goes straight to the caller with no further model turn.
from langgraph.types import Command
from langchain.messages import ToolMessage
@tool
def set_user_name(new_name: str, runtime: ToolRuntime) -> Command:
"""Set the user's name in conversation state."""
return Command(
update={
"user_name": new_name,
"messages": [
ToolMessage(content=f"User name set to {new_name}.",
tool_call_id=runtime.tool_call_id),
],
}
)Note the ToolMessage inside the update. Every tool call needs a matching result message or the conversation is malformed, and when you take over the return value you take over that obligation too.
Errors are a middleware concern
There is no handle_tool_errors flag on the tool. Failures are caught with a wrap_tool_call middleware that turns the exception into a ToolMessage the model can read and recover from. That is module 3, and it is the first hint that middleware is not an advanced topic bolted on the end: it is where several things you would expect to be tool options actually live.
Try it yourself
Spot the bug
One of these tools will not behave as written. The defect is not a typo.
@tool
def fetch_invoice(invoice_id: str, runtime: str) -> str:
"""Fetch an invoice by id from the billing system."""
return billing.get(invoice_id, env=runtime)
@tool
def fetch_order(order_id: str, region: str) -> str:
"""Fetch an order by id from the orders service."""
return orders.get(order_id, region=region)Show answer
Correct answer: B — fetch_invoice, because runtime is a reserved parameter name
runtime is reserved for ToolRuntime injection, exactly as config is reserved for RunnableConfig, so using it as an ordinary argument collides with the framework. The tempting answer is the last one, because the code reads perfectly well and nothing about it looks wrong; the collision is invisible unless you already know the two reserved names.
What the docstring is doing
This is the piece most engineers underrate, because in every other codebase they have worked in a docstring is documentation for humans.
What role does a tool's docstring play at runtime, and what happens to tool selection if it is vague?
Reveal answer
The docstring becomes the tool's description, which is what the model reads when deciding whether to call it. It is not documentation, it is the selection contract. A vague docstring produces a model that calls the wrong tool or fails to call the right one, and the symptom presents as a reasoning failure rather than as a documentation problem, which is why it is so often misdiagnosed. Type hints matter for the same reason: they define the input schema the model has to fill in.
What return_direct changes
A tool is declared with @tool(return_direct=True). What happens after it runs?
Show answer
Correct answer: A — The agent returns the tool's output to the caller with no further model call
return_direct short-circuits the loop; the tool's output goes straight back to the caller with no further model turn. The second option is exactly what happens without return_direct, which is what makes it the natural wrong answer: it describes the default behaviour that this flag exists to switch off.
Making a tool write to state
A tool needs to update a field in the agent's state, not just return a string. What does it return?
Show answer
Correct answer: C — A Command with an update mapping, including a ToolMessage for the tool call id
A tool returns Command(update={...}) to write state, and the update must include a ToolMessage carrying the runtime's tool_call_id, because every tool call needs a matching result message or the conversation is malformed. The first option is tempting because returning a dict is legal, but a returned dict is structured tool output, not a state update.
Write the same tool twice
In a scratch file, write one tool with a lazy one-line docstring and the same tool with a docstring that names its arguments, its failure mode, and when not to call it.
The second version reads like a specification a stranger could act on, and you can point at the sentence that would stop the model calling it in the wrong situation.