{"record":{"id":"fa350d498f58d8bc","repo":"HKUDS/DeepTutor","slug":"no-audio-data-to-transcribe","errorCode":null,"errorMessage":"No audio data to transcribe.","messagePattern":"No audio data to transcribe\\.","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":306,"sourceCode":"class OpenAICompatSTTAdapter(BaseSTTAdapter):\n    \"\"\"POST ``{base}/audio/transcriptions``.\n\n    Multipart ``file`` upload by default; OpenRouter uses a base64-JSON body\n    (``request_style == \"base64_json\"``) sharing the same path.\n    \"\"\"\n\n    async def transcribe(\n        self,\n        audio: bytes,\n        config: STTConfig,\n        *,\n        filename: str = \"audio.webm\",\n        content_type: str = \"application/octet-stream\",\n    ) -> str:\n        if not config.base_url:\n            raise VoiceProviderError(\"No endpoint URL configured for STT.\")\n        if not audio:\n            raise VoiceProviderError(\"No audio data to transcribe.\")\n        url = join_audio_path(config.base_url, \"audio/transcriptions\")\n        auth = build_auth_headers(config.auth_style, config.api_key)\n\n        try:\n            async with httpx.AsyncClient(timeout=config.request_timeout) as client:\n                if config.request_style == STT_BASE64_JSON:\n                    resp = await self._post_base64(client, url, auth, audio, filename, config)\n                else:\n                    resp = await self._post_multipart(\n                        client, url, auth, audio, filename, content_type, config\n                    )\n        except httpx.HTTPError as exc:\n            raise VoiceProviderError(f\"STT request error: {exc}\") from exc\n        _raise_for_provider(resp, \"Transcription\")\n        return self._parse_text(resp)\n\n    async def _post_multipart(\n        self,","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L288-L324","documentation":"transcribe rejects empty audio input before making any request: if the bytes passed are empty (or a falsy value), VoiceProviderError(\"No audio data to transcribe.\") is raised. This prevents pointless provider round-trips and confusing downstream errors.","triggerScenarios":"Calling transcribe(b\"\", config) or transcribe with an empty buffer — e.g. a zero-length recording, a failed client-side capture, or an upload that produced no bytes.","commonSituations":"Browser mic permission granted but recording stopped instantly, WebSocket audio frame aggregation producing an empty blob, or a test passing an empty fixture.","solutions":["Check audio length before calling transcribe; skip or prompt the user to re-record","Fix the upstream capture path if recordings are systematically empty (permissions, codec, timing)","In tests, use a real small audio fixture"],"exampleFix":"// before\nif audio:\n    pass\ntext = await stt.transcribe(audio, cfg)\n// after\nif not audio:\n    return \"\"\ntext = await stt.transcribe(audio, cfg)","handlingStrategy":"validation","validationCode":"if not audio:\n    return \"\"  # or prompt user to re-record\ntext = await stt.transcribe(audio, stt_config)","typeGuard":"def has_audio_payload(audio: bytes | None) -> bool:\n    return bool(audio)","tryCatchPattern":null,"preventionTips":["Guard client-side: skip empty recordings before upload","Enforce a minimum recording duration in the mic capture UI"],"tags":["voice","stt","empty-input","validation"],"backgroundTag":"empty-request-payload","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}