BerriAI/litellm · error · Exception
Error in response object format
Error message
Error in response object format
What it means
convert_to_streaming_response (used for replaying cached responses as streams) raises this generic Exception when response_object is None. The function then goes on to build ModelResponseStream chunks from response_object['choices'], so a None input cannot be processed. It is a defensive precondition check at the top of the converter in convert_dict_to_response.py.
Source
Thrown at litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py:173
response_object: dict | None = None,
):
"""
Asynchronously converts a response object to a streaming response.
Args:
response_object (Optional[dict]): The response object to be converted. Defaults to None.
Raises:
Exception: If the response object is None.
Yields:
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="",View on GitHub (pinned to 6c2dcb801b)
Solutions
- Make your custom cache hook return None on miss and only yield cached values that are valid completion dicts.
- Validate/repair cached entries: ensure the stored JSON has 'choices' before treating it as a cache hit.
- Flush stale cache entries after upgrading litellm if the cached payload format changed.
- If calling the converter directly, always pass a populated response_object dict.
Example fix
// before (custom logger cache hook)
cached = await self.async_get_cache(**kwargs)
return cached # may be None
# after
cached = await self.async_get_cache(**kwargs)
return cached if isinstance(cached, dict) and cached.get('choices') else None Defensive patterns
Strategy: validation
Validate before calling
def is_replayable_cache_hit(cached) -> bool:
return isinstance(cached, dict) and bool(cached.get('choices')) Try / catch
try:
for chunk in convert_to_streaming_response(response_object=cached):
yield chunk
except Exception as e:
if 'response object format' in str(e):
cache.delete(key); yield from real_stream() # bypass cache
else:
raise Prevention
- Custom cache hooks must return None on miss; never forward None as a hit.
- Validate cached dicts have 'choices' before replaying them as streams.
- Flush cache after litellm upgrades that change stored payload formats.
When it happens
Trigger: Returning a cache hit with stream=True where the cached value deserializes to None; custom caching hooks (CustomLogger.async_get_cache) returning a falsy/malformed entry that gets forwarded as None; calling convert_to_streaming_response() with no arguments (the parameter defaults to None).
Common situations: Redis cache storing empty values or wrong keys; a custom cache implementation that returns None on miss but the caller treats it as a hit; corrupted cache entries after a serialization format change between litellm versions.
Related errors
- Braintrust API error: {e.response.text}
- Failed to connect to Braintrust API: {str(e)}
- LLM client cache lazy import: unknown attribute {name!r}
- api_base is required for Pydantic AI agents
- cache key is None
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/b9329f3077275f9a.
Report an issue: GitHub.