Back to Ai

dynamicTool

content/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx

2.1.107.3 KB
Original Source

dynamicTool()

The dynamicTool function creates tools where the input and output types are not known at compile time. This is useful for scenarios such as:

  • MCP (Model Context Protocol) tools without schemas
  • User-defined functions loaded at runtime
  • Tools loaded from external sources or databases
  • Dynamic tool generation based on user input

Unlike the regular tool function, dynamicTool accepts and returns unknown types, allowing you to work with tools that have runtime-determined schemas.

ts
import { dynamicTool } from 'ai';
import { z } from 'zod';

export const customTool = dynamicTool({
  description: 'Execute a custom user-defined function',
  inputSchema: z.object({}),
  // input is typed as 'unknown'
  execute: async input => {
    const { action, parameters } = input as any;

    // Execute your dynamic logic
    return {
      result: `Executed ${action} with ${JSON.stringify(parameters)}`,
    };
  },
});

Import

<Snippet text={import { dynamicTool } from "ai"} prompt={false} />

API Signature

Parameters

<PropertiesTable content={[ { name: 'tool', type: 'Object', description: 'The dynamic tool definition.', properties: [ { type: 'Object', parameters: [ { name: 'description', isOptional: true, type: 'string', description: 'Information about the purpose of the tool including details on how and when it can be used by the model.' }, { name: 'title', isOptional: true, type: 'string', description: 'A human-readable title for the tool.' }, { name: 'needsApproval', isOptional: true, type: 'boolean | ((options: { args: unknown }) => boolean | Promise<boolean>)', description: 'Whether the tool needs user approval before execution. Can be a boolean or a function that receives the tool arguments and returns a boolean.' }, { name: 'inputSchema', type: 'FlexibleSchema<unknown>', description: 'The schema of the input that the tool expects. While the type is unknown, a schema is still required for validation. You can use Zod schemas with z.unknown() or z.any() for fully dynamic inputs.' }, { name: 'execute', type: 'ToolExecuteFunction<unknown, unknown>', description: 'An async function that is called with the arguments from the tool call. The input is typed as unknown and must be validated/cast at runtime.', properties: [ { type: "ToolExecutionOptions", parameters: [ { name: 'toolCallId', type: 'string', description: 'The ID of the tool call.', }, { name: "messages", type: "ModelMessage[]", description: "Messages that were sent to the language model." }, { name: "abortSignal", type: "AbortSignal", isOptional: true, description: "An optional abort signal." }, { name: "experimental_context", type: "unknown", isOptional: true, description: "Context that is passed into tool execution. Experimental (can break in patch releases)." } ] } ] }, { name: 'outputSchema', isOptional: true, type: 'Zod Schema | JSON Schema', description: 'The schema of the output that the tool produces. Used for validation and type inference.' }, { name: 'toModelOutput', isOptional: true, type: '({toolCallId: string; input: unknown; output: unknown}) => ToolResultOutput | PromiseLike<ToolResultOutput>', description: 'Optional conversion function that maps the tool result to an output that can be used by the language model.' }, { name: 'onInputStart', isOptional: true, type: '(options: ToolExecutionOptions) => void | PromiseLike<void>', description: 'Optional function that is called when the argument streaming starts. Only called when the tool is used in a streaming context.' }, { name: 'onInputDelta', isOptional: true, type: '(options: { inputTextDelta: string } & ToolExecutionOptions) => void | PromiseLike<void>', description: 'Optional function that is called when an argument streaming delta is available. Only called when the tool is used in a streaming context.' }, { name: 'onInputAvailable', isOptional: true, type: '(options: { input: unknown } & ToolExecutionOptions) => void | PromiseLike<void>', description: 'Optional function that is called when a tool call can be started, even if the execute function is not provided.' }, { name: 'providerOptions', isOptional: true, type: 'ProviderOptions', description: 'Additional provider-specific metadata.' } ] } ] }

]} />

Returns

A Tool<unknown, unknown> with type: 'dynamic' that can be used with generateText, streamText, and other AI SDK functions.

Type-Safe Usage

When using dynamic tools alongside static tools, you need to check the dynamic flag for proper type narrowing:

ts
const result = await generateText({
  model: __MODEL__,
  tools: {
    // Static tool with known types
    weather: weatherTool,
    // Dynamic tool with unknown types
    custom: dynamicTool({
      /* ... */
    }),
  },
  onStepFinish: ({ toolCalls, toolResults }) => {
    for (const toolCall of toolCalls) {
      if (toolCall.dynamic) {
        // Dynamic tool: input/output are 'unknown'
        console.log('Dynamic tool:', toolCall.toolName);
        console.log('Input:', toolCall.input);
        continue;
      }

      // Static tools have full type inference
      switch (toolCall.toolName) {
        case 'weather':
          // TypeScript knows the exact types
          console.log(toolCall.input.location); // string
          break;
      }
    }
  },
});

Usage with useChat

When used with useChat (UIMessage format), dynamic tools appear as dynamic-tool parts:

tsx
{
  message.parts.map(part => {
    switch (part.type) {
      case 'dynamic-tool':
        return (
          <div>
            <h4>Tool: {part.toolName}</h4>
            <pre>{JSON.stringify(part.input, null, 2)}</pre>
          </div>
        );
      // ... handle other part types
    }
  });
}