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
- Convert SDK objects to a dict: usage.model_dump() (or Usage(**openai_usage.model_dump()) for a litellm Usage)
- json.loads() the string first if usage was serialized as JSON text
- Pass None to get the zero-usage object when usage is genuinely unavailable
- 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
- Standardize on litellm Usage or plain dicts at every custom-logger boundary
- Call .model_dump() on openai-SDK objects before reuse inside litellm
- json.loads() serialized usage before passing it back into logging helpers
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
- end_time is required, got=
- start_time is required, got=
- api base needs to be a string. api_base=
- 'argilla_transformation_object' must be a dictionary, to…
- completion_response must be of type ImageResponse for…
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 _emptyView on GitHub (pinned to 77b7c6c40c)