{"record":{"id":"1da40bb57bf09a20","repo":"zylon-ai/private-gpt","slug":"remote-detokenize-response-did-not-contain-a-suppo","errorCode":null,"errorMessage":"Remote detokenize response did not contain a supported text field","messagePattern":"Remote detokenize response did not contain a supported text field","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"private_gpt/components/llm/tokenizers/remote.py","lineNumber":353,"sourceCode":"    def _extract_tokens(payload: Any) -> list[int]:\n        if isinstance(payload, dict):\n            tokens = payload.get(\"tokens\")\n            if isinstance(tokens, list) and all(\n                isinstance(token, int) for token in tokens\n            ):\n                return tokens\n        raise ValueError(\n            \"Remote tokenizer response did not contain a valid 'tokens' field\"\n        )\n\n    @staticmethod\n    def _extract_text(payload: Any) -> str:\n        if isinstance(payload, dict):\n            for key in (\"content\", \"text\"):\n                value = payload.get(key)\n                if isinstance(value, str):\n                    return value\n        raise ValueError(\n            \"Remote detokenize response did not contain a supported text field\"\n        )\n","sourceCodeStart":335,"sourceCodeEnd":356,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/llm/tokenizers/remote.py#L335-L356","documentation":"Raised by RemoteTokenizeTokenizer._extract_text while parsing a detokenize response. The accepted contract is a dict with either a 'content' or 'text' key holding a string (checked in that order). If both are absent, hold non-string values, or the payload is not a dict, this ValueError fires. Like error 145 it signals an API contract mismatch with the remote tokenizer service on the detokenize direction.","triggerScenarios":"Calling the remote detokenize path and receiving JSON without 'content'/'text' (e.g. {'decoded': \"...\"}), with a non-string value (null, number), or a top-level list/string instead of an object; endpoint returning an error body with HTTP 200.","commonSituations":"Custom tokenizer server using a different field name; server upgrade changing the response schema; URL pointing to a tokenize-only endpoint; gateways injecting wrapper objects around the real payload.","solutions":["Make the detokenize endpoint return {\"content\": \"...\"} or {\"text\": \"...\"} with a string value.","Inspect the raw response with curl to find the actual field name and align server/client.","Return proper HTTP error statuses from the server so failures do not masquerade as malformed success payloads.","If the field is nested (e.g. {'result': {'text': ...}}), unwrap it server-side or at the proxy."],"exampleFix":"# before (server returns)\n{\"decoded\": \"hello world\"}  # ValueError: no supported text field\n\n# after (server returns)\n{\"content\": \"hello world\"}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_valid_text_payload(payload: Any) -> bool:\n    return isinstance(payload, dict) and isinstance(\n        payload.get('content', payload.get('text')), str)","tryCatchPattern":"try:\n    text = await tok.adetokenize(ids)\nexcept ValueError as e:\n    if 'text field' in str(e):\n        log_payload(); raise","preventionTips":["Pin the tokenizer service version next to the client so response-schema drift is caught in upgrades.","Return {'content': str} from detokenize and cover it with an integration test."],"tags":["network","api-contract","tokenizer","json"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}