Tools and agents

Expose Python functions to a model, let it choose between them, and keep the loop bounded and safe.

Turning functions into tools

A tool is a normal function plus a description the model can read. The signature becomes the argument schema and the docstring becomes the description, so both are prompt surface - write them for a reader who cannot see your code.

from langchain_core.tools import tool

@tool
def stock_level(sku: str) -> int:
    """Return units on hand for a SKU. Call this before promising any delivery date."""
    return warehouse.on_hand(sku)

@tool
def place_order(sku: str, quantity: int) -> dict:
    """Create a draft order. Does not charge the customer; the draft needs approval."""
    return orders.create_draft(sku, quantity)

print(stock_level.name, stock_level.args)      # stock_level {'sku': {'title': 'Sku', 'type': 'string'}}
  • Describe when to call the tool, not only what it does - that sentence drives selection.
  • Constrain arguments with type hints and enums so malformed calls are hard to express.
  • Return small structured results with stable keys; the model reads them like a document.
  • Return errors as data such as {"error": "unknown sku"} so the model can correct itself instead of crashing the run.

The agent loop

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a support agent. Use tools for every fact; never guess stock or prices."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

tools = [stock_level, place_order]
agent = create_tool_calling_agent(llm, tools, prompt)

executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=6,             # hard stop: agents do not always converge
    return_intermediate_steps=True,
    handle_parsing_errors=True,   # malformed tool call becomes feedback, not a crash
)

result = executor.invoke({"input": "Can we ship 3 units of SKU-9 by Friday?"})
for action, observation in result["intermediate_steps"]:
    print(action.tool, action.tool_input, "->", observation)
Class of toolExamplePolicy
ReadLook up stock, search docs, list ordersRun freely, but rate-limit and validate
Reversible writeCreate a draft, add a labelAllow; log it and make undo possible
Irreversible writeCharge a card, send an email, delete a recordRequire explicit confirmation or human approval
PrivilegedShell, arbitrary SQL, filesystem writeAvoid; if unavoidable, sandbox and scope it tightly
⚠️
Never pass model-generated text into a shell command, a SQL string, a filesystem path or a URL. Retrieved documents, web pages and tool output can all contain instructions aimed at the model, and one successful injection turns a read tool into a write primitive. Decide authorisation in your own code, not inside the prompt.

FAQ

Agent or chain?
Use a chain when the steps are known in advance; it is cheaper, faster and testable. Use an agent only when the number and order of steps genuinely depend on the intermediate results.
Why does my agent loop forever?
Usually the goal has no acceptance test, or a tool keeps returning an unhelpful error so the model retries the same call. Cap iterations, detect repeated identical calls, and make error messages say what to do differently.

Tools and function calling Prompt templates and chains

Last refreshed 2026-09-18.