Tool definition for knowledge base retrieval
Example tool definition for querying knowledge base:
getInformation: tool({
description: `get information from your knowledge base to answer questions.`,
inputSchema: z.object({
question: z.string().describe('the users question'),
}),
execute: async ({ question }) => findRelevantContent(question),
})
Define a tool with Zod schema and execute function
Example tool definition for adding resources to knowledge base:
import { tool } from 'ai';
import { z } from 'zod';
import { createResource } from '@/lib/actions/resources';
tools: {
addResource: tool({
description: `add a resource to your knowledge base.
If the user provides a random piece of knowledge unprompted, use this tool without asking for confirmation.`,
inputSchema: z.object({
content: z
.string()
.describe('the content or resource to add to the knowledge base'),
}),
execute: async ({ content }) => createResource({ content }),
}),
}
Tools for agent function calling
A tool is a function that can be called by the model to perform a specific task. Tools are defined in the streamText configuration with three elements: description (influences when the tool is picked), inputSchema (Zod schema defining required inputs), and execute (async function called with tool arguments). When the model calls a tool, it generates a tool-call message that is appended to the messages array, and the AI SDK runs the execute function with the provided parameters.
isStepCount for multi-step tool calls
The isStepCount function from the AI SDK creates a stopping condition based on the number of generation steps. Usage: stopWhen: isStepCount(5) stops after 5 steps, allowing intermediate tool calls to complete before stopping the generation.
Multi-step tool calls with stopWhen
The stopWhen parameter in streamText allows stopping conditions when the model generates a tool call. If stopping conditions are not met, the AI SDK automatically sends tool call results back to the model for further generation. Example usage: stopWhen: isStepCount(5) stops after 5 steps, allowing the model to call a tool and then summarize the result.
Complete chat API route with tools
Example Next.js API route with multiple tools for a RAG agent:
import { createResource } from '@/lib/actions/resources';
import {
convertToModelMessages,
createUIMessageStreamResponse,
streamText,
tool,
toUIMessageStream,
UIMessage,
isStepCount,
} from 'ai';
import { z } from 'zod';
import { findRelevantContent } from '@/lib/ai/embedding';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: 'openai/gpt-4o',
messages: await convertToModelMessages(messages),
stopWhen: isStepCount(5),
system: `You are a helpful assistant. Check your knowledge base before answering any questions.
Only respond to questions using information from tool calls.
if no relevant information is found in the tool calls, respond, "Sorry, I don't know."`,
tools: {
addResource: tool({
description: `add a resource to your knowledge base.
If the user provides a random piece of knowledge unprompted, use this tool without asking for confirmation.`,
inputSchema: z.object({
content: z
.string()
.describe('the content or resource to add to the knowledge base'),
}),
execute: async ({ content }) => createResource({ content }),
}),
getInformation: tool({
description: `get information from your knowledge base to answer questions.`,
inputSchema: z.object({
question: z.string().describe('the users question'),
}),
execute: async ({ question }) => findRelevantContent(question),
}),
},
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}
Gemini 3 tool calling with multi-step workflows example
The following example demonstrates tool calling with Gemini 3 Pro using the AI SDK. It defines a weather tool that returns temperature data, and uses stopWhen with isStepCount(5) to enable multi-step tool calling:
```ts
import { z } from 'zod';
import { generateText, tool, isStepCount } from 'ai';
import { google } from '@ai-sdk/google';
const result = await generateText({
model: google('gemini-3-pro-preview'),
prompt: 'What is the weather in San Francisco?',
tools: {
weather: tool({
description: 'Get the weather in a location',
inputSchema: z.object({
location: z.string().describe('The location to get the weather for'),
}),
execute: async ({ location }) => ({
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
}),
}),
},
stopWhen: isStepCount(5), // enables multi-step calling
});
console.log(result.text);
console.log(result.steps);
```
Gemini 3 with Google Search grounding example
The following example demonstrates using Google Search grounding with Gemini 3 Pro to access the latest information:
```ts
import { google } from '@ai-sdk/google';
import { GoogleProviderMetadata } from '@ai-sdk/google';
import { generateText } from 'ai';
const { text, sources, providerMetadata } = await generateText({
model: google('gemini-3-pro-preview'),
tools: {
google_search: google.tools.googleSearch({}),
},
prompt:
'List the top 5 San Francisco news from the past week.' +
'You must include the date of each article.',
});
// access the grounding metadata. Casting to the provider metadata type
// is optional but provides autocomplete and type safety.
const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined;
const groundingMetadata = metadata?.groundingMetadata;
const safetyRatings = metadata?.safetyRatings;
console.log({ text, sources, groundingMetadata, safetyRatings });
```
Connect to MCP servers with openai.tools.mcp
Use openai.tools.mcp({ serverLabel, serverUrl, serverDescription }) to connect to Model Context Protocol servers, allowing models to call remote tools. Requires serverLabel (string), serverUrl (string), and serverDescription (string).
Define and call tools with Responses API
Tools are defined in the tools object with a description, inputSchema (Zod schema), and async execute function. Pass tools to generateText to enable tool calling with the Responses API.
Configure webSearchPreview with metadata
webSearchPreview accepts optional configuration: searchContextSize (string like 'high') and userLocation object with type, city, and region properties to improve search result quality.
Use webSearchPreview tool for internet grounding
Call openai.tools.webSearchPreview() as a tool in the tools object to enable the model to access the internet and find relevant information. The result includes a sources property with reference information.
Tool calling best practices
Use tool preambles to provide clear upfront plans. Define safe vs. unsafe actions for different tools. Create structured updates about tool call progress.
Tool calling with GPT-5
GPT-5 supports tool calling out of the box. Define tools using the tool() function with a description, inputSchema (Zod schema), and execute function. The model will call tools as needed when processing prompts.
Web search with GPT-5
GPT-5 can access real-time information through an integrated web search tool. Include tools: { web_search: openai.tools.webSearch({ searchContextSize: 'high' }) } in the generateText call, and access URL sources via result.sources.
Using tools with generateText and Llama 3.1
Pass a tools object to generateText with Llama 3.1. Each tool is defined with tool() function from 'ai' package, with description, inputSchema (Zod object), and execute async function. The model can then choose to call these tools when appropriate. Example: tools: { getWeather: tool({ description: '...', inputSchema: z.object({...}), execute: async ({...}) => {...} }) }
Defining a tool for Llama 3.1
Tools are defined with three properties: description (string explaining what the tool does), inputSchema (Zod schema defining required parameters), and execute (async function that performs the tool's action). Example tool for weather: getWeather with description 'Get the weather in a location', inputSchema with location string parameter, and execute function returning location and temperature.
Using tools with o1 model
This example shows how to use a tool with the AI SDK and o1:
```ts
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
const { text } = await generateText({
model: openai('o1'),
prompt: 'What is the weather like today?',
tools: {
getWeather: tool({
description: 'Get the weather in a location',
inputSchema: z.object({
location: z.string().describe('The location to get the weather for'),
}),
execute: async ({ location }) => ({
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
}),
}),
},
});
```
Tools are compatible with o1.
Tool calling with o3-mini
This example demonstrates tool calling with o3-mini using the tool function:
```ts
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
const { text } = await generateText({
model: openai('o3-mini'),
prompt: 'What is the weather like today in San Francisco?',
tools: {
getWeather: tool({
description: 'Get the weather in a location',
inputSchema: z.object({
location: z.string().describe('The location to get the weather for'),
}),
execute: async ({ location }) => ({
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
}),
}),
},
});
```
Agent loop control with isStepCount
Use the stopWhen parameter with isStepCount(n) in streamText to control how many steps an agent can execute. This allows the agent to continue executing for multiple iterations, enabling it to use tools and reason iteratively before stopping. For example, stopWhen: isStepCount(5) allows up to 5 steps of execution.
DeepSeek V3.2 tool definition with Zod schema
When defining tools for DeepSeek V3.2 agents, use the tool function with a description, inputSchema defined with Zod, and an execute function. The inputSchema should be a Zod object with properly described fields. The execute function receives the validated inputs and returns the tool's result.
DeepSeek V3.2 agent with tools and loop control
```tsx
import { deepSeek } from '@ai-sdk/deepseek';
import {
convertToModelMessages,
createUIMessageStreamResponse,
isStepCount,
streamText,
tool,
toUIMessageStream,
UIMessage,
} from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: deepSeek('deepseek-reasoner'),
messages: await convertToModelMessages(messages),
tools: {
weather: tool({
description: 'Get the weather in a location',
inputSchema: z.object({
location: z.string().describe('The location to get the weather for'),
}),
execute: async ({ location }) => ({
location,
temperature: 72,
unit: 'fahrenheit',
}),
}),
},
stopWhen: isStepCount(5),
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream, sendReasoning: true }),
});
}
```
This example demonstrates how to build an agentic system with DeepSeek V3.2 that includes tool definitions and loop control. The agent can execute up to 5 steps using the stopWhen parameter with isStepCount(5), allowing it to iteratively use tools and reason.
toolChoice 'required' forces model tool calls
Setting toolChoice to 'required' in streamText forces the model to call a tool on that step. This is useful for deterministic extraction steps that must produce structured output via tool calls.
Sending messages with useChat sendMessage
The sendMessage function from useChat accepts an object with a text property: sendMessage({ text: input }). This sends the user input to the configured API endpoint and triggers message streaming and automatic tool call handling.
Client-side tool call implementation example
A complete client example using useChat for tool calling: import useChat from @ai-sdk/react, set up DefaultChatTransport pointing to /api/chat, render input that calls sendMessage({ text: input }) on Enter key, and render messages by iterating message.parts and switching on part.type to render 'text' or tool-specific parts like 'tool-getWeather'.
ToolSet type definition and ChatTools inference
Tools must satisfy the ToolSet type. Use 'satisfies ToolSet' when defining the tools object to ensure type safety. Export ChatTools type using InferUITools<typeof tools> to automatically infer all tool types from the tools definition. This ChatTools type is then used in UIMessage<never, UIDataTypes, ChatTools> to type chat messages.
Rendering tool results in React client
Tool results are rendered by switching on message.parts[].type. Text parts have type 'text' with a text property. Tool results have type names like 'tool-getWeather'. Tool parts can be serialized with JSON.stringify(part, null, 2) to display their full structure and results.
Tool definition with Zod schema in AI SDK
Tools are defined using the tool() function with three required properties: description (string explaining the tool's purpose), inputSchema (Zod schema object defining parameters with .describe() for each field), and execute (async function performing the tool's action). For example, a getWeather tool takes city (string) and unit (enum ['C', 'F']) as inputs and returns a formatted string result.
streamText function parameters for tool use
The streamText function accepts: model (string like 'openai/gpt-4o'), system (system prompt string), messages (converted using convertToModelMessages), stopWhen (optional condition like isStepCount(5)), and tools (the tools object). It returns a result with a stream property that can be processed by toUIMessageStream().
Server-side tool call implementation example
A complete server example at /api/chat/route.ts: define tools with tool() function, infer ChatTools type, set up POST handler receiving ChatMessage[], call streamText with model, system prompt, converted messages, stopWhen condition, and tools object, then return createUIMessageStreamResponse wrapping toUIMessageStream of result.stream.
Multiple sequential tool calls with stopWhen isStepCount
To allow models to call tools in multiple dependent steps during the same generation, use the stopWhen option in streamText set to isStepCount(5). This permits up to 5 consecutive tool-calling steps where each tool's output can inform the next tool call. The model will use this to chain dependent operations together in a single request.
Tool definition with zod schema validation
Tools are defined using the tool() function with three properties: description (string), inputSchema (a zod schema object defining parameters), and execute (async function). The inputSchema uses z.object() to define named parameters with types and optional describe() calls. Tool functions are declared in a ToolSet object and exported as InferUITools for type inference. Example: getLocation tool has empty inputSchema z.object({}), while getWeather has city (string) and unit (enum of 'C' or 'F') parameters.
Optional: Use official @modelcontextprotocol/sdk transports
The AI SDK MCP package provides lightweight built-in transports. Optionally install @modelcontextprotocol/sdk for official transports: StdioClientTransport, SSEClientTransport, StreamableHTTPClientTransport. Create them and pass to createMCPClient({ transport }).
Create MCP client with HTTP transport
Use createMCPClient with transport type 'http' to connect to HTTP MCP servers. Configuration: { transport: { type: 'http', url: 'http://localhost:3000/mcp', headers?: {}, authProvider?: oAuthProvider } }. Headers and authProvider are optional.
MCP client lifecycle: create, retrieve tools, close
The AI SDK provides createMCPClient to connect to MCP servers. After retrieving tools via client.tools(), the client should always be closed via client.close() to release resources. Close clients in onEnd and optionally in onError callbacks when using streamText.
Merge tools from multiple MCP clients
Retrieve tools from multiple MCP clients using client.tools(), which returns a tools object. Merge them using object spread syntax: { ...toolSetOne, ...toolSetTwo, ...toolSetThree }. Note that subsequent tool sets override tools with the same name.
needsApproval tool property for human approval gates
Add `needsApproval: true` to a tool definition to pause tool execution and wait for user approval before running the `execute` function. The SDK sends a tool part with the `approval-requested` state to the client instead of running the tool automatically.
Dynamic approval based on tool input
The `needsApproval` property can be an async function that returns a boolean, allowing approval requirements to be conditional on the tool's input parameters. For example, `needsApproval: async ({ amount }) => amount > 1000` will only request approval for payments over $1000.
Server-side tool approval setup with streamText
Configure tool approval on the server by adding `needsApproval: true` to the tool definition. The tool keeps its `execute` function, but execution pauses until approval. Example:
```ts
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
getWeatherInformation: tool({
description: 'show the weather in a given city to the user',
inputSchema: z.object({ city: z.string() }),
needsApproval: true,
execute: async ({ city }) => {
const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy'];
return weatherOptions[Math.floor(Math.random() * weatherOptions.length)];
},
}),
},
});
```
Human-in-the-loop approval UI implementation example
The following example shows how to render approval buttons and handle tool states in a Next.js component:
```tsx
if (part.type === 'tool-getWeatherInformation') {
switch (part.state) {
case 'approval-requested':
return (
<div key={part.toolCallId}>
Get weather information for {part.input.city}?
<div>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: true,
})
}
>
Approve
</button>
<button
onClick={() =>
addToolApprovalResponse({
id: part.approval.id,
approved: false,
})
}
>
Deny
</button>
</div>
</div>
);
case 'output-available':
return (
<div key={part.toolCallId}>
Weather in {part.input.city}: {part.output}
</div>
);
case 'output-denied':
return (
<div key={part.toolCallId}>
Weather request for {part.input.city} was denied.
</div>
);
}
}
```
addToolApprovalResponse hook method
Use `addToolApprovalResponse` from the `useChat` hook to send the user's approval decision back to the server. Call it with an object containing `id: part.approval.id` (the approval ID from the tool part) and `approved: boolean` (true to approve, false to deny).
Tool approval UI states and transitions
When a tool requires approval, the tool part has three possible states: (1) `approval-requested` - user must approve or deny; (2) `output-available` - tool executed successfully after approval; (3) `output-denied` - user denied the tool execution. Each state includes different data: `approval-requested` has `part.approval.id` for the approval response, `output-available` has `part.output` with the result, and `output-denied` shows the denial.
ToolLoopAgent basic setup with tool and AgentUIMessage
Create a ToolLoopAgent by instantiating it with a model name and tools object. Use InferAgentUIMessage to derive type-safe messages that include typed tool calls and results. Example: new ToolLoopAgent({ model: 'anthropic/claude-haiku-4.5', tools: { greet: tool({ description: 'Greets a person by their name.', inputSchema: z.object({ name: z.string() }), execute: async ({ name }) => `Greeted ${name}` }) } }). Export the inferred AgentUIMessage type for use in frontend components.
Weather information tool output structure
The getWeatherInformation tool returns an object with: city (string), value (number for temperature), unit (string: 'celsius' or 'fahrenheit'), and weeklyForecast (array of objects each containing day and value).
Client-side tools without execute method
Client-side tools can be defined without an execute method. These tools are handled entirely on the client side via the onToolCall handler in useChat.
Server-side tool definition with execute method
Server-side tools are defined using the tool() function with a description, inputSchema (Zod schema), and execute async function that processes the tool inputs and returns the result.
searchKnowledge tool for querying knowledge base
The searchKnowledge tool accepts a query string and optional limit parameter (default 3). It returns an array of results with resourceId, rank, title, content text, section, and score. The tool uses Upstash Search with reranking enabled. It returns 'No relevant information found in the knowledge base.' if no results are found.
Knowledge Base Agent setup with Upstash Search
To build a knowledge base agent with Upstash Search, first create a Search database on Upstash Console to get a REST URL and token. Set these in environment variables as UPSTASH_SEARCH_REST_URL and UPSTASH_SEARCH_REST_TOKEN. Install dependencies with 'pnpm i ai zod @ai-sdk/openai @upstash/search' and 'pnpm i -D tsx'.
Knowledge base agent implementation with multiple tools
This example shows a complete agent using generateText with three tools for knowledge base interaction:
```ts
import { tool, isStepCount, generateText, generateId } from 'ai';
import { z } from 'zod';
import { Search } from '@upstash/search';
import 'dotenv/config';
const search = new Search({
url: process.env.UPSTASH_SEARCH_REST_URL!,
token: process.env.UPSTASH_SEARCH_REST_TOKEN!,
});
type KnowledgeContent = {
text: string;
section: string;
title?: string;
};
const index = search.index<KnowledgeContent>('knowledge-base');
async function main(prompt: string) {
const { text } = await generateText({
model: 'openai/gpt-4o',
prompt,
stopWhen: isStepCount(5),
tools: {
addResource: tool({
description: 'Add a new resource or piece of information to the knowledge base',
inputSchema: z.object({
resource: z.string().describe('The content or resource to add to the knowledge base'),
title: z.string().optional().describe('Optional title for the resource'),
}),
execute: async ({ resource, title }) => {
const id = generateId();
await index.upsert({
id,
content: {
text: resource,
section: 'user-added',
title: title || `Resource ${id.slice(0, 8)}`,
},
});
return `Successfully added resource "${title || 'Untitled'}" to knowledge base with ID: ${id}`;
},
}),
searchKnowledge: tool({
description: 'Search the knowledge base to find relevant information for answering questions',
inputSchema: z.object({
query: z.string().describe('The search query to find relevant information'),
limit: z.number().optional().describe('Maximum number of results to return (default: 3)'),
}),
execute: async ({ query, limit = 3 }) => {
const results = await index.search({
query,
limit,
reranking: true,
});
if (results.length === 0) {
return 'No relevant information found in the knowledge base.';
}
return results.map((hit, i) => ({
resourceId: hit.id,
rank: i + 1,
title: hit.content.title || 'Untitled',
content: hit.content.text || '',
section: hit.content.section || 'unknown',
score: hit.score,
}));
},
}),
deleteResource: tool({
description: 'Delete a resource from the knowledge base',
inputSchema: z.object({
resourceId: z.string().describe('The ID of the resource to delete'),
}),
execute: async ({ resourceId }) => {
try {
await index.delete({ ids: [resourceId] });
return `Successfully deleted resource with ID: ${resourceId}`;
} catch (error) {
return `Failed to delete resource: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
},
}),
},
onStepEnd: ({ toolResults }) => {
if (toolResults.length > 0) {
console.log('Tool results:');
console.dir(toolResults, { depth: null });
}
},
});
return text;
}
const question = 'What are the two main things I worked on before college? (utilize knowledge base)';
main(question).then(console.log).catch(console.error);
```
deleteResource tool for removing from knowledge base
The deleteResource tool accepts a required resourceId string parameter. It deletes the resource from the index and returns a success message. If deletion fails, it returns a failure message with the error details.
addResource tool for adding to knowledge base
The addResource tool accepts a resource string (required) containing content to add, and an optional title string. It generates a unique ID, upserts the resource to the index with section 'user-added', and returns a success message with the generated ID. If no title is provided, it uses a default 'Resource [id prefix]' format.
Tool execution in parallel uses execute async function
Each tool definition includes an execute async function that runs when the tool is called. In parallel tool calling, multiple execute functions run concurrently in the same generation step. The execute function receives the input parameters defined in inputSchema and returns the tool output as a string or structured data.
Parallel tool calling example with weather tool
This example demonstrates parallel tool calling by asking the model to fetch weather for multiple cities. The weather tool is defined with an inputSchema containing city (string) and unit (enum of 'C' or 'F'). When prompted 'What is the weather in Paris and New York?', the model calls the weather tool twice in parallel, once for Paris and once for New York, both with unit 'C'. The toolCalls array shows both calls, and toolResults shows both outputs in sequence.
Parallel tool calls return toolCalls and toolResults arrays
The generateText result object contains toolCalls array with objects having toolName and input fields, and toolResults array with objects having toolName, input, and output fields. Both arrays preserve the order of parallel executions and allow inspection of what was called and what was returned.
Access tool calls from generateText result
Tool calls made by the model during generateText are accessible via the `result.toolCalls` array. Each toolCall object has properties: `toolName` (string), `input` (parsed arguments), and `dynamic` (boolean indicating if tool has no execute function).
Type-safe tool call handling with switch
Tool calls can be handled with a switch statement on `toolCall.toolName` for type narrowing. Skip dynamic tools with `if (toolCall.dynamic) continue;`. This provides type-safe access to input properties based on the tool definition.
Tool definition without execute function
Tools can be defined with inputSchema but without an execute function. These are dynamic tools where the model generates the tool call but the application handles execution separately.
Tool definition with execute function
Tools are defined using the `tool()` function with a description, inputSchema (Zod schema), and execute function. The execute function is async and receives the parsed input parameters. Example: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for') }), execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10 }) })
Access tool results from generateText
Tool execution results are accessible via `result.toolResults` array. Each toolResult contains: `toolName` (string), `input` (the tool input), `output` (the return value from execute), and `dynamic` (boolean). toolResults are only available if the tool has an execute function.
Type-safe tool result handling
Tool results can be accessed with type safety by switching on `toolResult.toolName` and skipping dynamic results. This provides access to both `toolResult.input` and `toolResult.output` with correct types based on the tool schema.