{"record":{"id":"0ebfacef3ca406ea","repo":"moeru-ai/airi","slug":"gemini-tts-response-missing-audio-data","errorCode":null,"errorMessage":"Gemini TTS response missing audio data","messagePattern":"Gemini TTS response missing audio data","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts","lineNumber":106,"sourceCode":"        contents: [{ parts: [{ text: body.input }] }],\n        generationConfig: {\n          responseModalities: ['AUDIO'],\n          speechConfig: {\n            voiceConfig: { prebuiltVoiceConfig: { voiceName: body.voice || 'Kore' } },\n          },\n          ...(body.temperature !== undefined ? { temperature: body.temperature } : {}),\n        },\n      }),\n    })\n    if (!response.ok)\n      throw new Error(`Gemini TTS request failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n    const data = await response.json() as {\n      candidates?: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }>\n    }\n    const audio = data.candidates?.[0]?.content?.parts?.find(part => part.inlineData)?.inlineData?.data\n    if (!audio)\n      throw new Error('Gemini TTS response missing audio data')\n\n    return new Response(toWavFromPCM16(decodeBase64(audio), 24000), {\n      status: 200,\n      headers: { 'Content-Type': 'audio/wav' },\n    })\n  }\n}\n\nexport const providerGoogleGeminiAudioSpeech = defineProvider<GoogleGeminiSpeechConfig>({\n  id: 'google-gemini-audio-speech',\n  name: 'Google Gemini',\n  nameLocalize: ({ t }) => t('settings.pages.providers.provider.google-gemini-audio-speech.title'),\n  description: 'aistudio.google.com',\n  descriptionLocalize: ({ t }) => t('settings.pages.providers.provider.google-gemini-audio-speech.description'),\n  tasks: ['text-to-speech', 'tts'],\n  icon: 'i-lobe-icons:gemini',\n  iconColor: 'i-lobe-icons:gemini-color',\n  createProviderConfig: () => googleGeminiSpeechConfigSchema,","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts#L88-L124","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// after a 2xx response, inspect candidates before assuming audio exists\nconst data = await response.json()\nconst candidate = data.candidates?.[0]\nif (!candidate || candidate.finishReason === 'SAFETY' || candidate.finishReason === 'RECITATION') {\n  // filtered; do not attempt to decode audio; surface a content-filter message\n}","typeGuard":"function hasGeminiAudio(data: unknown): data is { candidates: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }> } {\n  if (!data || typeof data !== 'object') return false\n  const parts = (data as any)?.candidates?.[0]?.content?.parts\n  return Array.isArray(parts) && parts.some((p: any) => p?.inlineData?.data)\n}","tryCatchPattern":"try {\n  const res = await provider.speech(model).fetch(url, { body: JSON.stringify(body) })\n}\ncatch (err) {\n  if (err instanceof Error && err.message === 'Gemini TTS response missing audio data') {\n    // likely safety filter; retry with rephrased/shorter input or choose another voice/model\n  }\n  else throw err\n}","preventionTips":["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)."],"tags":["gemini","tts","safety-filter","response-parsing","api-error"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}