AI SDK Core

Tool Search

Let models discover tools on demand, with direct calling or cache-preserving code mode.

toolSearch() lets a model find the tools it needs without loading every tool's definition into its initial context. Register tools with deferLoading: true; search matches their names and descriptions and makes them available on the next model step.

Use it with generateText, streamText, ToolLoopAgent, or WorkflowAgent from @ai-sdk/workflow. The model supplies a search query; the factory optionally accepts an application-defined search callback and a maxResults limit. WorkflowAgent supports direct tool calling; the other APIs also support cache-preserving code mode.

Direct Tool Calling

The model initially sees only search. After searching, matching definitions are added to the provider's tool list and the model calls those tools directly. This changes the tool definitions and can invalidate the cached prompt prefix.

import { generateText, isStepCount, tool, toolSearch } from 'ai';
import { z } from 'zod/v4';

const weather = tool({
  deferLoading: true,
  description: 'Get the weather forecast for a city.',
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => ({ city, forecast: 'Rain tomorrow.' }),
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { search: toolSearch(), weather },
  stopWhen: isStepCount(5),
  prompt: 'Will it rain in Bangalore tomorrow?',
});

Code Mode

With code mode, the model searches and calls tools through generated code. Set toolDiscovery: 'conversation' to announce discovered definitions in user messages while keeping the provider-visible code tool unchanged. This preserves the tool-definition cache as tools are discovered. Actual prompt-cache reuse depends on the provider.

Using the weather tool defined above:

import { experimental_codeModeTool as codeModeTool } from '@ai-sdk/code-mode';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    code: codeModeTool({ toolDiscovery: 'conversation' }),
    search: toolSearch(),
    weather,
  },
  experimental_toolCallers: {
    search: ['code'],
    weather: ['code'],
  },
  stopWhen: isStepCount(5),
  prompt: 'Will it rain in Bangalore tomorrow?',
});

The model first runs tools.search({ query: 'weather forecast' }). On the next step, it receives the updated capability catalog and can call tools.weather({ city: 'Bangalore' }).

In both modes, search loads up to five matches by default, respects activeTools and caller routing, and keeps discovered tools available for the rest of the generation. MCP client tools work too: add deferLoading: true to the tools returned by client.tools().

Set maxResults to change the number of matching tools loaded per search:

const search = toolSearch({ maxResults: 10 });

maxResults must be a positive safe integer. A larger limit can load related tools in one search, but also adds more tool definitions to the model context. It does not change keyword matching or ranking.

Provide a search callback when your application needs different matching or ranking. It receives the query and all eligible deferred tool names and resolved descriptions, and returns ranked names. It can be synchronous or asynchronous.

const search = toolSearch({
  search: ({ query, tools }) => {
    // Discover related Gmail actions together, in the application's preferred order.
    const preferred = /\bgmail\b/i.test(query)
      ? ['gmail_list_messages', 'gmail_get_message']
      : [];
    return preferred.filter(name => tools.some(tool => tool.name === name));
  },
});

The SDK filters unknown names and duplicates before keeping the first maxResults valid results (five by default). Tools excluded by activeTools or caller routing remain unavailable. Discovery still takes effect on the next model step. Without a callback, search keeps its default keyword matching and ranking.

See the toolSearch() reference for input, output, and matching details.