vercel/ai · error · InvalidArgumentError

unknown tool "${toolName}".

Error message

unknown tool "${toolName}".

What it means

When resolving `experimental_toolCallers`, every key in the toolCallers map must correspond to a tool actually defined in the request's `tools`. resolveToolCallerConfiguration throws this InvalidArgumentError if a toolCallers key references a tool name that does not exist (own-property check, so inherited names like 'constructor' don't count). This is an upfront configuration validation to fail fast instead of silently ignoring routing rules.

Source

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

export type ResolvedToolCallers = Record<string, ReadonlyArray<string>>;

export function resolveToolCallerConfiguration<TOOLS extends ToolSet>({
  tools,
  toolCallers,
}: {
  tools: TOOLS | undefined;
  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;
      }

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Make every key of experimental_toolCallers exactly match a key in the tools object of the same call.
  2. Fix typos/renames in the toolCallers map (or in tools).
  3. Derive the toolCallers keys programmatically from the same object used for tools.
  4. Remove stale entries for tools no longer passed in this request.

Example fix

// before
streamText({ model, tools: { weather }, experimental_toolCallers: { wether: ['weatherAgent'] } });

// after
streamText({ model, tools: { weather }, experimental_toolCallers: { weather: ['weatherAgent'] } });
Defensive patterns

Strategy: validation

Validate before calling

function validateToolCallers(tools: Record<string, unknown>, toolCallers: Record<string, unknown>) {
  for (const name of Object.keys(toolCallers)) {
    if (!Object.prototype.hasOwnProperty.call(tools, name)) {
      throw new Error(`experimental_toolCallers references unknown tool "${name}"`);
    }
  }
}

Type guard

function allCallersKnown<K extends string>(
  tools: Record<K, unknown>,
  toolCallers: Partial<Record<K, unknown>>
): toolCallers is Partial<Record<K, unknown>> {
  return Object.keys(toolCallers).every(k => k in tools);
}

Try / catch

try {
  await generateText({ tools, experimental_toolCallers, ... });
} catch (error) {
  if (InvalidArgumentError.isInstance(error) && error.parameter === 'experimental_toolCallers') {
    console.error('Tool caller config error:', error.message);
  } else throw error;
}

Prevention

When it happens

Trigger: Passing `experimental_toolCallers: { myTool: [...] }` to streamText/generateText where 'myTool' is misspelled, was renamed, or was never added to the `tools` object of the same call.

Common situations: Typo or renamed tool after refactoring; defining toolCallers at call sites that share a config object but have different tool sets; dynamically built tool maps where a tool was conditionally omitted; case-sensitivity mismatches in tool names.

Related errors


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