n4nAI

Five design patterns for AI agent tool use

Practical AI agent tool use patterns for production: schema-first definitions, deterministic dispatch, idempotent caching, HITL gates, and composable chains.

n4n Team3 min read762 words

Audio narration

Coming soon — every post will get a voice note here.

Reliable agents treat tool calls as a strict contract between model and runtime. The AI agent tool use patterns that hold up under load share a few traits: explicit schemas, predictable dispatch, and deliberate failure handling. Below are five patterns we apply when shipping agents that mutate state or call external APIs.

1. Explicit schema-first tool definition

Define tools as JSON Schema before writing any execution code. The model sees the schema; your runtime validates against it. This catches malformed arguments early and decouples the LLM’s reasoning from your function signatures.

{
  "name": "create_refund",
  "description": "Issue a refund to a customer order",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": {"type": "string"},
      "amount_cents": {"type": "integer", "minimum": 1}
    },
    "required": ["order_id", "amount_cents"]
  }
}

Generate the handler from the same schema using pydantic or zod. If validation fails, return a structured error to the model rather than raising an exception that breaks the loop.

from pydantic import BaseModel, ValidationError

class CreateRefundArgs(BaseModel):
    order_id: str
    amount_cents: int

try:
    args = CreateRefundArgs(**raw_args)
except ValidationError as e:
    return {"error": "invalid_args", "detail": e.errors()}

In practice, we generate the OpenAI tools array directly from the pydantic model. This eliminates drift between what the model is told and what the code accepts. If you support multiple model providers, the same schema translates to Anthropic’s format with a thin adapter.

Schema-first forces you to think about boundaries. It also makes AI agent tool use patterns testable: you can fuzz the schema without invoking the model.

2. Deterministic dispatch with fallback

Never let the model pick an arbitrary code path. Maintain a registry mapping tool names to functions. After the model emits a tool call, look it up; if missing, reject. This keeps execution inside your trust boundary.

TOOLS = {"create_refund": create_refund, "lookup_user": lookup_user}

def dispatch(name, args):
    handler = TOOLS.get(name)
    if not handler:
        return {"error": "unknown_tool", "name": name}
    return handler(**args)

Avoid heuristic matching like fuzzy name similarity. A typo in the model’s output should fail loud, not silently call a similar tool. Offer the model a none tool to explicitly decline when no action fits.

When the primary model stalls or rate-limits, fall back to a secondary model with the same schema. If you route through an OpenAI-compatible gateway such as n4n.ai, automatic fallback when a provider is rate-limited saves you from writing your own retry loop, but you still need deterministic mapping from tool name to handler.

Keep the fallback model’s tool schema identical. Differing required fields cause silent argument drops. Log which model served the call for later audit.

3. Idempotent tool execution with caching

Tools that change state must be safe to retry. Network hiccups will cause the agent to resend the same call. Prefix your tool with an idempotency key derived from its arguments and a caller nonce.

import hashlib, redis

r = redis.Redis()

def create_refund(order_id, amount_cents, nonce):
    key = hashlib.sha256(f"{order_id}:{amount_cents}:{nonce}".encode()).hexdigest()
    if r.get(f"refund:{key}"):
        return {"status": "duplicate", "id": r.get(f"refund:{key}").decode()}
    # ... execute ...
    r.setex(f"refund:{key}", 86400, new_id)
    return {"status": "ok", "id": new_id}

Cache read-only tools aggressively. A lookup_user that hits your database should cache for seconds to minutes depending on consistency needs. This reduces cost and latency across multi-step AI agent tool use patterns.

For read caches, use a TTL aligned with your data’s freshness SLA. A user profile might cache for 30 seconds; a stock quote for 1 second. Measure cache hit ratio in your telemetry to tune.

Idempotency also simplifies human approval: if a user rejects a pending action, the same key will not execute later.

4. Human-in-the-loop confirmation gate

Destructive or external-send tools need a pause. Return a pending state with a token; the agent continues only after an out-of-band approval. This prevents the model from emailing your CEO unprompted.

def send_email_draft(to, body):
    token = generate_token()
    pending_store[token] = {"to": to, "body": body}
    return {"status": "pending_approval", "token": token}

def approve(token):
    if token not in pending_store:
        return {"error": "not_found"}
    send(pending_store.pop(token))
    return {"status": "sent"}

The agent’s loop should treat pending_approval as a terminal state for that step. Surface the token to your UI; do not let the model self-approve by guessing the token.

Implement timeout on pending tokens. If no approval within X minutes, expire and notify. Otherwise you accumulate dead state and confuse the agent on resume.

This pattern is non-negotiable for any agent with write access to financial or communication systems. It also gives you a natural audit log.

5. Composable tool chains with reducers

Single tools rarely solve a task. Build chains where the output of one tool feeds a reducer that shapes the next call. The reducer is plain code, not a model call, keeping the chain deterministic.

def search_then_summarize(query):
    results = search_web(query)["items"]
    condensed = [r["url"] for r in results[:3]]
    summary = summarize_urls(condensed)
    return summary

Expose the chain as a single composite tool to the model. The model sees one schema; your runtime handles the internal steps. This reduces the number of decision points the LLM must make and shrinks the context window.

Reducers can also enforce rate limits across sub-tools. If search_web returns 429, the reducer can backoff before calling summarize_urls. Good AI agent tool use patterns separate model reasoning from orchestration logic. The reducer is where you enforce ordering, retries, and error mapping.

Synthesis

Pattern Core benefit Key risk mitigated
Schema-first definition Contract clarity Malformed arguments
Deterministic dispatch Trust boundary Arbitrary code execution
Idempotent caching Safe retries Double charges, extra load
HITL confirmation Human control Unauthorized actions
Composable chains Deterministic orchestration Context bloat, flaky steps

Pick the patterns that match your blast radius. Read-only analytics agents may skip HITL; payment agents cannot skip idempotency.

Tagsai-agentstool-usedesign-patterns

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All ai agent tool use design patterns posts →