Hmbown/CodeWhale · error · anyhow::Error

Speech model cannot be empty

Error message

Speech model cannot be empty

What it means

synthesize_speech preconditions: the request named an empty speech model. Speech synthesis on the Xiaomi MiMo TTS surface requires an exact non-empty TTS model id, so a blank model is rejected before any provider call.

Solutions

  1. Set the speech model in the request (e.g. the xiaomi voice model you intend to use).
  2. Set the speech model in config.toml / the relevant config key so defaults populate it.
  3. If invoking via CLI/command, pass the model argument explicitly.

Example fix

// before
synthesize(SpeechSynthesisRequest { model: "".into(), .. }).await?;

// after
synthesize(SpeechSynthesisRequest { model: "milivo-speech-02-turbo".into(), .. }).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn validate_speech_request(req: &SpeechSynthesisRequest) -> Result<(), String> {
    if req.model.trim().is_empty() { return Err("model must be set".into()); }
    if req.text.trim().is_empty() { return Err("text must be set".into()); }
    Ok(())
}

Try / catch

match result {
    Err(e) if e.to_string() == "Speech model cannot be empty" => {
        eprintln!("Set a speech model via config or --model flag");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling speech synthesis with `SpeechSynthesisRequest { model: "" }` or model consisting only of whitespace.

Common situations: A CLI/default where the speech model config key was never set; a slash command invoked without the model argument; config parse yielding an empty string.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/64b50651c93342ce. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/client.rs:3332

    /// MiMo's TTS chat-completions surface expects that shape. The optional
    /// `instruction` is a `user` message that controls style, voice design, or
    /// voice-clone performance and is not spoken verbatim.
    pub async fn synthesize_speech(
        &self,
        request: SpeechSynthesisRequest,
    ) -> Result<SpeechSynthesisResponse> {
        let _inference = self.acquire_remote_control_inference_permit().await;
        let _permit = self.acquire_provider_request_permit().await;
        if self.api_provider != crate::config::ApiProvider::XiaomiMimo {
            anyhow::bail!(
                "speech synthesis requires provider 'xiaomi-mimo' (current: {})",
                self.api_provider.as_str()
            );
        }

        let model = request.model.trim().to_string();
        if model.is_empty() {
            anyhow::bail!("Speech model cannot be empty");
        }
        let text = request.text.trim().to_string();
        if text.is_empty() {
            anyhow::bail!("Speech text cannot be empty");
        }

        let audio_format = normalize_audio_format(&request.audio_format);
        let model = self.wire_model_for_route(&model);
        let model_lower = model.to_ascii_lowercase();
        let instruction = request
            .instruction
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty());
        let voice = request
            .voice
            .as_deref()
            .map(str::trim)

View on GitHub (pinned to 73e0f67d83)