{"record":{"id":"541321fb5591ef01","repo":"BerriAI/litellm","slug":"usage-is-required-got-usage-of-type-type-usage","errorCode":null,"errorMessage":"usage is required, got={usage} of type {type(usage)}","messagePattern":"usage is required, got=(.+?) of type (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/litellm_logging.py","lineNumber":4940,"sourceCode":"            )\n\n        usage: Final = response_obj.get(\"usage\", None) or {}\n        if usage is None or (not isinstance(usage, dict) and not isinstance(usage, Usage)):\n            return Usage(\n                prompt_tokens=0,\n                completion_tokens=0,\n                total_tokens=0,\n            )\n        elif isinstance(usage, Usage):\n            return usage\n        elif isinstance(usage, ResponseAPIUsage):\n            return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage)\n        elif isinstance(usage, dict):\n            if ResponseAPILoggingUtils._is_response_api_usage(usage):\n                return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage)\n            return Usage(**usage)\n\n        raise ValueError(f\"usage is required, got={usage} of type {type(usage)}\")\n\n    @staticmethod\n    def get_usage_as_dict(\n        response_obj: dict | None,\n        combined_usage_object: Usage | None = None,\n    ) -> dict:\n        \"\"\"\n        Like get_usage_from_response_obj but returns a plain dict, skipping\n        the Pydantic Usage construction on the hot path.\n        \"\"\"\n        _empty: Final[dict] = {\"prompt_tokens\": 0, \"completion_tokens\": 0, \"total_tokens\": 0}\n        if combined_usage_object is not None:\n            return combined_usage_object.model_dump()\n        if not response_obj:\n            return _empty\n        _raw: Final = response_obj.get(\"usage\", None)\n        if _raw is None:\n            return _empty","sourceCodeStart":4922,"sourceCodeEnd":4958,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/litellm_core_utils/litellm_logging.py#L4922-L4958","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nfrom openai.types import CompletionUsage\nresp.usage = CompletionUsage(prompt_tokens=5, completion_tokens=7, total_tokens=12)\n# -> ValueError: usage is required, got=... of type <class 'openai.types.CompletionUsage'>\n\n# after\nresp.usage = {\"prompt_tokens\": 5, \"completion_tokens\": 7, \"total_tokens\": 12}\n# or: from litellm.types.utils import Usage; resp.usage = Usage(**raw.model_dump())","handlingStrategy":"type-guard","validationCode":"from litellm.types.utils import Usage\n\nif usage is not None and not isinstance(usage, (Usage, dict)):\n    usage = usage.model_dump() if hasattr(usage, \"model_dump\") else dict(usage)\n# hand usage (Usage | dict | None) to litellm logging/cost helpers","typeGuard":"from litellm.types.utils import Usage\n\ndef is_supported_usage(v: object) -> bool:\n    return v is None or isinstance(v, (Usage, dict))","tryCatchPattern":"try:\n    u = ResponseAPILoggingUtils.get_usage_from_response_obj(response_obj)\nexcept ValueError as e:\n    if \"usage is required\" in str(e):\n        u = None  # zero-usage fallback\n    else:\n        raise","preventionTips":["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"],"tags":["litellm","usage","logging","type-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}