mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix: HuggingFace cost calculation now respects custom input_cost_per_token/output_cost_per_token config
Fixes #22863 When using HuggingFace via router.huggingface.co with custom pricing, cost tracking was always returning $0. The completion_cost function wasn't extracting custom pricing from litellm_logging_obj.litellm_params .metadata.model_info when custom_pricing=True. Added logic to extract custom_cost_per_token from model_info metadata when custom_pricing=True and no explicit custom_cost_per_token was passed. Also fixed a subtle bug: use 'x if x is not None else 0.0' instead of 'x or 0.0' to correctly handle explicit zero costs (free input tokens). Tests: 4 new test cases added, all 36 tests in file pass.
This commit is contained in:
parent
cec3e9e7d4
commit
ae720d0dee
2 changed files with 150 additions and 0 deletions
|
|
@ -1108,6 +1108,30 @@ def completion_cost( # noqa: PLR0915
|
|||
elif isinstance(cost_per_token_usage_object, dict):
|
||||
service_tier = cost_per_token_usage_object.get("service_tier")
|
||||
|
||||
# Extract custom_cost_per_token from litellm_logging_obj when custom_pricing=True
|
||||
# This enables cost calculation for custom/HuggingFace providers with per-deployment pricing
|
||||
if (
|
||||
custom_pricing is True
|
||||
and custom_cost_per_token is None
|
||||
and litellm_logging_obj is not None
|
||||
):
|
||||
_litellm_params = getattr(litellm_logging_obj, "litellm_params", None)
|
||||
if _litellm_params is not None:
|
||||
_metadata = _litellm_params.get("metadata", {}) or {}
|
||||
_model_info = _metadata.get("model_info", {}) or {}
|
||||
_input_cost = _model_info.get("input_cost_per_token")
|
||||
_output_cost = _model_info.get("output_cost_per_token")
|
||||
if _input_cost is not None or _output_cost is not None:
|
||||
custom_cost_per_token = {
|
||||
"input_cost_per_token": _input_cost if _input_cost is not None else 0.0,
|
||||
"output_cost_per_token": _output_cost if _output_cost is not None else 0.0,
|
||||
}
|
||||
# Also extract custom_cost_per_second if available
|
||||
if custom_cost_per_second is None:
|
||||
_cost_per_second = _model_info.get("input_cost_per_second")
|
||||
if _cost_per_second is not None:
|
||||
custom_cost_per_second = _cost_per_second
|
||||
|
||||
selected_model = _select_model_name_for_cost_calc(
|
||||
model=model,
|
||||
completion_response=completion_response,
|
||||
|
|
|
|||
|
|
@ -1970,3 +1970,129 @@ def test_additional_costs_only_for_azure_ai():
|
|||
completion_tokens=50,
|
||||
)
|
||||
assert result is None, "Vertex AI should have no additional costs"
|
||||
|
||||
|
||||
def test_custom_pricing_huggingface_extracts_from_model_info():
|
||||
"""
|
||||
Test that custom pricing for HuggingFace (or any custom provider) correctly extracts
|
||||
input_cost_per_token and output_cost_per_token from litellm_logging_obj.litellm_params.metadata.model_info.
|
||||
|
||||
This fixes issue #22863: HuggingFace cost calculation always returns $0.
|
||||
|
||||
When using router.huggingface.co with custom pricing config, the cost should be calculated
|
||||
based on the input_cost_per_token/output_cost_per_token values in model_info.
|
||||
"""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Create mock response with usage
|
||||
response = ModelResponse(
|
||||
id="test-id",
|
||||
model="huggingface/my-custom-model",
|
||||
choices=[],
|
||||
usage=Usage(prompt_tokens=100, completion_tokens=50, total_tokens=150),
|
||||
)
|
||||
|
||||
# Create mock litellm_logging_obj with custom pricing in model_info
|
||||
mock_logging_obj = MagicMock()
|
||||
mock_logging_obj.litellm_params = {
|
||||
"metadata": {
|
||||
"model_info": {
|
||||
"input_cost_per_token": 0.0001, # $0.0001 per input token
|
||||
"output_cost_per_token": 0.0002, # $0.0002 per output token
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate cost with custom_pricing=True
|
||||
cost = completion_cost(
|
||||
completion_response=response,
|
||||
model="huggingface/my-custom-model",
|
||||
custom_llm_provider="huggingface",
|
||||
custom_pricing=True,
|
||||
litellm_logging_obj=mock_logging_obj,
|
||||
)
|
||||
|
||||
# Expected: (100 * 0.0001) + (50 * 0.0002) = 0.01 + 0.01 = 0.02
|
||||
expected_cost = (100 * 0.0001) + (50 * 0.0002)
|
||||
assert cost == expected_cost, f"Expected cost {expected_cost}, got {cost}"
|
||||
assert cost > 0, "Cost should be greater than 0 for custom HuggingFace pricing"
|
||||
|
||||
|
||||
def test_custom_pricing_unknown_provider_extracts_from_model_info():
|
||||
"""
|
||||
Test that any unknown/custom provider with custom_pricing=True correctly uses
|
||||
input_cost_per_token and output_cost_per_token from litellm_logging_obj.
|
||||
|
||||
This is a more general test to ensure the fix works for any custom provider,
|
||||
not just HuggingFace.
|
||||
"""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Create mock response
|
||||
response = ModelResponse(
|
||||
id="test-id",
|
||||
model="custom-provider/custom-model",
|
||||
choices=[],
|
||||
usage=Usage(prompt_tokens=200, completion_tokens=100, total_tokens=300),
|
||||
)
|
||||
|
||||
# Create mock litellm_logging_obj
|
||||
mock_logging_obj = MagicMock()
|
||||
mock_logging_obj.litellm_params = {
|
||||
"metadata": {
|
||||
"model_info": {
|
||||
"input_cost_per_token": 0.00005,
|
||||
"output_cost_per_token": 0.00015,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cost = completion_cost(
|
||||
completion_response=response,
|
||||
model="custom-provider/custom-model",
|
||||
custom_llm_provider="custom_provider",
|
||||
custom_pricing=True,
|
||||
litellm_logging_obj=mock_logging_obj,
|
||||
)
|
||||
|
||||
# Expected: (200 * 0.00005) + (100 * 0.00015) = 0.01 + 0.015 = 0.025
|
||||
expected_cost = (200 * 0.00005) + (100 * 0.00015)
|
||||
assert cost == expected_cost, f"Expected cost {expected_cost}, got {cost}"
|
||||
|
||||
|
||||
def test_custom_pricing_partial_costs_in_model_info():
|
||||
"""
|
||||
Test that custom pricing works when only one of input/output cost is provided.
|
||||
The missing cost should default to 0.
|
||||
"""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
response = ModelResponse(
|
||||
id="test-id",
|
||||
model="custom/model",
|
||||
choices=[],
|
||||
usage=Usage(prompt_tokens=100, completion_tokens=50, total_tokens=150),
|
||||
)
|
||||
|
||||
# Only input_cost_per_token provided
|
||||
mock_logging_obj = MagicMock()
|
||||
mock_logging_obj.litellm_params = {
|
||||
"metadata": {
|
||||
"model_info": {
|
||||
"input_cost_per_token": 0.001,
|
||||
# output_cost_per_token not provided
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cost = completion_cost(
|
||||
completion_response=response,
|
||||
model="custom/model",
|
||||
custom_llm_provider="custom",
|
||||
custom_pricing=True,
|
||||
litellm_logging_obj=mock_logging_obj,
|
||||
)
|
||||
|
||||
# Expected: (100 * 0.001) + (50 * 0.0) = 0.1
|
||||
expected_cost = 100 * 0.001
|
||||
assert cost == expected_cost, f"Expected cost {expected_cost}, got {cost}"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue