BerriAI/litellm · error · ValueError

usage is required, got=

Error message

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

What it means

The usage-normalization step of response logging (get_usage_from_response_obj) accepts only: a falsy value (None -> zero-usage Usage), a litellm Usage object, an OpenAI Responses-API ResponseAPIUsage (auto-transformed), or a plain dict (Usage(**usage)). Any other object raises this ValueError with the value and type. The classic trap is passing a pydantic usage object from the openai SDK package (openai.types.CompletionUsage) - it duck-types identically but is a different class than litellm's Usage.

Solutions

  1. Convert SDK objects to a dict: usage.model_dump() (or Usage(**openai_usage.model_dump()) for a litellm Usage)
  2. json.loads() the string first if usage was serialized as JSON text
  3. Pass None to get the zero-usage object when usage is genuinely unavailable
  4. In custom provider adapters, always emit usage as a plain dict with prompt_tokens/completion_tokens/total_tokens

Example fix

# before
from openai.types import CompletionUsage
resp.usage = CompletionUsage(prompt_tokens=5, completion_tokens=7, total_tokens=12)
# -> ValueError: usage is required, got=... of type <class 'openai.types.CompletionUsage'>

# after
resp.usage = {"prompt_tokens": 5, "completion_tokens": 7, "total_tokens": 12}
# or: from litellm.types.utils import Usage; resp.usage = Usage(**raw.model_dump())
Defensive patterns

Strategy: type-guard

Validate before calling

from litellm.types.utils import Usage

if usage is not None and not isinstance(usage, (Usage, dict)):
    usage = usage.model_dump() if hasattr(usage, "model_dump") else dict(usage)
# hand usage (Usage | dict | None) to litellm logging/cost helpers

Type guard

from litellm.types.utils import Usage

def is_supported_usage(v: object) -> bool:
    return v is None or isinstance(v, (Usage, dict))

Try / catch

try:
    u = ResponseAPILoggingUtils.get_usage_from_response_obj(response_obj)
except ValueError as e:
    if "usage is required" in str(e):
        u = None  # zero-usage fallback
    else:
        raise

Prevention

When it happens

Trigger: Building synthetic or adapter responses for tests/custom providers and handing get_usage_from_response_obj an openai.types.CompletionUsage, a JSON string, an int, or some other package's usage class instead of a litellm Usage or dict.

Common situations: Migrating openai-SDK code to litellm and reusing openai usage objects in custom logging hooks; rehydrating cached responses where usage was serialized to a str; different litellm/openai package versions exporting distinct Usage classes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at litellm/litellm_core_utils/litellm_logging.py:4940

            )

        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 77b7c6c40c)