mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(anthropic cost): apply regional geo uplift to cached tokens
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
1d3b64c66f
commit
dc58c35bba
2 changed files with 117 additions and 14 deletions
|
|
@ -24,9 +24,10 @@ def _compute_cache_only_cost(model_info: "ModelInfo", usage: "Usage", service_ti
|
|||
"""
|
||||
Return only the cache-related portion of the prompt cost (cache read + cache write).
|
||||
|
||||
These costs must NOT be scaled by geo/speed multipliers because the old
|
||||
These costs must NOT be scaled by the ``fast`` speed multiplier because the old
|
||||
explicit ``fast/`` model entries carried unchanged cache rates while
|
||||
multiplying only the regular input/output token costs.
|
||||
multiplying only the regular input/output token costs. Regional pricing, by
|
||||
contrast, uplifts every token type, so the geo multiplier does scale them.
|
||||
"""
|
||||
if usage.prompt_tokens_details is None:
|
||||
return 0.0
|
||||
|
|
@ -81,20 +82,23 @@ def cost_per_token(model: str, usage: "Usage", service_tier: str | None = None)
|
|||
model_info: Final = litellm.get_model_info(model=model, custom_llm_provider="anthropic")
|
||||
provider_specific_entry: Final[dict] = model_info.get("provider_specific_entry") or {}
|
||||
|
||||
multiplier = 1.0
|
||||
if (
|
||||
hasattr(usage, "inference_geo")
|
||||
and usage.inference_geo
|
||||
and usage.inference_geo.lower() not in ["global", "not_available"]
|
||||
):
|
||||
multiplier *= provider_specific_entry.get(usage.inference_geo.lower(), 1.0)
|
||||
if hasattr(usage, "speed") and usage.speed == "fast":
|
||||
multiplier *= provider_specific_entry.get("fast", 1.0)
|
||||
geo_multiplier: Final = (
|
||||
provider_specific_entry.get(usage.inference_geo.lower(), 1.0)
|
||||
if getattr(usage, "inference_geo", None) and usage.inference_geo.lower() not in ("global", "not_available")
|
||||
else 1.0
|
||||
)
|
||||
speed_multiplier: Final = (
|
||||
provider_specific_entry.get("fast", 1.0) if getattr(usage, "speed", None) == "fast" else 1.0
|
||||
)
|
||||
|
||||
if multiplier != 1.0:
|
||||
if speed_multiplier != 1.0:
|
||||
cache_cost: Final = _compute_cache_only_cost(model_info=model_info, usage=usage, service_tier=service_tier)
|
||||
prompt_cost = (prompt_cost - cache_cost) * multiplier + cache_cost
|
||||
completion_cost *= multiplier
|
||||
prompt_cost = (prompt_cost - cache_cost) * speed_multiplier + cache_cost
|
||||
completion_cost *= speed_multiplier
|
||||
|
||||
if geo_multiplier != 1.0:
|
||||
prompt_cost *= geo_multiplier
|
||||
completion_cost *= geo_multiplier
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -2726,6 +2726,105 @@ def test_anthropic_cost_per_token_prices_cache_at_served_tier_with_multiplier():
|
|||
assert completion_cost == pytest.approx(expected_completion)
|
||||
|
||||
|
||||
def _register_anthropic_geo_cache_model(model: str) -> None:
|
||||
litellm.register_model(
|
||||
model_cost={
|
||||
model: {
|
||||
"input_cost_per_token": 5e-6,
|
||||
"output_cost_per_token": 25e-6,
|
||||
"cache_creation_input_token_cost": 6.25e-6,
|
||||
"cache_read_input_token_cost": 0.5e-6,
|
||||
"litellm_provider": "anthropic",
|
||||
"max_tokens": 8192,
|
||||
"provider_specific_entry": {"us": 1.1, "fast": 2.0},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_anthropic_geo_multiplier_applies_to_cache_tokens():
|
||||
"""
|
||||
Regression: the regional (geo) uplift must scale cache read and cache write
|
||||
cost too, not just non-cache input and output.
|
||||
|
||||
Anthropic's regional surcharge applies to every token type, so a cache-heavy
|
||||
row (nearly all cache-creation tokens) must still come in 10% above the
|
||||
global-priced row. Before the fix the uplift was applied only to the
|
||||
non-cache portion, so cache-heavy spend was under-reported by ~10%.
|
||||
"""
|
||||
from litellm.llms.anthropic.cost_calculation import (
|
||||
cost_per_token as anthropic_cost_per_token,
|
||||
)
|
||||
from litellm.types.utils import PromptTokensDetailsWrapper, Usage
|
||||
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
model = "claude-test-geo-cache-model"
|
||||
_register_anthropic_geo_cache_model(model)
|
||||
|
||||
def make_usage() -> "Usage":
|
||||
return Usage(
|
||||
prompt_tokens=1_000_000,
|
||||
completion_tokens=500,
|
||||
total_tokens=1_000_500,
|
||||
prompt_tokens_details=PromptTokensDetailsWrapper(
|
||||
cached_tokens=200_000,
|
||||
cache_creation_tokens=799_800,
|
||||
),
|
||||
)
|
||||
|
||||
base_usage = make_usage()
|
||||
base_prompt_cost, base_completion_cost = anthropic_cost_per_token(model=model, usage=base_usage)
|
||||
|
||||
geo_usage = make_usage()
|
||||
geo_usage.inference_geo = "us"
|
||||
geo_prompt_cost, geo_completion_cost = anthropic_cost_per_token(model=model, usage=geo_usage)
|
||||
|
||||
expected_base_prompt = 200 * 5e-6 + 200_000 * 0.5e-6 + 799_800 * 6.25e-6
|
||||
assert base_prompt_cost == pytest.approx(expected_base_prompt)
|
||||
assert geo_prompt_cost == pytest.approx(expected_base_prompt * 1.1)
|
||||
assert geo_completion_cost == pytest.approx(base_completion_cost * 1.1)
|
||||
|
||||
|
||||
def test_anthropic_geo_and_fast_multipliers_compose():
|
||||
"""
|
||||
The ``fast`` speed multiplier stays cache-exclusive (the old explicit
|
||||
``fast/`` entries kept base cache rates) while the geo multiplier scales the
|
||||
whole cost, so a fast + regional row prices as
|
||||
``((non_cache * fast) + cache) * geo``.
|
||||
"""
|
||||
from litellm.llms.anthropic.cost_calculation import (
|
||||
cost_per_token as anthropic_cost_per_token,
|
||||
)
|
||||
from litellm.types.utils import PromptTokensDetailsWrapper, Usage
|
||||
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
model = "claude-test-geo-fast-cache-model"
|
||||
_register_anthropic_geo_cache_model(model)
|
||||
|
||||
usage = Usage(
|
||||
prompt_tokens=10_000,
|
||||
completion_tokens=500,
|
||||
total_tokens=10_500,
|
||||
prompt_tokens_details=PromptTokensDetailsWrapper(
|
||||
cached_tokens=2_000,
|
||||
cache_creation_tokens=6_000,
|
||||
),
|
||||
)
|
||||
usage.inference_geo = "us"
|
||||
usage.speed = "fast"
|
||||
|
||||
prompt_cost, completion_cost = anthropic_cost_per_token(model=model, usage=usage)
|
||||
|
||||
cache_cost = 2_000 * 0.5e-6 + 6_000 * 6.25e-6
|
||||
non_cache_cost = 2_000 * 5e-6
|
||||
assert prompt_cost == pytest.approx((non_cache_cost * 2.0 + cache_cost) * 1.1)
|
||||
assert completion_cost == pytest.approx(500 * 25e-6 * 2.0 * 1.1)
|
||||
|
||||
|
||||
def test_gemini_cache_tokens_details_no_negative_values():
|
||||
"""
|
||||
Test for Issue #18750: Negative text_tokens with Gemini caching
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue