fix(cost_calculator): respect custom pricing for known providers like anthropic

When custom_llm_provider is set to a known provider (e.g., "anthropic")
and the user configures explicit zero costs (input_cost_per_token: 0.0),
cost_per_token() dispatched directly to the provider-specific function
(anthropic_cost_per_token) before checking custom pricing entries. This
caused the zero-cost override to be ignored and real provider pricing to
be applied instead.

- Add custom_pricing parameter to cost_per_token() and pass it from
  completion_cost()
- When custom_pricing=True and model is in model_cost, bypass the
  provider dispatch chain and use generic_cost_per_token() directly
- Fix zero-cost check in else branch: use `is not None` instead of
  `or 0.0 > 0` which collapsed 0.0 to False

Fixes #25204

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MEgooneh 2026-04-06 03:22:34 +03:30
parent d251238bd7
commit bc8be27ab2
2 changed files with 82 additions and 2 deletions

View file

@ -283,6 +283,7 @@ def cost_per_token( # noqa: PLR0915
audio_transcription_file_duration: float = 0.0, # for audio transcription calls - the file time in seconds
### SERVICE TIER ###
service_tier: Optional[str] = None, # for OpenAI service tier pricing
custom_pricing: Optional[bool] = None, # whether model uses custom pricing from router/config
response: Optional[Any] = None,
### REQUEST MODEL ###
request_model: Optional[str] = None, # original request model for router detection
@ -475,6 +476,13 @@ def cost_per_token( # noqa: PLR0915
else None
),
)
elif custom_pricing is True and model in model_cost_ref:
return generic_cost_per_token(
model=model,
usage=usage_block,
custom_llm_provider=custom_llm_provider or "",
service_tier=service_tier,
)
elif custom_llm_provider == "vertex_ai":
cost_router = google_cost_router(
model=model_without_prefix,
@ -545,8 +553,8 @@ def cost_per_token( # noqa: PLR0915
)
if (
(model_info.get("input_cost_per_token") or 0.0) > 0
or (model_info.get("output_cost_per_token") or 0.0) > 0
model_info.get("input_cost_per_token") is not None
or model_info.get("output_cost_per_token") is not None
):
return generic_cost_per_token(
model=model,
@ -1509,6 +1517,7 @@ def completion_cost( # noqa: PLR0915
audio_transcription_file_duration=audio_transcription_file_duration,
rerank_billed_units=rerank_billed_units,
service_tier=service_tier,
custom_pricing=custom_pricing,
response=completion_response,
request_model=request_model_for_cost,
)

View file

@ -447,6 +447,77 @@ def test_per_request_custom_pricing_with_router():
assert "gpt-3.5-turbo" in selected
def test_zero_cost_custom_pricing_with_anthropic_provider():
"""
When a user sets input_cost_per_token=0.0 and output_cost_per_token=0.0
with custom_llm_provider="anthropic" via the router, the zero-cost
override must be respected. The provider-specific dispatch should
be bypassed so that real Anthropic pricing is NOT used.
Regression test for https://github.com/BerriAI/litellm/issues/25204
"""
from litellm import Router
router = Router(
model_list=[
{
"model_name": "free-claude",
"litellm_params": {
"model": "anthropic/claude-sonnet-4-20250514",
"api_key": "test_api_key",
},
"model_info": {
"id": "zero-cost-model-id",
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
},
},
]
)
result = router.completion(
model="free-claude",
messages=[{"role": "user", "content": "Hello!"}],
mock_response="Hi there!",
)
assert result._hidden_params["response_cost"] == 0.0
def test_cost_per_token_custom_pricing_bypasses_provider_dispatch():
"""
cost_per_token with custom_pricing=True should use generic_cost_per_token
instead of the provider-specific dispatch, ensuring custom pricing entries
(including zero costs) are respected.
Regression test for https://github.com/BerriAI/litellm/issues/25204
"""
from litellm.cost_calculator import cost_per_token
custom_model_id = "custom-zero-cost-anthropic"
litellm.register_model(
model_cost={
custom_model_id: {
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
"litellm_provider": "anthropic",
"max_tokens": 8192,
}
}
)
prompt_cost, completion_cost_val = cost_per_token(
model=custom_model_id,
prompt_tokens=1000,
completion_tokens=500,
custom_llm_provider="anthropic",
custom_pricing=True,
)
assert prompt_cost == 0.0
assert completion_cost_val == 0.0
def test_azure_realtime_cost_calculator():
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
litellm.model_cost = litellm.get_model_cost_map(url="")