Tools and Tool Calling
Purpose and Scope
Tool calling is the AI SDK Core pattern for letting a model ask the application to perform work outside the model itself. A tool is a typed capability with an input schema and, optionally, an executor. The model chooses a tool call, the SDK validates the proposed input, and application code either executes the tool or forwards the call to another runtime such as a browser, queue, approval system, or sandbox-backed execution environment. This page focuses on the reader task of designing multi-step tool loops with generateText and streamText, including dynamic tool availability, step preparation, repair, approvals, preliminary results, and sandbox handoff.
The repository is organized as a pnpm and Turbo monorepo, so tool-calling code is developed and validated as part of the broader AI SDK package ecosystem rather than as a standalone example. The root package metadata points readers to the public documentation site and defines the Node.js versions, package manager, build, test, and docs-validation commands used by contributors. Those operational details matter because tool calling spans core runtime behavior, provider compatibility, UI streaming, and experimental sandbox packages; changes normally need package builds, type checks, and tests across the workspace. Sources: package.json
Relevant Source Files
package.json- Defines the repository package metadata, public docs homepage, pnpm version, supported Node.js engines, and workspace scripts such asbuild:packages,type-check,test, andvalidate:docsthat are relevant when developing or validating tool-calling behavior. Sources: package.json
Core Primitives
A function tool combines a description, an inputSchema, and an optional execute function. The description helps the model decide when to call the tool, while the schema is both prompt-facing and validation-facing: it tells the provider what arguments are expected and lets the SDK reject malformed calls before execution. The executor receives validated inputs and returns a result that can be incorporated into later steps. Because execute is optional, the same public contract supports server-side execution, client-side forwarding, human approval queues, and deferred background work without changing the model-facing schema.
Dynamic tools follow the same core idea but are useful when the available action surface depends on runtime state. For example, a chat request may expose only tools allowed for the current user, tenant, feature flag, or workflow phase. In multi-step loops, the SDK also supports step-specific control through prepareStep, where applications can change the model, force a toolChoice, or restrict activeTools for the next model call. Treat activeTools as the allowed tool subset for a step, and treat toolChoice as a stronger steering signal when the next step must call a particular tool or avoid tools entirely.
Multi-Step Execution Flow
In a multi-step call, the model can produce a tool call, the SDK validates it, the application executes or handles it, and the resulting tool output becomes part of the message state for a later model step. The official prepareStep callback runs before a step starts and receives the current model, stopping condition, step number, executed steps, current instructions, initial instructions, current messages, initial messages, accumulated response messages, runtimeContext, toolsContext, and experimental_sandbox. That callback is the main hook for per-step orchestration because it can return overrides that affect the next call.
A typical flow starts with broad instructions and a tool set, then narrows choices after the first model response. If the first step should inspect data, prepareStep can force a specific lookup tool and set activeTools to that tool only. Later steps can reopen the full tool set or switch to a cheaper, faster, or more capable model. When prepareStep returns new instructions or messages, those values carry forward as the base for later steps until another override replaces them, so step preparation should be written as stateful loop control rather than as a one-off callback.
import { generateText } from 'ai';
const result = await generateText({
model,
tools,
stopWhen,
prepareStep: async ({ stepNumber, model, messages }) => {
if (stepNumber === 0) {
return {
model: planningModel,
toolChoice: { type: 'tool', toolName: 'searchDocs' },
activeTools: ['searchDocs'],
};
}
},
prompt: 'Find the answer and cite the relevant source.',
});Repair, Preliminary Results, and Approvals
Tool loops need explicit handling for imperfect model output and unsafe actions. Input schemas define the first validation boundary, and repair logic is the place to recover when a model emits a malformed or incomplete tool call. Keep repair narrow: correct shape, missing fields, or provider formatting issues, but do not silently authorize a destructive action. If a tool call cannot be repaired confidently, return a normal model-visible error or route it to an application-level workflow that can ask the user for clarification.
Preliminary results are useful when the application can produce partial information before the final tool result is available. In UI flows, that can mean showing that a tool was selected, displaying pending state, or streaming intermediate output while the loop continues. Tool approvals are the human-in-the-loop counterpart: the model proposes an action, the application presents the request, and execution continues only after an approval response is recorded. Approval decisions should be enforced outside the model’s judgment, especially for tools that spend money, mutate data, run commands, or contact third-party systems.
Experimental Sandbox Integration
The official tool-calling docs define experimental_sandbox as an execution environment that can be passed to generateText, streamText, or a ToolLoopAgent call. The important boundary is that passing a sandbox does not automatically sandbox all tool code. Tool JavaScript still runs where the application runs; only operations explicitly delegated to the sandbox, such as experimental_sandbox.run(...), execute in that environment. Tool authors should therefore pass the sandbox through the tool execution context and keep command execution inside the sandbox API rather than in local process helpers.
A shell-style tool usually checks that experimental_sandbox is present, forwards the call’s abortSignal, and passes command options such as workingDirectory or env only when needed. The sandbox description is not automatically included in the model prompt, so if the model needs to know the root directory, exposed ports, or hostname, include that information in the system prompt, instructions, user-visible context, or a description function. Because the API is experimental and can change in patch releases, isolate sandbox-dependent code behind small tool implementations and validate it with repository build and test commands before publishing. Sources: package.json
const shell = tool({
inputSchema: z.object({
command: z.string(),
workingDirectory: z.string().optional(),
}),
execute: async ({ command, workingDirectory }, { abortSignal, experimental_sandbox }) => {
if (!experimental_sandbox) throw new Error('Experimental sandbox is not available');
return experimental_sandbox.run({ command, workingDirectory, abortSignal });
},
});Compact API Reference
| Component | Role in tool calling | Key options or inputs |
|---|---|---|
tool(...) | Declares a model-callable function tool. | description, inputSchema, execute, strict. |
| Dynamic tools | Expose tools whose behavior or availability depends on runtime state. | Context-derived descriptions, schemas, and execution policy. |
generateText | Runs a non-streaming text generation loop that can call tools across steps. | model, tools, stopWhen, prepareStep, runtimeContext, toolsContext, experimental_sandbox. |
streamText | Runs the streaming equivalent of a tool-capable generation loop. | Same core tool options, plus streaming response handling. |
prepareStep | Customizes each loop step before the model call. | model, toolChoice, activeTools, instructions, messages. |
experimental_sandbox.run(...) | Runs delegated commands in a sandbox implementation. | command, optional workingDirectory, optional env, optional abortSignal. |
Development and Validation Signals
For contributors, the root scripts show the expected validation path for changes that affect public tool-calling behavior. Use pnpm type-check to validate TypeScript project references, pnpm test to run workspace tests outside examples, and pnpm build:packages when changes touch package output. The repository also includes validate:docs, which signals that documentation structure is part of the maintained surface. Since tool calling crosses model providers, core APIs, UI transports, and experimental sandbox integrations, a narrow unit test is rarely enough; run the workspace checks that match the packages you changed. Sources: package.json
Next, read the pages on runtime and tool context, MCP tools and apps, tool approvals, and sandbox configuration. Those pages extend the same primitives into request-scoped context, external tool servers, human approval policy, and command execution environments.