Tool calling done right: typed functions, allowlists, and guardrails
Letting an LLM take actions is where the risk lives. Typed schemas, per-state tool allowlists, timeouts, and iteration caps are what keep a capable agent from becoming a runaway one.
Tool calling is what turns a chatbot into something that can act — look up an order, issue a refund, run a query. That power is also the entire risk surface. Doing it right is less about the happy path and more about the guardrails that keep a capable agent from becoming a runaway one.
Type every tool with a schema
Describe each tool with a typed schema (Pydantic is the common choice) so the model's arguments are validated before anything runs. Never take a raw string from the model and execute it — parse it into a validated object first, and reject what doesn't fit.
from pydantic import BaseModel, Field
class RefundArgs(BaseModel):
order_id: str = Field(pattern=r'^ORD-\d{6}$')
amount: float = Field(gt=0, le=500) # bounded by policy
def refund(args: RefundArgs): # args are already validated
...
# per-state allowlist: 'refund' isn't even callable while greeting
TOOLS = {'support': [lookup_order, refund], 'greeting': []}Allowlist tools per state
Not every tool should be available at every moment. In a state-machine agent (see the WhatsApp case study), scope the callable tools to the current state — the refund tool simply doesn't exist during onboarding. It's least privilege applied to actions: the model can't misuse a tool it was never handed.
Bound the loop
- Iteration cap — a hard limit on tool calls per turn, so no infinite loops.
- Timeouts — every tool call has a deadline; a hung dependency can't hang the agent.
- Read-only by default — write and destructive tools are opt-in, per action, ideally with human approval.
- Validate outputs too — schema-check tool results before the model reasons over them (see the structured-outputs post).
An agent is only as safe as the tools it can reach. Type them, scope them to the moment, bound the loop — and a fooled model still can't do much.