Skip to content

Google embedMany fails above 100 values when per-value content is provided #21095

Description

@brennanbutler01

Description

embedMany fails with more than 100 values when providerOptions.google.content contains one entry per value, as documented.

For 101 values and 101 content entries, it throws before sending any HTTP request:

The number of multimodal content entries (101) must match the number of values (100).

Expected: split the values and their corresponding content into matching batches and return all 101 embeddings.

Actual: embedMany splits values into batches of 100 but forwards the entire providerOptions object to each call. The Google provider then compares the full content array against the smaller batch and rejects it.

The relevant code is unchanged on main at 9528712:

Related: #13312 introduced per-value content, and #16575 fixed the Google batch limit. This still occurs with that limit correctly set to 100. Text-only calls without the content option batch successfully.

Workaround: manually slice both values and content into matching groups of at most 100. The fix needs to preserve their index alignment, including null entries, while keeping the length validation for genuinely mismatched inputs.

Reproduction

No API key or network request is needed. The custom fetch returns synthetic embeddings; the failing case never reaches it.

npm install --ignore-scripts --save-exact ai@7.0.106 @ai-sdk/google@4.0.75 zod@4.4.3
node repro.mts

Save as repro.mts:

import { embedMany } from 'ai';
import { createGoogle } from '@ai-sdk/google';

for (const count of [100, 101]) {
  let requests = 0;
  const google = createGoogle({
    apiKey: 'synthetic-test-key',
    fetch: async url => {
      requests++;
      return Response.json(
        String(url).endsWith(':embedContent')
          ? { embedding: { values: [1, 2] } }
          : {
              embeddings: Array.from({ length: 100 }, () => ({
                values: [1, 2],
              })),
            },
      );
    },
  });

  try {
    const result = await embedMany({
      model: google.embedding('gemini-embedding-2'),
      values: Array.from({ length: count }, (_, i) => `Document ${i}`),
      providerOptions: {
        google: {
          content: Array.from({ length: count }, (_, i) => [
            { text: `Context ${i}` },
          ]),
        },
      },
      maxRetries: 0,
      maxParallelCalls: 1,
    });
    console.log({ count, requests, embeddings: result.embeddings.length });
  } catch (error) {
    console.log({
      count,
      requests,
      error: error instanceof Error ? error.message : String(error),
    });
  }
}

Output:

{ count: 100, requests: 1, embeddings: 100 }
{
  count: 101,
  requests: 0,
  error: 'The number of multimodal content entries (101) must match the number of values (100).'
}

Local regression checks on Node 22.20.0 and 24.13.0: 7 failing cases and 11 passing controls per runtime. Failures cover 101/200/201 values, sequential and parallel batches, null/mixed content entries, and gemini-embedding-2-preview. Controls cover single calls, 99/100-item calls, text-only batching, matching manual slices, empty input, and rejection of genuinely mismatched content. The standalone example also passes strict TypeScript checking. No live Google requests were made.

I used AI assistance to investigate and prepare the synthetic reproduction and tests.

AI SDK Version

  • ai: 7.0.106
  • @ai-sdk/google: 4.0.75
  • @ai-sdk/provider-utils: 5.0.44
  • zod: 4.4.3
  • Node.js: 22.20.0 and 24.13.0

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions