All articles
August 10, 2025 6 min read

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.

Written forEngineering
Tool CallingAgentsSafety

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.

Validated arguments, not raw strings
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.
Building something with LLMs?
I help teams ship GenAI that’s reliable and cost-efficient.
Let’s talk