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

  1. Make your custom cache hook return None on miss and only yield cached values that are valid completion dicts.
  2. Validate/repair cached entries: ensure the stored JSON has 'choices' before treating it as a cache hit.
  3. Flush stale cache entries after upgrading litellm if the cached payload format changed.
  4. 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

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


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/b9329f3077275f9a. Report an issue: GitHub.