{"record":{"id":"3378d1922020acdb","repo":"BerriAI/litellm","slug":"litellm-provider-returned-a-response-with-no-cho","errorCode":null,"errorMessage":"LiteLLM: provider returned a response with no 'choices'. Raw keys: {list(response_object.keys())}","messagePattern":"LiteLLM: provider returned a response with no 'choices'\\. Raw keys: (.+?)","errorType":"http","errorClass":"APIError","httpStatus":500,"severity":"error","filePath":"litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py","lineNumber":185,"sourceCode":"        ModelResponse: The converted streaming response object.\n\n    Returns:\n        None\n    \"\"\"\n    if response_object is None:\n        raise Exception(\"Error in response object format\")\n\n    model_response_object: Final = ModelResponseStream()\n\n    if model_response_object is None:\n        raise Exception(\"Error in response creating model response object\")\n\n    choice_list: Final[list[StreamingChoices]] = []\n\n    if not response_object.get(\"choices\"):\n        from litellm.exceptions import APIError\n\n        raise APIError(\n            status_code=500,\n            message=(\n                f\"LiteLLM: provider returned a response with no 'choices'. Raw keys: {list(response_object.keys())}\"\n            ),\n            llm_provider=\"\",\n            model=\"\",\n        )\n\n    for idx, choice in enumerate(response_object[\"choices\"]):\n        if (\n            choice[\"message\"].get(\"tool_calls\", None) is not None\n            and isinstance(choice[\"message\"][\"tool_calls\"], list)\n            and len(choice[\"message\"][\"tool_calls\"]) > 0\n            and isinstance(choice[\"message\"][\"tool_calls\"][0], dict)\n        ):\n            pydantic_tool_calls = []\n            for index, t in enumerate(choice[\"message\"][\"tool_calls\"]):\n                if \"index\" not in t:","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py#L167-L203","documentation":"While converting a provider dict into a streaming ModelResponse, litellm found the response_object had no non-empty 'choices' key and raised APIError(500) with the raw key list included. This surfaces when a provider (or cache/middleware) returns a well-formed JSON dict that is not a chat completion — e.g. an error payload, moderation payload, or empty object. The message intentionally echoes response_object.keys() so you can identify what the payload actually was.","triggerScenarios":"A provider returns {'error': {...}} with HTTP 200; a cached embedding response is replayed into a chat stream path; middleware rewrites the response body and drops 'choices'; Azure/OpenAI-compatible gateways return empty {} on partial failures.","commonSituations":"Custom API gateways or mock servers that don't include 'choices'; provider outages returning JSON error bodies without error status codes; response-translation bugs in custom handlers; cache collisions between different response types under the same key.","solutions":["Inspect the 'Raw keys' in the message to identify what the payload actually is (error body, empty dict, other response type).","If a custom provider/handler produced it, ensure its translation layer emits {'choices': [...]} for chat completions.","Check provider status pages and retry — some providers emit non-choices JSON on transient faults.","If caching is enabled, verify cache keys don't collide across response types and cached values are chat-shaped."],"exampleFix":"// before (mock/edge server)\nreturn JsonResponse({'id': 'x', 'output': 'hi'})\n\n# after\nreturn JsonResponse({'id': 'x', 'choices': [{'index': 0, 'message': {'role': 'assistant', 'content': 'hi'}, 'finish_reason': 'stop'}]})","handlingStrategy":"try-catch","validationCode":"def provider_dict_is_chat_completion(d: dict) -> bool:\n    return bool(d.get('choices'))","typeGuard":null,"tryCatchPattern":"from litellm.exceptions import APIError\ntry:\n    stream = litellm.completion(**params, stream=True)\nexcept APIError as e:\n    if \"no 'choices'\" in str(e):\n        logging.error('provider sent non-completion payload: %s', e.message)\n    raise","preventionTips":["Point mocks and gateways at the OpenAI chat-completion schema (choices required).","Log raw provider payloads at debug level in custom handlers to catch shape drift early.","Monitor for this error per provider — it often flags provider incidents."],"tags":["streaming","provider-response","choices-missing","api-error"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}