jeecgboot/JeecgBoot · error · RuntimeException
TTS API调用失败,状态码: {statusCode},{errorBody}
Error message
TTS API调用失败,状态码: {statusCode},{errorBody} What it means
VoiceApiHelper.generateAudio() POSTs an OpenAI-compatible request to {apiHost}/audio/speech. Any HTTP status other than 200 is thrown as a RuntimeException that embeds both the status code and the raw upstream error body. The request uses Bearer apiKey auth with a configurable timeout.
Source
Thrown at jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/voice/util/VoiceApiHelper.java:83
body.put("voice", voice);
body.put("speed", speed);
body.put("response_format", "wav");
log.info("TTS请求: url={}, model={}, voice={}, speed={}, textLength={}", url, config.getModel(), voice, speed, text.length());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + config.getApiKey())
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toJSONString()))
.timeout(Duration.ofSeconds(config.getTimeout()))
.build();
HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() != 200) {
String errorBody = new String(response.body().readAllBytes());
log.error("TTS API调用失败: status={}, body={}", response.statusCode(), errorBody);
throw new RuntimeException("TTS API调用失败,状态码: " + response.statusCode() + "," + errorBody);
}
try (InputStream is = response.body()) {
Files.copy(is, audioPath, StandardCopyOption.REPLACE_EXISTING);
}
log.info("TTS语音已生成: {}", audioPath);
}
}
View on GitHub (pinned to 96fb33f5ec)
Solutions
- Read the status code and errorBody baked into the message -- they identify the failure class.
- 401/403 -> correct/regenerate the apiKey in aiChatConfig.aiModelVoice.
- 429 -> add backoff/throttling or raise provider quota; retry with jitter.
- 404/400 -> verify apiHost base URL and that model + voice exist on the provider.
- Confirm the provider actually exposes the /audio/speech endpoint.
Defensive patterns
Strategy: retry
Validate before calling
// before calling VoiceApiHelper, sanity-check config
AiChatConfig.VoiceModelConfig c = aiChatConfig.getAiModelVoice();
if (oConvertUtils.isEmpty(c.getApiHost()) || oConvertUtils.isEmpty(c.getApiKey())) {
throw new IllegalStateException("TTS apiHost/apiKey 未配置");
} Try / catch
// retry only transient (5xx/429) failures; fail fast on 4xx auth errors
try {
voiceApiHelper.generateAudio(text, audioPath, voice, speed);
} catch (RuntimeException e) {
String msg = e.getMessage();
if (msg.contains("状态码: 429") || msg.contains("状态码: 5")) {
// backoff and retry once
} else {
throw e;
}
} Prevention
- Store apiKey via a secrets manager, never in plain yml.
- Validate apiHost ends with '/' handling the url-join logic already in place.
- Add a small test request at deploy time to confirm 200.
When it happens
Trigger: 401 = apiKey invalid/revoked; 403/region blocked; 404 = apiHost or model wrong; 429 = rate/quota exceeded; 400 = unsupported voice/model/response_format; 5xx = provider outage. Also a missing/blank apiHost produces a malformed URL.
Common situations: apiKey expired or copied with whitespace; apiHost missing trailing handling mismatch; model name not provisioned on the account; provider rate limiting under load; apiHost pointing at a non-OpenAI base that lacks /audio/speech.
Related errors
AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14).
Data as JSON: /api/errors/0ce5db59d2587a66.
Report an issue: GitHub.