1. ai
  2. /building
  3. /tool-calling-patterns

Tool Calling Patterns

Tool calling lets models invoke functions you define — query APIs, run code, update tickets — instead of guessing answers. The hard part is schema design and safe execution, not the API call.

Last reviewed: June 2026

Tool APIs differ between OpenAI, Anthropic, and the Vercel AI SDK. Pin versions and verify provider docs.

The problem

Without tools, models hallucinate database rows and API responses. With tools, you execute code — the model only proposes structured calls. Production quality depends on:

  • Small, well-named tools with strict schemas
  • Validation before side effects
  • Clear error messages fed back to the model
  • Limits on call count and parallelism

Basics: LLM APIs and Tool Calling. MCP variant: Building MCP Servers.

Tool schema design

import { z } from "zod";

export const lookupOrderTool = {
  description: "Look up order status by order ID. Read-only.",
  parameters: z.object({
    orderId: z.string().uuid().describe("UUID of the order"),
  }),
  execute: async ({ orderId }: { orderId: string }) => {
    const order = await db.orders.findById(orderId);
    if (!order) return { error: "Order not found" };
    return { status: order.status, updatedAt: order.updatedAt };
  },
};
RuleWhy
One action per toolModel picks correctly
.describe() on every fieldReduces wrong arg types
Return errors as JSONModel can retry or explain
Read-only defaultWrites need separate tools + gates

AI SDK tool loop

import { openai } from "@ai-sdk/openai";
import { streamText, tool } from "ai";
import { z } from "zod";

const result = streamText({
  model: openai("gpt-4o"),
  tools: {
    lookup_order: tool({
      description: "Look up order status by UUID",
      parameters: z.object({ orderId: z.string().uuid() }),
      execute: async ({ orderId }) => {
        const order = await db.orders.findById(orderId);
        return order ?? { error: "Not found" };
      },
    }),
  },
  maxSteps: 5,
  messages,
});

maxSteps caps agent loops — prevents runaway tool chains.

Parallel vs sequential tools

PatternUse when
ParallelIndependent lookups (user + orders + prefs)
SequentialOutput of A required for input of B
Single batchProvider supports parallel tool calls in one turn

Return partial results on failure — do not fail entire batch silently.

Human confirmation pattern

For destructive tools:

execute: async ({ orderId, confirm }) => {
  if (!confirm) {
    return {
      status: "needs_confirmation",
      message: `Call again with confirm:true to cancel order ${orderId}`,
    };
  }
  await db.orders.cancel(orderId);
  return { status: "cancelled" };
},

Require explicit confirm: true in schema — model must ask user first.

Error handling loop

Model calls tool → execute throws or returns error object
→ append tool result to messages
→ model explains or retries with corrected args
→ maxSteps prevents infinite retry

Log tool name, latency, and error class — not full PII payloads.

Tool calling vs MCP

In-app tools (API route)MCP (IDE agent)
HostYour Next.js serverCursor, Claude Code
UseUser-facing product featuresDeveloper workflows
AuthUser sessionDeveloper env vars

Same design principles apply to both.

Production concerns

ConcernWhat to do
CostLimit maxSteps; cheap model for routing
LatencyParallel reads; cache hot lookups
Failure modesTimeouts on execute; circuit breakers on external APIs
SecurityAllowlist tools per route; never eval model output
InjectionUser content in user role; tools never run raw SQL from model strings

See Security and Prompt Injection and Structured Outputs.