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:
shivam 2026-07-27 23:43:21 +00:00 • committed by Devin AI
parent 1d3b64c66f
commit dc58c35bba
2 changed files with 117 additions and 14 deletions

View file

@ -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

View file

@ -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