vercel/ai · error · InvalidArgumentError

callers for tool "${toolName}" must be an array.

Error message

callers for tool "${toolName}" must be an array.

What it means

Each value in `experimental_toolCallers` must be an array of caller tool names. resolveToolCallerConfiguration throws this InvalidArgumentError when a tool's callers value is not an array (e.g. a single string or an object was passed). The validation runs before any model call, so the request never reaches the provider.

Source

Thrown at packages/ai/src/generate-text/tool-caller-configuration.ts:49

  toolCallers: Experimental_ToolCallers<TOOLS> | undefined;
}): ResolvedToolCallers | undefined {
  if (tools == null || toolCallers == null) {
    return undefined;
  }

  const resolved: ResolvedToolCallers = {};

  for (const [toolName, callers] of Object.entries(toolCallers)) {
    if (!Object.prototype.hasOwnProperty.call(tools, toolName)) {
      throw new InvalidArgumentError({
        parameter: 'experimental_toolCallers',
        value: toolCallers,
        message: `unknown tool "${toolName}".`,
      });
    }

    if (!Array.isArray(callers)) {
      throw new InvalidArgumentError({
        parameter: 'experimental_toolCallers',
        value: toolCallers,
        message: `callers for tool "${toolName}" must be an array.`,
      });
    }

    resolved[toolName] = callers.map(caller => {
      if (caller === DIRECT_TOOL_CALL) {
        return caller;
      }

      if (
        typeof caller !== 'string' ||
        !Object.prototype.hasOwnProperty.call(tools, caller) ||
        experimental_getToolCaller(tools[caller]) == null
      ) {
        throw new InvalidArgumentError({
          parameter: 'experimental_toolCallers',

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Wrap the caller name in an array: { toolName: ['callerTool'] }.
  2. If the value comes from config, normalize it (Array.isArray(x) ? x : [x]) before passing.
  3. Validate the toolCallers shape with a schema (e.g. zod) at startup.

Example fix

// before
experimental_toolCallers: { summarize: 'summarizerTool' }

// after
experimental_toolCallers: { summarize: ['summarizerTool'] }
Defensive patterns

Strategy: validation

Validate before calling

function normalizeToolCallers(toolCallers: Record<string, unknown>) {
  for (const [name, callers] of Object.entries(toolCallers)) {
    if (!Array.isArray(callers)) {
      throw new Error(`callers for tool "${name}" must be an array`);
    }
  }
}

Type guard

function isToolCallersConfig(
  v: unknown
): v is Record<string, string[]> {
  return (
    typeof v === 'object' && v !== null &&
    Object.values(v).every(arr => Array.isArray(arr) && arr.every(c => typeof c === 'string'))
  );
}

Try / catch

try {
  await streamText({ tools, experimental_toolCallers, ... });
} catch (error) {
  if (InvalidArgumentError.isInstance(error) && error.parameter === 'experimental_toolCallers') {
    // normalize: wrap scalar values in arrays, then retry
  } else throw error;
}

Prevention

When it happens

Trigger: Passing `experimental_toolCallers: { myTool: 'otherTool' }` (bare string) or any non-array value instead of `myTool: ['otherTool']`.

Common situations: Misreading the API and passing a single caller as a string instead of a one-element array; config loaded from JSON/DB where the array wrapper was lost; merging configs where one entry was overwritten with a scalar.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/c67d17f499fec4aa. Report an issue: GitHub.