{"record":{"id":"95e8abb4217258a6","repo":"sipeed/picoclaw","slug":"transcription-request-failed-w","errorCode":null,"errorMessage":"transcription request failed: %w","messagePattern":"transcription request failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/audio/asr/audio_model_transcriber.go","lineNumber":81,"sourceCode":"\tif err != nil {\n\t\tlogger.ErrorCF(\"voice\", \"Failed to detect audio format\", map[string]any{\"path\": audioFilePath, \"error\": err})\n\t\treturn nil, err\n\t}\n\n\tresp, err := t.provider.Chat(ctx, []providers.Message{\n\t\t{\n\t\t\tRole:    \"user\",\n\t\t\tContent: t.prompt,\n\t\t\tMedia: []string{\n\t\t\t\tfmt.Sprintf(\"data:audio/%s;base64,%s\", format, base64.StdEncoding.EncodeToString(audioBytes)),\n\t\t\t},\n\t\t},\n\t}, nil, t.modelID, map[string]any{\n\t\t\"temperature\": 0,\n\t})\n\tif err != nil {\n\t\tlogger.ErrorCF(\"voice\", \"Audio model transcription request failed\", map[string]any{\"error\": err})\n\t\treturn nil, fmt.Errorf(\"transcription request failed: %w\", err)\n\t}\n\n\ttext := strings.TrimSpace(resp.Content)\n\tlogger.InfoCF(\"voice\", \"Audio model transcription completed successfully\", map[string]any{\n\t\t\"text_length\":           len(text),\n\t\t\"transcription_preview\": utils.Truncate(text, 50),\n\t})\n\n\treturn &TranscriptionResponse{Text: text}, nil\n}\n\nfunc (t *AudioModelTranscriber) Name() string {\n\treturn \"audio-model\"\n}\n","sourceCodeStart":63,"sourceCodeEnd":96,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/audio/asr/audio_model_transcriber.go#L63-L96","documentation":"Thrown when the underlying provider.Chat call fails while sending the base64-encoded audio (data:audio/<fmt>;base64,... media attachment) to the configured chat model. This wraps whatever error the providers layer produced: HTTP transport failure, non-2xx API response, model rejection of audio input, or context cancellation. The error is wrapped with %w so errors.As/Is can reach provider-specific error types.","triggerScenarios":"provider.Chat(ctx, messages, nil, t.modelID, {temperature: 0}) returns an error: network/DNS/TLS failure to the LLM endpoint; 401/403 from an invalid or missing API key; 404 when modelID does not exist; 4xx when the model does not accept audio media parts (only OpenAI-compatible audio models work); 413 when the base64 payload exceeds the gateway limit; ctx cancelled or deadline exceeded.","commonSituations":"Model config points at a text-only model (e.g. plain gpt-4o instead of an audio-capable variant); API key env var not exported in the service environment; very long recordings inflate the base64 payload past a reverse-proxy body limit; user cancels the turn mid-request.","solutions":["Inspect the wrapped error with errors.As for the provider's HTTP error type to get status code and response body.","Verify the model configured for this transcriber actually accepts audio input media (check supportsAudioTranscription in pkg/audio/asr/asr.go and the model list).","Confirm the API key/base URL in the model config is valid for that protocol.","For transient transport errors (timeout, connection reset), retry with backoff; for 4xx do not retry.","For long audio, switch to ElevenLabsTranscriber or WhisperTranscriber which upload raw files instead of base64 chat payloads."],"exampleFix":"// before\nresp, err := t.provider.Chat(ctx, msgs, nil, t.modelID, opts)\nif err != nil { return nil, err }\n\n// after (caller side, with retry on transient failures)\nvar httpErr *api.HTTPError\nif errors.As(err, &httpErr) && httpErr.StatusCode >= 500 && httpErr.StatusCode < 600 {\n    // transient upstream failure: safe to retry\n    return retryWithBackoff(ctx, func() (*TranscriptionResponse, error) {\n        return transcriber.Transcribe(ctx, path)\n    })\n}","handlingStrategy":"retry","validationCode":"// Before first use: smoke-test the chat provider with a tiny payload.\nif _, err := provider.Chat(ctx, []providers.Message{{Role: \"user\", Content: \"ping\"}}, nil, modelID, nil); err != nil {\n    return fmt.Errorf(\"audio transcriber backend unhealthy: %w\", err)\n}","typeGuard":"func isRetryableProviderErr(err error) bool {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() { return true }\n    if errors.Is(err, context.DeadlineExceeded) { return true }\n    var httpErr *api.HTTPError\n    return errors.As(err, &httpErr) && httpErr.StatusCode >= 500 && httpErr.StatusCode < 600\n}","tryCatchPattern":"resp, err := transcriber.Transcribe(ctx, path)\nif err != nil {\n    if isRetryableProviderErr(err) {\n        resp, err = backoff.Retry(ctx, 3, time.Second, func() (*asr.TranscriptionResponse, error) {\n            return transcriber.Transcribe(ctx, path)\n        })\n    }\n    if err != nil { return fmt.Errorf(\"audio-model transcription failed: %w\", err) }\n}","preventionTips":["Pick an audio-capable model (OpenAI-compatible multimodal) when constructing the transcriber.","Keep base64 payload small: downsample to 16kHz mono before transcription.","Set a caller ctx deadline larger than expected transcription time to distinguish cancel from failure."],"tags":["network","api","llm","audio","transcription","retry","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}