BerriAI/litellm · error · APIError
LiteLLM: provider returned a response with no 'choices'. Raw
Error message
LiteLLM: provider returned a response with no 'choices'. Raw keys: {list(response_object.keys())} What it means
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.
Source
Thrown at litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py:185
ModelResponse: The converted streaming response object.
Returns:
None
"""
if response_object is None:
raise Exception("Error in response object format")
model_response_object: Final = ModelResponseStream()
if model_response_object is None:
raise Exception("Error in response creating model response object")
choice_list: Final[list[StreamingChoices]] = []
if not response_object.get("choices"):
from litellm.exceptions import APIError
raise APIError(
status_code=500,
message=(
f"LiteLLM: provider returned a response with no 'choices'. Raw keys: {list(response_object.keys())}"
),
llm_provider="",
model="",
)
for idx, choice in enumerate(response_object["choices"]):
if (
choice["message"].get("tool_calls", None) is not None
and isinstance(choice["message"]["tool_calls"], list)
and len(choice["message"]["tool_calls"]) > 0
and isinstance(choice["message"]["tool_calls"][0], dict)
):
pydantic_tool_calls = []
for index, t in enumerate(choice["message"]["tool_calls"]):
if "index" not in t:View on GitHub (pinned to 6c2dcb801b)
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.
Example fix
// before (mock/edge server)
return JsonResponse({'id': 'x', 'output': 'hi'})
# after
return JsonResponse({'id': 'x', 'choices': [{'index': 0, 'message': {'role': 'assistant', 'content': 'hi'}, 'finish_reason': 'stop'}]}) Defensive patterns
Strategy: try-catch
Validate before calling
def provider_dict_is_chat_completion(d: dict) -> bool:
return bool(d.get('choices')) Try / catch
from litellm.exceptions import APIError
try:
stream = litellm.completion(**params, stream=True)
except APIError as e:
if "no 'choices'" in str(e):
logging.error('provider sent non-completion payload: %s', e.message)
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Braintrust API error: {e.response.text}
- Failed to connect to Braintrust API: {str(e)}
- Failed to transform Braintrust response: {str(e)}
- api_base is required for Pydantic AI agents
- Unexpected responses stream payload
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/3378d1922020acdb.
Report an issue: GitHub.