Skip to content

[JS] Vertex tool calls fail: anyOf/union schema properties are dropped but kept in required400 required fields [...] are not defined in the schema properties #5656

Description

@bbyiringiro

Environment

  • genkit: 1.39.0
  • @genkit-ai/core: 1.39.0
  • @genkit-ai/google-genai: 1.39.0 (Vertex AI / vertexai plugin)
  • zod: 3.25.76
  • zod-to-json-schema: 3.25.2
  • Node: v22.20.0
  • Model: a Gemini model on Vertex (e.g. gemini-2.5-pro)

Summary

When a tool's inputSchema contains a property defined as a union (z.union([...]), which zod-to-json-schema emits as JSON Schema anyOf), the @genkit-ai/google-genai Vertex converter silently drops that property from the generated FunctionDeclaration.parameters.properties, but leaves the property name in the parent's required array.

Vertex then rejects the request:

GenkitError: INVALID_ARGUMENT: Error fetching from
https://aiplatform.googleapis.com/v1beta1/.../models/<model>:streamGenerateContent
[400 Bad Request] Unable to submit request because required fields ['tags'] are not defined in the schema properties.

Reproduction

import { z } from 'zod';
import { toJsonSchema } from '@genkit-ai/core/schema';
import { toGeminiTool } from '@genkit-ai/google-genai/lib/common/converters.mjs';

// Any tool with a union-typed property reproduces it.
const inputSchema = z.object({
  id: z.string(),
  tags: z.union([z.string(), z.array(z.string())]), // <-- union property
});

const decl = toGeminiTool({
  name: 'updateItem',
  description: 'Update an item by id.',
  inputSchema: toJsonSchema({ schema: inputSchema }),
});

console.log(JSON.stringify(decl, null, 2));

Actual output (broken)

{
  "name": "updateItem",
  "description": "Update an item by id.",
  "parameters": {
    "type": "OBJECT",
    "properties": {
      "id": { "type": "STRING" }
      // "tags" is MISSING here ...
    },
    "required": ["id", "tags"] // ... but still listed as required
  }
}

tags is absent from properties yet present in required — exactly the shape Vertex rejects with required fields ['tags'] are not defined in the schema properties.

Using the same tool through a model that consumes JSON Schema directly does not fail, because the union (anyOf) is preserved instead of being rewritten/dropped.

Root cause

In @genkit-ai/google-genai/src/common/converters.ts, toGeminiSchemaProperty:

function toGeminiSchemaProperty(property?: ToolDefinition['inputSchema']) {
  if (!property || !property.type) {
    return undefined; // (1) an `anyOf` node has no `.type`, so it is dropped
  }
  ...
  if (propertyType === 'object') {
    const nestedProperties = {};
    if (property.properties) {
      Object.keys(property.properties).forEach((key) => {
        nestedProperties[key] = toGeminiSchemaProperty(property.properties[key]);
        // (2) a union/`anyOf` child becomes `undefined` -> removed on serialization
      });
    }
    return {
      ...baseSchema,
      type: SchemaType.OBJECT,
      properties: nestedProperties,
      required: property.required, // (3) `required` copied verbatim, still lists the dropped key
    };
  }
  ...
}
  1. The function early-returns undefined for any node without a top-level .type. A union compiles to an anyOf node, which has no .type.
  2. In the object branch, each child property is mapped through toGeminiSchemaProperty, so the union-typed child becomes undefined and disappears from properties once serialized.
  3. required is copied straight from the source schema, so the now-missing property is still listed as required.

There is no handling for anyOf / oneOf / allOf anywhere in the converter — it branches only on .type (object, array, else primitive). Union/polymorphic properties are therefore never forwarded to Vertex, and the resulting properties/required mismatch makes Vertex reject the whole request.

Expected behaviour

One of:

  1. Support anyOf in toGeminiSchemaProperty (preferred). Vertex's Schema type supports anyOf, so the converter should translate union nodes into a Vertex anyOf (recursively converting each member) instead of dropping them.
  2. At minimum, keep required and properties consistent: if a property is dropped during conversion, it must also be removed from the parent's required array so a malformed declaration is never sent - ideally with a warning rather than silently producing invalid output.

Impact

Any tool whose input schema uses z.union (string-or-array values, polymorphic params, discriminated unions, etc.) cannot be used with the Vertex AI plugin — the request fails before reaching the model. The only workaround today is to avoid unions entirely in model-facing tool schemas and normalize the looser shape inside the tool handler instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingjs

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions