moeru-ai/airi · error · Error
Gemini TTS response missing audio data
Error message
Gemini TTS response missing audio data
What it means
Thrown by the Gemini TTS fetch wrapper after a 2xx response when no inlineData audio part can be found in the candidates' content. The HTTP call succeeded but the model returned no decodable audio — most commonly because a safety filter blocked the output or the model returned an empty finish. The wrapper cannot synthesize WAV without the base64 audio, so it aborts.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts:106
contents: [{ parts: [{ text: body.input }] }],
generationConfig: {
responseModalities: ['AUDIO'],
speechConfig: {
voiceConfig: { prebuiltVoiceConfig: { voiceName: body.voice || 'Kore' } },
},
...(body.temperature !== undefined ? { temperature: body.temperature } : {}),
},
}),
})
if (!response.ok)
throw new Error(`Gemini TTS request failed: ${response.status} ${await response.text().catch(() => '')}`)
const data = await response.json() as {
candidates?: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }>
}
const audio = data.candidates?.[0]?.content?.parts?.find(part => part.inlineData)?.inlineData?.data
if (!audio)
throw new Error('Gemini TTS response missing audio data')
return new Response(toWavFromPCM16(decodeBase64(audio), 24000), {
status: 200,
headers: { 'Content-Type': 'audio/wav' },
})
}
}
export const providerGoogleGeminiAudioSpeech = defineProvider<GoogleGeminiSpeechConfig>({
id: 'google-gemini-audio-speech',
name: 'Google Gemini',
nameLocalize: ({ t }) => t('settings.pages.providers.provider.google-gemini-audio-speech.title'),
description: 'aistudio.google.com',
descriptionLocalize: ({ t }) => t('settings.pages.providers.provider.google-gemini-audio-speech.description'),
tasks: ['text-to-speech', 'tts'],
icon: 'i-lobe-icons:gemini',
iconColor: 'i-lobe-icons:gemini-color',
createProviderConfig: () => googleGeminiSpeechConfigSchema,View on GitHub (pinned to 27111382b4)
Solutions
- Inspect the full response: check candidates[0].finishReason — SAFETY/RECITATION means the content was filtered; adjust the input text.
- Retry with different/shorter input to rule out a transient empty response.
- Confirm the model id is a TTS-capable model that returns inlineData audio parts.
- If safety blocking is persistent for legitimate input, consider a different voice/model or input rephrasing.
Defensive patterns
Strategy: validation
Validate before calling
// after a 2xx response, inspect candidates before assuming audio exists
const data = await response.json()
const candidate = data.candidates?.[0]
if (!candidate || candidate.finishReason === 'SAFETY' || candidate.finishReason === 'RECITATION') {
// filtered; do not attempt to decode audio; surface a content-filter message
} Type guard
function hasGeminiAudio(data: unknown): data is { candidates: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }> } {
if (!data || typeof data !== 'object') return false
const parts = (data as any)?.candidates?.[0]?.content?.parts
return Array.isArray(parts) && parts.some((p: any) => p?.inlineData?.data)
} Try / catch
try {
const res = await provider.speech(model).fetch(url, { body: JSON.stringify(body) })
}
catch (err) {
if (err instanceof Error && err.message === 'Gemini TTS response missing audio data') {
// likely safety filter; retry with rephrased/shorter input or choose another voice/model
}
else throw err
} Prevention
- Inspect candidates[0].finishReason to distinguish safety blocks from empty responses.
- Retry with different input when a safety block is suspected.
- Confirm the model returns inlineData audio parts (TTS-capable model).
When it happens
Trigger: response.json() parses successfully but data.candidates[0].content.parts contains no part with inlineData.data. Happens when Gemini applies a safety block (finishReason SAFETY with no content), the model returns no candidates, or the audio is nested under a different part type.
Common situations: Input text tripped a safety filter (prompt or output). Empty/harmless-looking input that the model still refused. Model variant that returns audio under a different schema field. Transient empty response from the model.
Related errors
- Gemini TTS request failed: ${response.status} ${await respon
- Missing input text for Gemini TTS
- Missing model for Gemini TTS
- Failed to fetch voices: ${response.statusText}
- listAccounts failed
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/0ebfacef3ca406ea.
Report an issue: GitHub.