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
Description
embedManyfails with more than 100 values whenproviderOptions.google.contentcontains one entry per value, as documented.For 101 values and 101 content entries, it throws before sending any HTTP request:
Expected: split the values and their corresponding content into matching batches and return all 101 embeddings.
Actual:
embedManysplitsvaluesinto batches of 100 but forwards the entireproviderOptionsobject 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:embedManyforwards unchanged provider options.GoogleEmbeddingModelchecks content length against the batch.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
valuesandcontentinto matching groups of at most 100. The fix needs to preserve their index alignment, includingnullentries, 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.
Save as
repro.mts:Output:
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.44zod: 4.4.3Code of Conduct