Skip to content

Commit 351dc5f

Browse files
committed
fix: handle empty responses from Gemini API
- Add tracking for whether any text content was yielded - Throw specific error when Gemini API returns empty response - Add translation keys for empty response error in English and Chinese - Add test coverage for empty response scenario Fixes #7046
1 parent 962df86 commit 351dc5f

File tree

4 files changed

+38
-2
lines changed

4 files changed

+38
-2
lines changed

src/api/providers/__tests__/gemini.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,24 @@ describe("GeminiHandler", () => {
9090
)
9191
})
9292

93+
it("should handle empty response from API", async () => {
94+
// Setup the mock to return an async generator with no text content
95+
;(handler["client"].models.generateContentStream as any).mockResolvedValue({
96+
[Symbol.asyncIterator]: async function* () {
97+
// Yield only usage metadata, no text content
98+
yield { usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 0 } }
99+
},
100+
})
101+
102+
const stream = handler.createMessage(systemPrompt, mockMessages)
103+
104+
await expect(async () => {
105+
for await (const _chunk of stream) {
106+
// Should throw before yielding any chunks
107+
}
108+
}).rejects.toThrow(t("common:errors.gemini.generate_stream"))
109+
})
110+
93111
it("should handle API errors", async () => {
94112
const mockError = new Error("Gemini API error")
95113
;(handler["client"].models.generateContentStream as any).mockRejectedValue(mockError)

src/api/providers/gemini.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
9494

9595
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
9696
let pendingGroundingMetadata: GroundingMetadata | undefined
97+
let hasYieldedContent = false // Track if we've yielded any text content
9798

9899
for await (const chunk of result) {
99100
// Process candidates and their parts to separate thoughts from content
@@ -115,6 +116,7 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
115116
// This is regular content
116117
if (part.text) {
117118
yield { type: "text", text: part.text }
119+
hasYieldedContent = true
118120
}
119121
}
120122
}
@@ -124,13 +126,27 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
124126
// Fallback to the original text property if no candidates structure
125127
else if (chunk.text) {
126128
yield { type: "text", text: chunk.text }
129+
hasYieldedContent = true
127130
}
128131

129132
if (chunk.usageMetadata) {
130133
lastUsageMetadata = chunk.usageMetadata
131134
}
132135
}
133136

137+
// Check if we got an empty response
138+
if (!hasYieldedContent) {
139+
// Log the issue for debugging
140+
console.warn("Gemini API returned empty response, no text content was generated")
141+
142+
// Throw a specific error that can be caught and retried
143+
throw new Error(
144+
t("common:errors.gemini.empty_response", {
145+
error: "The Gemini API did not return any text content. This may be a temporary issue.",
146+
}),
147+
)
148+
}
149+
134150
if (pendingGroundingMetadata) {
135151
const citations = this.extractCitationsOnly(pendingGroundingMetadata)
136152
if (citations) {

src/i18n/locales/en/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
"gemini": {
9191
"generate_stream": "Gemini generate context stream error: {{error}}",
9292
"generate_complete_prompt": "Gemini completion error: {{error}}",
93-
"sources": "Sources:"
93+
"sources": "Sources:",
94+
"empty_response": "Gemini API returned empty response: {{error}}"
9495
},
9596
"cerebras": {
9697
"authenticationFailed": "Cerebras API authentication failed. Please check your API key is valid and not expired.",

src/i18n/locales/zh-CN/common.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)