-
Notifications
You must be signed in to change notification settings - Fork 3.4k
fix(google): make candidates in response schema optional
#10522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Suggestion:
The doGenerate method assumes candidates is always present and tries to access it without checking if it's undefined. Since the schema now makes candidates optional, this will cause a runtime TypeError when the API returns a response without that field.
View Details
📝 Patch Details
diff --git a/packages/google/src/google-generative-ai-language-model.ts b/packages/google/src/google-generative-ai-language-model.ts
index 7c6557f5b..ca03856f7 100644
--- a/packages/google/src/google-generative-ai-language-model.ts
+++ b/packages/google/src/google-generative-ai-language-model.ts
@@ -210,11 +210,11 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
fetch: this.config.fetch,
});
- const candidate = response.candidates[0];
+ const candidate = response.candidates?.[0];
const content: Array<LanguageModelV3Content> = [];
// map ordered parts to content:
- const parts = candidate.content?.parts ?? [];
+ const parts = candidate?.content?.parts ?? [];
const usageMetadata = response.usageMetadata;
@@ -279,7 +279,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
const sources =
extractSources({
- groundingMetadata: candidate.groundingMetadata,
+ groundingMetadata: candidate?.groundingMetadata,
generateId: this.config.generateId,
}) ?? [];
for (const source of sources) {
@@ -289,7 +289,7 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
return {
content,
finishReason: mapGoogleGenerativeAIFinishReason({
- finishReason: candidate.finishReason,
+ finishReason: candidate?.finishReason,
hasToolCalls: content.some(part => part.type === 'tool-call'),
}),
usage: {
@@ -303,9 +303,9 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
providerMetadata: {
google: {
promptFeedback: response.promptFeedback ?? null,
- groundingMetadata: candidate.groundingMetadata ?? null,
- urlContextMetadata: candidate.urlContextMetadata ?? null,
- safetyRatings: candidate.safetyRatings ?? null,
+ groundingMetadata: candidate?.groundingMetadata ?? null,
+ urlContextMetadata: candidate?.urlContextMetadata ?? null,
+ safetyRatings: candidate?.safetyRatings ?? null,
usageMetadata: usageMetadata ?? null,
},
},
Analysis
Missing null check on optional candidates field in doGenerate() method
What fails: GoogleGenerativeAILanguageModel.doGenerate() directly accesses response.candidates[0] without checking if candidates is undefined, causing a TypeError when the Google Generative AI API returns a response without the candidates field.
How to reproduce:
- Call the
doGenerate()method on any Google Generative AI language model - The API returns a response where
candidatesis undefined (allowed by the schema at line 880:.optional()) - TypeScript compilation fails with error:
error TS18048: 'response.candidates' is possibly 'undefined'
Result: When response.candidates is undefined, accessing response.candidates[0] throws: TypeError: Cannot read property '0' of undefined
Expected: The code should handle responses without candidates gracefully, similar to the doStream() method which uses optional chaining value.candidates?.[0] and checks if (candidate == null) before using the candidate.
Root cause: The schema definition at line 880 makes candidates optional with .optional(), but the doGenerate method at line 213 assumes it always exists. The doStream method (line 400) correctly handles this with optional chaining.
Fix: Apply optional chaining throughout doGenerate() when accessing candidate properties:
- Line 213: Changed
response.candidates[0]toresponse.candidates?.[0] - Line 218: Changed
candidate.content?.partstocandidate?.content?.parts - Lines 282, 292, 306-308: Added optional chaining for all
candidateproperty accesses
This allows the method to gracefully handle responses without candidates by returning empty content while still providing usage metadata and other response data.
can you share a code snippet that triggers the error? The response schema was unchanged since its introduction last year, and we didn't have problems with it thus far. Might be related to the new gemini models? |
changeset is missing |
|
I often get this error with gemini 2.5 pro and gemini 2.5 flash lite and gemini 3 pro |
| urlContextMetadata: getUrlContextMetadataSchema().nullish(), | ||
| }), | ||
| ), | ||
| ).optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making the candidates field optional breaks the type definitions that depend on it. The types at lines 893, 896, 904, and 908 attempt to access array elements via ['candidates'][number], which is invalid when candidates is optional (could be undefined).
View Details
📝 Patch Details
diff --git a/packages/google/src/google-generative-ai-language-model.ts b/packages/google/src/google-generative-ai-language-model.ts
index 7c6557f5b..c8b6f11d9 100644
--- a/packages/google/src/google-generative-ai-language-model.ts
+++ b/packages/google/src/google-generative-ai-language-model.ts
@@ -890,10 +890,10 @@ const responseSchema = lazySchema(() =>
);
type ContentSchema = NonNullable<
- InferSchema<typeof responseSchema>['candidates'][number]['content']
+ NonNullable<InferSchema<typeof responseSchema>['candidates']>[number]['content']
>;
export type GroundingMetadataSchema = NonNullable<
- InferSchema<typeof responseSchema>['candidates'][number]['groundingMetadata']
+ NonNullable<InferSchema<typeof responseSchema>['candidates']>[number]['groundingMetadata']
>;
type GroundingChunkSchema = NonNullable<
@@ -901,11 +901,11 @@ type GroundingChunkSchema = NonNullable<
>[number];
export type UrlContextMetadataSchema = NonNullable<
- InferSchema<typeof responseSchema>['candidates'][number]['urlContextMetadata']
+ NonNullable<InferSchema<typeof responseSchema>['candidates']>[number]['urlContextMetadata']
>;
export type SafetyRatingSchema = NonNullable<
- InferSchema<typeof responseSchema>['candidates'][number]['safetyRatings']
+ NonNullable<InferSchema<typeof responseSchema>['candidates']>[number]['safetyRatings']
>[number];
// limited version of the schema, focussed on what is needed for the implementation
Analysis
Optional candidates field breaks type definitions in responseSchema
What fails: TypeScript compilation fails with TS2537 errors when candidates field is marked as optional with .optional() at line 880, because type definitions at lines 893, 896, 904, and 908 attempt to index into the optional field using ['candidates'][number], which is invalid when the field can be undefined.
How to reproduce:
cd packages/google
pnpm type-checkResult: TypeScript compiler throws four TS2537 errors:
src/google-generative-ai-language-model.ts(893,52): error TS2537: Type '...' has no matching index signature for type 'number'.src/google-generative-ai-language-model.ts(896,52): error TS2537: Type '...' has no matching index signature for type 'number'.src/google-generative-ai-language-model.ts(904,52): error TS2537: Type '...' has no matching index signature for type 'number'.src/google-generative-ai-language-model.ts(908,52): error TS2537: Type '...' has no matching index signature for type 'number'.
Expected: TypeScript compilation should succeed. The type definitions need to wrap the optional candidates field with NonNullable before attempting to index it, narrowing the type from Array<Candidate> | undefined to Array<Candidate>.
Fix: Wrap InferSchema<typeof responseSchema>['candidates'] with NonNullable in all four type definitions (ContentSchema, GroundingMetadataSchema, UrlContextMetadataSchema, and SafetyRatingSchema).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Suggestion:
The doGenerate method accesses response.candidates[0] without checking if candidates is null or undefined, which will crash when the API returns a response without candidates after the schema change.
View Details
📝 Patch Details
diff --git a/packages/google/src/google-generative-ai-language-model.test.ts b/packages/google/src/google-generative-ai-language-model.test.ts
index 6a33cc1fc..574c86119 100644
--- a/packages/google/src/google-generative-ai-language-model.test.ts
+++ b/packages/google/src/google-generative-ai-language-model.test.ts
@@ -1880,6 +1880,47 @@ describe('doGenerate', () => {
},
});
});
+
+ it('should handle null candidates array', async () => {
+ server.urls[TEST_URL_GEMINI_PRO].response = {
+ type: 'json-value',
+ body: {
+ candidates: null,
+ usageMetadata: {
+ promptTokenCount: 10,
+ totalTokenCount: 10,
+ },
+ },
+ };
+
+ const { content, finishReason, usage } = await model.doGenerate({
+ prompt: TEST_PROMPT,
+ });
+
+ expect(content).toEqual([]);
+ expect(finishReason).toEqual('other');
+ expect(usage.inputTokens).toEqual(10);
+ });
+
+ it('should handle undefined candidates array', async () => {
+ server.urls[TEST_URL_GEMINI_PRO].response = {
+ type: 'json-value',
+ body: {
+ usageMetadata: {
+ promptTokenCount: 10,
+ totalTokenCount: 10,
+ },
+ },
+ };
+
+ const { content, finishReason, usage } = await model.doGenerate({
+ prompt: TEST_PROMPT,
+ });
+
+ expect(content).toEqual([]);
+ expect(finishReason).toEqual('other');
+ expect(usage.inputTokens).toEqual(10);
+ });
});
describe('doStream', () => {
diff --git a/packages/google/src/google-generative-ai-language-model.ts b/packages/google/src/google-generative-ai-language-model.ts
index 1759f6a95..10c170a77 100644
--- a/packages/google/src/google-generative-ai-language-model.ts
+++ b/packages/google/src/google-generative-ai-language-model.ts
@@ -210,9 +210,42 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
fetch: this.config.fetch,
});
- const candidate = response.candidates[0];
+ const candidate = response.candidates?.[0];
const content: Array<LanguageModelV3Content> = [];
+ // sometimes the API returns an empty candidates array
+ if (candidate == null) {
+ const usageMetadata = response.usageMetadata;
+
+ return {
+ content: [],
+ finishReason: 'other' as const,
+ usage: {
+ inputTokens: usageMetadata?.promptTokenCount ?? undefined,
+ outputTokens: usageMetadata?.candidatesTokenCount ?? undefined,
+ totalTokens: usageMetadata?.totalTokenCount ?? undefined,
+ reasoningTokens: usageMetadata?.thoughtsTokenCount ?? undefined,
+ cachedInputTokens: usageMetadata?.cachedContentTokenCount ?? undefined,
+ },
+ warnings,
+ providerMetadata: {
+ google: {
+ promptFeedback: response.promptFeedback ?? null,
+ groundingMetadata: null,
+ urlContextMetadata: null,
+ safetyRatings: null,
+ usageMetadata: usageMetadata ?? null,
+ },
+ },
+ request: { body },
+ response: {
+ // TODO timestamp, model id, id
+ headers: responseHeaders,
+ body: rawResponse,
+ },
+ };
+ }
+
// map ordered parts to content:
const parts = candidate.content?.parts ?? [];
Analysis
Missing null check when accessing response.candidates in doGenerate()
What fails: GoogleGenerativeAILanguageModel.doGenerate() crashes when the Google Generative AI API returns a response without a candidates field (or with null/undefined), because the code accesses response.candidates[0] directly without checking if candidates exists first.
TypeScript error: error TS18049: 'response.candidates' is possibly 'null' or 'undefined' at line 213
How to reproduce: The bug occurs automatically when the API returns either:
{ "candidates": null, "usageMetadata": {...} }- explicitly null{ "usageMetadata": {...} }- field missing entirely
Both are valid API responses since the schema at line 872-882 declares candidates: z.array(...).nullish(), making it optional.
Result: Runtime error "Cannot read property '0' of undefined" or "Cannot read property '0' of null"
Expected: Should handle missing candidates gracefully by returning an empty response with appropriate metadata, matching the pattern already implemented in the doStream() method (lines 400-405).
Fix:
- Changed line 213 from
const candidate = response.candidates[0];toconst candidate = response.candidates?.[0];using optional chaining - Added null check:
if (candidate == null) { return {...}; }to handle the case where candidates is missing - Returns empty content array with
finishReason: 'other'and preserves usage metadata, consistent withdoStream()pattern
Tests added:
- Test for null candidates array
- Test for undefined/missing candidates array
Both tests pass, confirming the fix resolves the issue while maintaining backward compatibility with all 168 existing tests.
candidates in response schema optional
gr2m
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
merging based on google's own docs at https://googleapis.github.io/python-genai/genai.html#genai.types.GenerateContentResponse.candidates
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Suggestion:
The code will crash at runtime if the candidates field is undefined or null, since it directly accesses response.candidates[0] without checking if candidates exists first.
View Details
📝 Patch Details
diff --git a/packages/google/src/google-generative-ai-language-model.ts b/packages/google/src/google-generative-ai-language-model.ts
index e02f8a4d8..8c8e004b6 100644
--- a/packages/google/src/google-generative-ai-language-model.ts
+++ b/packages/google/src/google-generative-ai-language-model.ts
@@ -210,7 +210,16 @@ export class GoogleGenerativeAILanguageModel implements LanguageModelV3 {
fetch: this.config.fetch,
});
- const candidate = response.candidates[0];
+ const candidate = response.candidates?.[0];
+
+ if (candidate == null) {
+ throw new Error(
+ 'No candidates returned from Google Generative AI API. ' +
+ 'This can happen when the prompt is blocked by safety filters. ' +
+ `Check promptFeedback for details: ${JSON.stringify(response.promptFeedback)}`
+ );
+ }
+
const content: Array<LanguageModelV3Content> = [];
// map ordered parts to content:
Analysis
Unsafe access to candidates array in GoogleGenerativeAILanguageModel.doGenerate()
What fails: doGenerate() method in packages/google/src/google-generative-ai-language-model.ts crashes at runtime when the Google Generative AI API returns a response with candidates field set to null or undefined.
How to reproduce: The schema at line 894 defines candidates: z.array(...).nullish(), which allows the field to be null, undefined, or missing. When the API returns such a response (which can happen when the prompt is blocked by safety filters), the code at line 213 attempts to access response.candidates[0] without checking if candidates exists first.
Example that triggers the error:
// API returns: { candidates: null, promptFeedback: {...} }
const candidate = response.candidates[0]; // TypeError: Cannot read properties of null (reading '0')Result: Runtime error - TypeError: Cannot read properties of null (reading '0') or similar
Expected: Should handle missing/null candidates gracefully with a descriptive error, consistent with how doStream() handles this at line 400 using optional chaining value.candidates?.[0]
Related code pattern: The doStream() method correctly handles this case:
const candidate = value.candidates?.[0];
if (candidate == null) {
return;
}API Reference: Google Gemini API documentation indicates candidates field is optional and can be missing when the prompt is blocked or fails content filtering.
Fix applied: Added optional chaining and null check before accessing candidates array, with a descriptive error message for debugging.
|
hmm there is lots of type errors remaining, run It's not a straight forward fix. Can you look into these? |
|
see the review by the vercel bot: #10522 (review). It suggests this might be related to safety filters. |
Head branch was pushed to by a user without write access
|
@gr2m |
Background
The
candidatesfield caused the following validation error when it was missing or undefined:Invalid input: expected array, received undefined
Summary
candidatestoz.array(...).optional()Manual Verification
candidatesstill parse correctlycandidatesno longer throw validation errorspnpm buildand manual invocation to confirm end-to-end behaviorChecklist
pnpm changeset)Future Work
candidatesRelated Issues
N/A