BerriAI/litellm · error · ValueError

usage is required, got={usage} of type {type(usage)}

Error message

usage is required, got={usage} of type {type(usage)}

What it means

ResponseAPILoggingUtils.get_usage_from_response_obj normalizes the usage field and accepts only a Usage object, an OpenAI Responses-API ResponseAPIUsage object, or a plain dict. Any other type (None that isn't pre-handled, a string, a pydantic model from another library, an arbitrary object) reaches the terminal raise. The error exists to prevent silent zero-usage logging when callers pass malformed usage payloads.

Source

Thrown at litellm/litellm_core_utils/litellm_logging.py:4934

            )

        usage: Final = response_obj.get("usage", None) or {}
        if usage is None or (not isinstance(usage, dict) and not isinstance(usage, Usage)):
            return Usage(
                prompt_tokens=0,
                completion_tokens=0,
                total_tokens=0,
            )
        elif isinstance(usage, Usage):
            return usage
        elif isinstance(usage, ResponseAPIUsage):
            return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage)
        elif isinstance(usage, dict):
            if ResponseAPILoggingUtils._is_response_api_usage(usage):
                return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage)
            return Usage(**usage)

        raise ValueError(f"usage is required, got={usage} of type {type(usage)}")

    @staticmethod
    def get_usage_as_dict(
        response_obj: dict | None,
        combined_usage_object: Usage | None = None,
    ) -> dict:
        """
        Like get_usage_from_response_obj but returns a plain dict, skipping
        the Pydantic Usage construction on the hot path.
        """
        _empty: Final[dict] = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
        if combined_usage_object is not None:
            return combined_usage_object.model_dump()
        if not response_obj:
            return _empty
        _raw: Final = response_obj.get("usage", None)
        if _raw is None:
            return _empty

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Convert before logging: pass usage as a plain dict (Usage(**d) is constructed for you) or a litellm.Usage instance
  2. In provider adapters, transform provider-specific usage into Usage/ResponseAPIUsage before the response reaches logging
  3. In tests, replace MagicMocks for usage with real Usage objects or dicts

Example fix

# before
usage = '{"prompt_tokens": 1}'          # str -> ValueError

# after
usage = {"prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3}
# or
from litellm import Usage
usage = Usage(prompt_tokens=1, completion_tokens=2, total_tokens=3)
Defensive patterns

Strategy: type-guard

Validate before calling

from litellm import Usage

def normalize_usage(u):
    if isinstance(u, Usage) or isinstance(u, dict):
        return u
    if u is None:
        return {'prompt_tokens': 0, 'completion_tokens': 0, 'total_tokens': 0}
    raise TypeError(f'unsupported usage type: {type(u)}')

Type guard

from litellm import Usage

def is_supported_usage(u) -> bool:
    return isinstance(u, (Usage, dict))  # ResponseAPIUsage also accepted upstream

Prevention

When it happens

Trigger: Custom callbacks/handlers (or downstream code overriding response objects) passing usage as something other than Usage/ResponseAPIUsage/dict — e.g. usage=None bypassing the falsy branch because an earlier branch already consumed the empty case, usage as a JSON string, or a provider-specific usage model not yet transformed.

Common situations: Adding new providers whose raw usage objects are forwarded untransformed; mocking responses in tests with usage as a string or MagicMock; adapter code copying usage between response types.

Related errors


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