{"record":{"id":"b08fe9b8d08b1bce","repo":"chenhg5/cc-connect","slug":"qwen-tts-empty-audio-url-in-response","errorCode":null,"errorMessage":"qwen tts: empty audio URL in response","messagePattern":"qwen tts: empty audio URL in response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/tts.go","lineNumber":176,"sourceCode":"\t}\n\n\tvar result struct {\n\t\tCode    string `json:\"code\"`\n\t\tMessage string `json:\"message\"`\n\t\tOutput  struct {\n\t\t\tAudio struct {\n\t\t\t\tURL string `json:\"url\"`\n\t\t\t} `json:\"audio\"`\n\t\t} `json:\"output\"`\n\t}\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"qwen tts: parse response: %w\", err)\n\t}\n\tif result.Code != \"\" {\n\t\treturn nil, \"\", fmt.Errorf(\"qwen tts API error %s: %s\", result.Code, result.Message)\n\t}\n\tif result.Output.Audio.URL == \"\" {\n\t\treturn nil, \"\", fmt.Errorf(\"qwen tts: empty audio URL in response\")\n\t}\n\n\t// Download WAV from temporary URL\n\taudioReq, err := http.NewRequestWithContext(ctx, http.MethodGet, result.Output.Audio.URL, nil)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"qwen tts: create download request: %w\", err)\n\t}\n\taudioResp, err := q.Client.Do(audioReq)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"qwen tts: download audio: %w\", err)\n\t}\n\tdefer audioResp.Body.Close()\n\n\twavData, err := io.ReadAll(audioResp.Body)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"qwen tts: read audio: %w\", err)\n\t}\n\treturn wavData, \"wav\", nil","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/tts.go#L158-L194","documentation":"QwenTTS.Synthesize (core/tts.go:176) calls the Alibaba DashScope multimodal-generation API, parses the JSON body, and requires output.audio.url to contain a temporary URL from which the WAV file can be downloaded. This error is thrown when the API returned HTTP 200 with a parsable body and no business error code, yet the audio URL field is empty — i.e. the response shape did not carry the expected result.","triggerScenarios":"DashScope returns 200 with a JSON body whose output.audio.url is missing or empty: e.g. the configured model does not return audio URLs (wrong model name for this endpoint), the API changed its response schema, a partial success was returned, or a proxy/gateway answered 200 with a non-TTS JSON body.","commonSituations":"Configuring QwenTTS with a model other than a qwen-tts family model (e.g. a text-only qwen model), pointing BaseURL at a compatible-but-different endpoint that returns audio inline as base64 instead of a URL, or DashScope API schema drift.","solutions":["Verify the configured model is a DashScope TTS model (default qwen3-tts-flash) that returns output.audio.url for this endpoint","Log the raw response body on this error to inspect what output.audio actually contained","Check BaseURL points at the multimodal-generation endpoint, not another DashScope service","Update the client if DashScope changed the response schema; if the API now returns base64 audio, adapt parsing to that shape"],"exampleFix":"// before\nif result.Output.Audio.URL == \"\" {\n    return nil, \"\", fmt.Errorf(\"qwen tts: empty audio URL in response\")\n}\n// after\nif result.Output.Audio.URL == \"\" {\n    return nil, \"\", fmt.Errorf(\"qwen tts: empty audio URL in response: body=%s\", string(body))\n}","handlingStrategy":"try-catch","validationCode":"// No pre-call validation possible; verify provider/model config at startup:\nif ttsCfg.Provider == \"qwen\" && !strings.HasPrefix(ttsCfg.Model, \"qwen\") && ttsCfg.Model != \"\" {\n    log.Printf(\"warning: model %q may not return an audio URL from the DashScope multimodal endpoint\", ttsCfg.Model)\n}","typeGuard":"func hasAudioURL(body []byte) bool {\n    var r struct { Output struct { Audio struct { URL string `json:\"url\"` } `json:\"audio\"` } `json:\"output\"` }\n    if err := json.Unmarshal(body, &r); err != nil { return false }\n    return r.Output.Audio.URL != \"\"\n}","tryCatchPattern":"audio, format, err := tts.Synthesize(ctx, text, opts)\nif err != nil {\n    if strings.Contains(err.Error(), \"empty audio URL\") {\n        // fall back to a secondary TTS provider or notify user\n        return fallbackSynthesize(ctx, text)\n    }\n    return err\n}","preventionTips":["Pin the model to a qwen-tts family model that returns audio URLs","Log raw API responses at debug level so schema drift is visible immediately","Pin/verify BaseURL against the official DashScope multimodal-generation endpoint"],"tags":["tts","http","api-response","qwen","dashscope"],"backgroundTag":"empty-api-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}