{"record":{"id":"804a9bd54d9da026","repo":"HKUDS/DeepTutor","slug":"transcription-response-had-no-text-field","errorCode":null,"errorMessage":"Transcription response had no `text` field.","messagePattern":"Transcription response had no `text` field\\.","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":376,"sourceCode":"        headers = {\"Content-Type\": \"application/json\", **auth, **(config.extra_headers or {})}\n        return await client.post(url, headers=headers, json=body)\n\n    @staticmethod\n    def _parse_text(resp: httpx.Response) -> str:\n        content_type = resp.headers.get(\"content-type\", \"\")\n        if \"json\" in content_type:\n            data = resp.json()\n            if isinstance(data, dict):\n                text = data.get(\"text\")\n                if isinstance(text, str):\n                    return text.strip()\n                # OpenRouter/chat-style fallback.\n                choices = data.get(\"choices\")\n                if isinstance(choices, list) and choices:\n                    message = (choices[0] or {}).get(\"message\") or {}\n                    if isinstance(message.get(\"content\"), str):\n                        return message[\"content\"].strip()\n            raise VoiceProviderError(\"Transcription response had no `text` field.\")\n        # response_format=text returns a bare string.\n        return (resp.text or \"\").strip()\n\n\n__all__ = [\"OpenAICompatTTSAdapter\", \"OpenRouterTTSAdapter\", \"OpenAICompatSTTAdapter\"]\n","sourceCodeStart":358,"sourceCodeEnd":382,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L358-L382","documentation":"_parse_text inspects the transcription response: it expects a JSON body with a \"text\" field (or, as a fallback, a choices[0].message.content string for chat-style endpoints). If neither is present, VoiceProviderError is raised. Note that response_format=text (bare string body) skips this check.","triggerScenarios":"The provider returned 200 with JSON lacking \"text\" — e.g. an error payload, a different schema (some gateways return {transcript: ...}), or an HTML login/error page parsed as JSON.","commonSituations":"Non-OpenAI-compatible STT gateway whose response schema differs, a proxy returning an HTML error page with 200, or a chat-completions endpoint used for STT whose reply has empty content.","solutions":["Log/inspect resp text to see the actual schema returned","If the gateway uses a different field name, switch to an OpenAI-compatible endpoint or wrap it with an adapter that maps the field","Ensure response_format is set so the provider returns the expected shape","Verify the URL actually points to a transcriptions endpoint, not a captive/login page"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    text = await stt.transcribe(audio, stt_config)\nexcept VoiceProviderError as exc:\n    if \"no `text` field\" in str(exc):\n        log.error(\"Unexpected STT response schema — check gateway compatibility\")\n    raise","preventionTips":["Use OpenAI-compatible transcriptions endpoints","Smoke-test gateways once when onboarding them","Set response_format explicitly so providers return the expected shape"],"tags":["voice","stt","response-schema","parsing"],"backgroundTag":"unexpected-response-schema","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}