mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
fix(openrouter): read /images usage when image token details are missing or null
The /images usage schema allows null for completion_tokens_details and image_tokens, and per-image priced models report only completion_tokens. A null completion_tokens_details raised AttributeError and a null image_tokens failed ImageUsage validation after the image was already generated and billed, so the caller got an error and no spend was logged Those fields are now read null-safe, and output_tokens falls back to completion_tokens when image_tokens is absent. usage.cost is read as before
This commit is contained in:
parent
89bae53233
commit
4ba841c4d9
2 changed files with 36 additions and 5 deletions
|
|
@ -106,11 +106,13 @@ class OpenRouterImageGenerationConfig(BaseImageGenerationConfig):
|
|||
"""
|
||||
usage_data: Final = response_json.get("usage", {})
|
||||
if usage_data:
|
||||
prompt_tokens: Final = usage_data.get("prompt_tokens", 0)
|
||||
total_tokens: Final = usage_data.get("total_tokens", 0)
|
||||
# The /images usage schema allows null for completion_tokens_details and image_tokens, and
|
||||
# per-image priced models report only completion_tokens
|
||||
prompt_tokens: Final = usage_data.get("prompt_tokens") or 0
|
||||
total_tokens: Final = usage_data.get("total_tokens") or 0
|
||||
|
||||
completion_tokens_details: Final = usage_data.get("completion_tokens_details", {})
|
||||
image_tokens: Final = completion_tokens_details.get("image_tokens", 0)
|
||||
completion_tokens_details: Final = usage_data.get("completion_tokens_details") or {}
|
||||
image_tokens: Final = completion_tokens_details.get("image_tokens")
|
||||
|
||||
model_response.usage = ImageUsage(
|
||||
input_tokens=prompt_tokens,
|
||||
|
|
@ -118,7 +120,7 @@ class OpenRouterImageGenerationConfig(BaseImageGenerationConfig):
|
|||
image_tokens=0, # Input doesn't contain images for generation
|
||||
text_tokens=prompt_tokens,
|
||||
),
|
||||
output_tokens=image_tokens,
|
||||
output_tokens=image_tokens if image_tokens is not None else usage_data.get("completion_tokens") or 0,
|
||||
total_tokens=total_tokens,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -297,6 +297,35 @@ def test_transform_response_reports_openrouter_usage_and_cost():
|
|||
assert response._hidden_params["model"] == IMAGE_ONLY_MODEL
|
||||
|
||||
|
||||
# The ImageGenerationUsage schema in https://openrouter.ai/openapi.json (2026-10-03) requires only
|
||||
# prompt_tokens, completion_tokens and total_tokens, allows null for completion_tokens_details and
|
||||
# image_tokens, and its example for a per-image priced model has no completion_tokens_details
|
||||
PER_IMAGE_USAGE: Final = {"prompt_tokens": 0, "completion_tokens": 4175, "total_tokens": 4175, "cost": 0.04}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"usage",
|
||||
[
|
||||
PER_IMAGE_USAGE,
|
||||
{**PER_IMAGE_USAGE, "completion_tokens_details": None},
|
||||
{**PER_IMAGE_USAGE, "completion_tokens_details": {"image_tokens": None}},
|
||||
],
|
||||
ids=["no-details", "null-details", "null-image-tokens"],
|
||||
)
|
||||
def test_transform_response_without_image_tokens_reports_completion_tokens_and_cost(usage: dict[str, object]):
|
||||
response = _transform_response(
|
||||
httpx.Response(200, json={"created": 1790994427, "data": [{"b64_json": "aW1hZ2Ux"}], "usage": usage})
|
||||
)
|
||||
|
||||
assert response.usage == ImageUsage(
|
||||
input_tokens=0,
|
||||
input_tokens_details=ImageUsageInputTokensDetails(image_tokens=0, text_tokens=0),
|
||||
output_tokens=usage["completion_tokens"],
|
||||
total_tokens=usage["total_tokens"],
|
||||
)
|
||||
assert response._hidden_params["additional_headers"] == {"llm_provider-x-litellm-response-cost": usage["cost"]}
|
||||
|
||||
|
||||
def test_transform_response_with_non_json_body_raises_openrouter_exception():
|
||||
with pytest.raises(OpenRouterException, match="Error parsing OpenRouter response") as exc_info:
|
||||
_transform_response(httpx.Response(502, content=b"<html>bad gateway</html>"))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue