Track
Schema · Validate · Retry · Normalize
An agent is only as useful as the tools it can call. Without tools, an LLM is just a text generator — it can't look up the weather, run Python, query a database, or send an email. Tools are the bridge between the LLM's reasoning and the real world.
But giving an LLM access to arbitrary functions is dangerous. You need a contract: a schema that tells the LLM what parameters a tool expects, validation that catches bad calls before they execute, and retry logic that handles failures gracefully.
Every tool has a shape: a name, a description, a list of parameters with types, and rules about which parameters are required. This shape is what the LLM sees when deciding which tool to call and how to fill in the arguments.
The LLM reads this schema and produces structured output that matches it. Your job is to parse that output and dispatch it safely.
Tools fail. Networks drop, APIs rate-limit, timeouts happen. A well-built tool wrapper retries with exponential backoff rather than crashing the agent on the first error.
On failure, wait base * 2^attempt seconds (with jitter), then retry. If all attempts fail, re-raise the last exception. The jitter prevents multiple agents from retrying in lockstep — 'thundering herd' avoidance.
A tool schema starts with the name — use verb_noun: get_weather, send_email, search_database. A name like 'process' or 'do_stuff' is too vague. A good description tells the LLM when to invoke this tool and what it will do.
Parameters need names, typed constraints, and descriptions that remove ambiguity. Mark parameters without defaults as required. For optional parameters, include the default behavior in the description. The model should never have to guess.
Before executing any tool call, validate arguments against the schema. Catch mismatched types, missing required fields, and unknown parameters early. Return a structured error so the agent can correct itself.
Long-running tool calls need a timeout. If a tool does not respond within a reasonable threshold, kill the call and return a timeout observation. Normalize all errors into the same structured format.
Not every failure deserves a retry. Transient errors (network timeouts, 429 rate limits) are good candidates. Idempotent tools are safe to retry. But mutating operations like inserting a database record are dangerous to retry without deduplication.
When retrying, use exponential backoff with jitter. Set max attempts (3-5) and a backoff cap. After the cap, fail fast with a clear error observation telling the agent what went wrong.
Concrete Example
import inspect
from typing import get_type_hints
TYPE_MAP = {str: "string", int: "integer",
float: "number", bool: "boolean"}
def to_tool_schema(fn):
sig = inspect.signature(fn)
hints = get_type_hints(fn)
properties = {}
required = []
for name, param in sig.parameters.items():
typ = hints.get(name, str)
properties[name] = {"type": TYPE_MAP.get(typ, "string")}
if param.default is inspect.Parameter.empty:
required.append(name)
return {
"name": fn.__name__,
"description": fn.__doc__ or "",
"parameters": {
"type": "object",
"properties": properties,
"required": required,
},
}This takes any annotated Python function and produces an OpenAI-compatible JSON Schema. Parameters without defaults become required; Python types are mapped to JSON Schema types.
Auto-derive tool contracts from function signatures.
Clear verb_noun names, precise descriptions, typed parameters.
Catch mismatched types and missing required fields before they reach the tool.
Timeouts, unknown tool blocking, and structured error normalization.
Exponential backoff with jitter for transient errors; fail fast for mutations.
Explore these deeper articles to master specific topics in Tool Creation.
Interview revision
8 problems. Sign in to start solving.
Sign in to open a workspace and solve these problems.