spring-projects/spring-ai · warning

No speech response returned for prompt: + prompt

Error message

No speech response returned for prompt: + prompt

What it means

Warning logged by OpenAiAudioSpeechModel.call() when the speech synthesis request produced zero audio bytes (audioBytes.length == 0). The model returns a TextToSpeechResponse containing one Speech with an empty byte array rather than throwing, so callers can silently end up with empty audio files.

Source

Thrown at models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioSpeechModel.java:148

		SpeechCreateParams params = buildSpeechCreateParams(mergedOptions, inputText, false);

		RequestOptions requestOptions = this.buildRequestOptions(mergedOptions);

		HttpResponse httpResponse = this.openAiClient.audio().speech().create(params, requestOptions);
		Headers headers = httpResponse.headers();

		byte[] audioBytes;
		try (InputStream inputStream = httpResponse.body()) {
			audioBytes = inputStream.readAllBytes();
		}
		catch (IOException e) {
			throw new RuntimeException("Failed to read audio speech response", e);
		}

		if (audioBytes.length == 0) {
			if (logger.isWarnEnabled()) {
				logger.warn("No speech response returned for prompt: " + prompt);
			}
			return new TextToSpeechResponse(List.of(new Speech(new byte[0])));
		}

		Speech speech = new Speech(audioBytes);
		OpenAiAudioSpeechResponseMetadata metadata = OpenAiAudioSpeechResponseMetadata.from(headers);

		return new TextToSpeechResponse(List.of(speech), metadata);
	}

	@Override
	public Flux<TextToSpeechResponse> stream(TextToSpeechPrompt prompt) {
		Assert.notNull(prompt, "Prompt must not be null");

		// Lets openSpeechStream/emitNextChunk tell a deliberate cancellation
		// apart from a genuine I/O failure.
		AtomicBoolean cancelled = new AtomicBoolean(false);

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Verify OPENAI_API_KEY and billing/quota status for the audio endpoints.
  2. Validate speech options (voice, model, speed, response format) against current OpenAI supported values.
  3. Check the raw HTTP response via an interceptor; an error body may be collapsed into empty bytes.
  4. Treat zero-length Speech audio as a failure in application code and retry or alert.

Example fix

// before
byte[] audio = speechModel.call(new SpeechPrompt(text, opts)).getResult().getOutput();
Files.write(path, audio); // creates empty file
// after
if (audio == null || audio.length == 0) throw new IllegalStateException("OpenAI TTS returned no audio for prompt");
Files.write(path, audio);
Defensive patterns

Strategy: try-catch

Validate before calling

// validate options before calling
Assert.notNull(options.getVoice(), "voice is required");
Assert.hasText(prompt.getText(), "prompt text is required");

Type guard

static boolean hasAudio(SpeechPromptResult r) {
    return r != null && r.getSpeeches() != null && r.getSpeeches().stream().anyMatch(s -> s.getAudio() != null && s.getAudio().length > 0);
}

Try / catch

byte[] audio = speechModel.call(prompt).getResult().getOutput();
if (audio == null || audio.length == 0) throw new IllegalStateException("OpenAI TTS produced no audio");

Prevention

When it happens

Trigger: The OpenAI /audio/speech endpoint returns an empty body (read fully into a zero-length byte[]); IOException during reading is separate and thrown as RuntimeException. Triggered when the API returns 2xx with no payload or the response stream is empty.

Common situations: Invalid OpenAI API key or billing issues returning unusual empty responses; unsupported/invalid voice or model option values; proxies/gateways stripping response bodies; OpenAI service incidents.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/e8518c44d3a31b5b. Report an issue: GitHub.