mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
Merge bdaf38e361 into e9b1c11098
This commit is contained in:
commit
01c7853df7
4 changed files with 167 additions and 5 deletions
|
|
@ -2070,6 +2070,7 @@ def completion_cost(
|
|||
call_type=call_type,
|
||||
model_info=_deployment_model_info(litellm_logging_obj, custom_pricing, router_model_id),
|
||||
vertex_location=vertex_location,
|
||||
data_residency=data_residency,
|
||||
)
|
||||
elif call_type in _VIDEO_CALL_TYPES:
|
||||
### VIDEO GENERATION COST CALCULATION ###
|
||||
|
|
|
|||
|
|
@ -2104,6 +2104,7 @@ def calculate_image_response_cost_from_usage(
|
|||
custom_llm_provider: str,
|
||||
model_info: ModelInfo | None = None,
|
||||
vertex_location: str | None = None,
|
||||
data_residency: str | None = None,
|
||||
) -> float | None:
|
||||
"""
|
||||
Calculate image generation cost from usage metadata when available.
|
||||
|
|
@ -2188,6 +2189,7 @@ def calculate_image_response_cost_from_usage(
|
|||
custom_llm_provider=custom_llm_provider,
|
||||
model_info=model_info,
|
||||
vertex_location=vertex_location,
|
||||
data_residency=data_residency,
|
||||
)
|
||||
return prompt_cost + completion_cost
|
||||
|
||||
|
|
@ -2295,6 +2297,7 @@ class CostCalculatorUtils:
|
|||
call_type: str | None = None,
|
||||
model_info: ModelInfo | None = None,
|
||||
vertex_location: str | None = None,
|
||||
data_residency: str | None = None,
|
||||
) -> float:
|
||||
"""
|
||||
Route the image generation cost calculator based on the custom_llm_provider
|
||||
|
|
@ -2420,6 +2423,7 @@ class CostCalculatorUtils:
|
|||
image_response=completion_response,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
model_info=pricing,
|
||||
data_residency=data_residency,
|
||||
)
|
||||
# Fall through to default for DALL-E models
|
||||
return default_image_cost_calculator(
|
||||
|
|
|
|||
|
|
@ -21,8 +21,13 @@ def cost_calculator(
|
|||
image_response: ImageResponse,
|
||||
custom_llm_provider: str | None = None,
|
||||
model_info: ModelInfo | None = None,
|
||||
data_residency: str | None = None,
|
||||
) -> float:
|
||||
"""Calculate cost for OpenAI gpt-image models (token-based pricing)."""
|
||||
"""Calculate cost for OpenAI gpt-image models (token-based pricing).
|
||||
|
||||
``data_residency`` carries the OpenAI regional-processing region (e.g. "eu",
|
||||
"us") so the per-model uplift multiplier applies exactly like the chat path.
|
||||
"""
|
||||
provider: Final = custom_llm_provider or "openai"
|
||||
price_table: Final = (
|
||||
None
|
||||
|
|
@ -38,7 +43,11 @@ def cost_calculator(
|
|||
# A chat Usage with an explicit output breakdown: cost via generic_cost_per_token.
|
||||
if isinstance(usage, Usage) and usage.completion_tokens_details is not None:
|
||||
prompt_cost, completion_cost = generic_cost_per_token(
|
||||
model=model, usage=usage, custom_llm_provider=provider, model_info=price_table
|
||||
model=model,
|
||||
usage=usage,
|
||||
custom_llm_provider=provider,
|
||||
model_info=price_table,
|
||||
data_residency=data_residency,
|
||||
)
|
||||
return prompt_cost + completion_cost
|
||||
|
||||
|
|
@ -48,7 +57,11 @@ def cost_calculator(
|
|||
# does not itemize output and splitting text/image when it does.
|
||||
if getattr(usage, "input_tokens", None) is not None:
|
||||
token_based_cost: Final = calculate_image_response_cost_from_usage(
|
||||
model=model, image_response=image_response, custom_llm_provider=provider, model_info=price_table
|
||||
model=model,
|
||||
image_response=image_response,
|
||||
custom_llm_provider=provider,
|
||||
model_info=price_table,
|
||||
data_residency=data_residency,
|
||||
)
|
||||
if token_based_cost is not None:
|
||||
return token_based_cost
|
||||
|
|
@ -57,7 +70,11 @@ def cost_calculator(
|
|||
# cost via generic_cost_per_token (text rate) instead of returning 0.0.
|
||||
if isinstance(usage, Usage):
|
||||
prompt_cost, completion_cost = generic_cost_per_token(
|
||||
model=model, usage=usage, custom_llm_provider=provider, model_info=price_table
|
||||
model=model,
|
||||
usage=usage,
|
||||
custom_llm_provider=provider,
|
||||
model_info=price_table,
|
||||
data_residency=data_residency,
|
||||
)
|
||||
return prompt_cost + completion_cost
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ gpt-image-1 uses token-based pricing:
|
|||
- Image Output: $40.00/1M tokens
|
||||
"""
|
||||
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
|
|
@ -18,6 +20,7 @@ from litellm.types.utils import (
|
|||
ImageResponse,
|
||||
ImageUsage,
|
||||
ImageUsageInputTokensDetails,
|
||||
ModelInfo,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -91,7 +94,6 @@ class TestGPTImageCostRouting:
|
|||
"""Test that gpt-image models are properly routed to the token-based calculator"""
|
||||
|
||||
|
||||
|
||||
class TestGPTImage15OutputImageTokens:
|
||||
"""
|
||||
Test for GitHub issue #19508:
|
||||
|
|
@ -120,3 +122,141 @@ class TestGPTImage2OutputImageTokensNoBreakdown:
|
|||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
||||
|
||||
class TestGPTImageDataResidencyUplift:
|
||||
"""
|
||||
Test for GitHub issue #46407:
|
||||
gpt-image cost ignored the OpenAI regional-processing (data residency) uplift.
|
||||
|
||||
The chat path applies ``regional_processing_uplift_multiplier_<region>`` from
|
||||
the resolved price table inside ``generic_cost_per_token``; the image route
|
||||
never received ``data_residency``, so the uplift could never reach gpt-image
|
||||
calls. These tests pin the uplift on the image path using a price table that
|
||||
carries the multiplier key (same mechanism the chat path uses with cost-map
|
||||
entries).
|
||||
"""
|
||||
|
||||
_MODEL_INFO: Final[ModelInfo] = {
|
||||
"input_cost_per_token": 5e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"input_cost_per_image_token": 1e-05,
|
||||
"output_cost_per_image_token": 4e-05,
|
||||
"regional_processing_uplift_multiplier_eu": 1.1,
|
||||
"regional_processing_uplift_multiplier_us": 1.1,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _image_response() -> ImageResponse:
|
||||
return ImageResponse(
|
||||
created=1234567890,
|
||||
data=[ImageObject(url="http://example.com/image.jpg")],
|
||||
usage=ImageUsage(
|
||||
input_tokens=1000,
|
||||
input_tokens_details=ImageUsageInputTokensDetails(image_tokens=600, text_tokens=400),
|
||||
output_tokens=500,
|
||||
total_tokens=1500,
|
||||
),
|
||||
)
|
||||
|
||||
def test_data_residency_uplift_applies_to_gpt_image_cost(self) -> None:
|
||||
from litellm.llms.openai.image_generation.cost_calculator import cost_calculator
|
||||
|
||||
base: Final = cost_calculator(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=self._MODEL_INFO,
|
||||
)
|
||||
eu: Final = cost_calculator(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=self._MODEL_INFO,
|
||||
data_residency="eu",
|
||||
)
|
||||
assert base > 0
|
||||
assert abs(eu - base * 1.1) < 1e-12
|
||||
|
||||
def test_data_residency_uplift_via_calculate_image_response_cost_from_usage(self) -> None:
|
||||
from litellm.litellm_core_utils.llm_cost_calc.utils import calculate_image_response_cost_from_usage
|
||||
|
||||
base: Final = calculate_image_response_cost_from_usage(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=self._MODEL_INFO,
|
||||
)
|
||||
eu: Final = calculate_image_response_cost_from_usage(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=self._MODEL_INFO,
|
||||
data_residency="eu",
|
||||
)
|
||||
assert base is not None and eu is not None
|
||||
assert abs(eu - base * 1.1) < 1e-12
|
||||
|
||||
def test_no_uplift_when_model_carries_no_multiplier(self) -> None:
|
||||
from litellm.llms.openai.image_generation.cost_calculator import cost_calculator
|
||||
|
||||
model_info: Final[ModelInfo] = {
|
||||
"input_cost_per_token": 5e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"input_cost_per_image_token": 1e-05,
|
||||
"output_cost_per_image_token": 4e-05,
|
||||
}
|
||||
base: Final = cost_calculator(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=model_info,
|
||||
)
|
||||
eu: Final = cost_calculator(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=model_info,
|
||||
data_residency="eu",
|
||||
)
|
||||
assert eu == base
|
||||
|
||||
def test_no_uplift_when_data_residency_absent(self) -> None:
|
||||
from litellm.llms.openai.image_generation.cost_calculator import cost_calculator
|
||||
|
||||
none_region: Final = cost_calculator(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=self._MODEL_INFO,
|
||||
data_residency=None,
|
||||
)
|
||||
base: Final = cost_calculator(
|
||||
model="gpt-image-1",
|
||||
image_response=self._image_response(),
|
||||
custom_llm_provider="openai",
|
||||
model_info=self._MODEL_INFO,
|
||||
)
|
||||
assert none_region == base
|
||||
|
||||
def test_completion_cost_threads_data_residency_to_image_route(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""End-to-end: completion_cost's image branch must hand data_residency to the route."""
|
||||
from litellm.cost_calculator import completion_cost
|
||||
|
||||
monkeypatch.setitem(litellm.model_cost["gpt-image-1"], "regional_processing_uplift_multiplier_eu", 1.1)
|
||||
|
||||
base: Final = completion_cost(
|
||||
completion_response=self._image_response(),
|
||||
model="gpt-image-1",
|
||||
custom_llm_provider="openai",
|
||||
call_type="image_generation",
|
||||
)
|
||||
eu: Final = completion_cost(
|
||||
completion_response=self._image_response(),
|
||||
model="gpt-image-1",
|
||||
custom_llm_provider="openai",
|
||||
call_type="image_generation",
|
||||
data_residency="eu",
|
||||
)
|
||||
assert base > 0
|
||||
assert abs(eu - base * 1.1) < 1e-12
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue