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

  1. Inspect the full response: check candidates[0].finishReason — SAFETY/RECITATION means the content was filtered; adjust the input text.
  2. Retry with different/shorter input to rule out a transient empty response.
  3. Confirm the model id is a TTS-capable model that returns inlineData audio parts.
  4. 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

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


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/0ebfacef3ca406ea. Report an issue: GitHub.