fix(cost): bill the requested model's rates when the response names an unpriced model (#48900)

* fix(cost): bill the requested model's rates when the response names an unpriced model

Fireworks answers chat and /v1/messages calls with a display name such as
"Qwen 3.8 Max" in the response's model field. The cost path priced that
name, the Fireworks calculator fell back to its free default bucket without
raising, and the call billed $0 even though the requested model has a row.

When the response model resolves to no cost-map row or rule that declares
rates and the requested model does, price from the requested model. A priced
response model still wins, so dated snapshots keep their own rates, and an
unpriced requested model such as a router alias leaves the result unchanged.

* perf(cost): check the exact cost-map row before generalization rules when picking the priced model

* fix(cost): apply the priced fallback to the provider response model streaming records

* perf(cost): skip the served model's rule lookup when the requested model has no rates

* fix(cost): price the requested model without a model arg and keep Fireworks size buckets behind the proxy

When completion_cost runs with no model, the requested model now comes from the
call's hidden litellm_model_name, so a display-name response still bills at the
requested model's rates. A Fireworks model whose name carries a parameter size
counts as priced through its size bucket.

The Fireworks per-token lookup now treats a cost-map row that prices nothing, like
the empty row the Router registers for an unlisted deployment, as missing, so an
unlisted Fireworks model bills at its size bucket instead of $0 even when Fireworks
echoes its slug. The "row prices anything" helpers move to llm_cost_calc/utils so
the Fireworks lookup can reuse them without a circular import.

* fix(fireworks): keep prices from a matching fallback rule for an unlisted model

An unlisted Fireworks model that only a fallback generalization rule prices
has no raw cost map row, so the empty-row check sent it to the size bucket
and dropped the rule's rates. The Fireworks lookup now keeps the model info
when the row or a matching rule declares a rate.

* fix(cost): price a bare response model under the requested provider and drop the unlisted Fireworks bucket change

The logging payload picks the pricing model without a provider, so a bare echo
like mistral-large-latest failed its price check and the requested alias took
over model_map_key. The served name is now priced under the requested model's
provider, and an echo that is the requested name minus its provider prefix
skips the checks.

Treating the Router's empty price row as missing let unlisted Fireworks
deployments bill from a name guess, which misprices dedicated deployment ids
and routes some short names to another provider's rates. That change and its
tests go back to the merge base, so unlisted Fireworks models keep billing $0
behind the proxy as on main.

* perf(cost): read the response model's price row without building full model info

An unpriced display name like Qwen 3.8 Max made the price check build full
model info, and Fireworks' supported params lookup scans the whole cost map
for an unknown name. The row check now resolves the cost map key through the
cached model info helper, so that pick takes about 0.45 ms instead of several.

A regional test covers a Bedrock call whose response names an unpriced display
name with no model passed, which bills the requested model's regional row.
The duplicate get_model_info wrapper goes, since its twin already does the
same thing.

* fix(cost): keep the served model's filled-in and size-bucket rates, and cache misses

A served row priced only by a fill_missing_for_providers rule bills those
rates on main, but the price check read only the raw row and billed the
requested alias instead. It now merges the rule's fill-in keys through the
helper model info lookup uses.

A served Fireworks or Together AI id that names its parameter count keeps its
size-bucket price, as on main, instead of switching to the requested row.

The served model is checked first, cost map misses are cached and cleared with
the model info cache, and the rules are read only for a name that resolved
through one. A priced snapshot behind an Azure deployment name takes about
19 us of CPU time against 7 us on the merge base, down from about 300 us.

* fix(cost): bill the requested model's listed rates over a served size-bucket guess

A served Fireworks or Together AI name that is not in the cost map but names
its parameter count, like a display name with a size in it, now bills at the
requested model's listed rates instead of the provider's size-bucket price.
The bucket price still applies when the requested model has no rates, as on
main

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
norkelbyte 2026-10-29 19:19:36 -07:00 • committed by GitHub
parent a7030a5a2c
commit 44cd858697
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 430 additions and 35 deletions

View file

@ -153,7 +153,9 @@ from litellm.utils import (
ProviderConfigManager,
TextCompletionResponse,
TranscriptionResponse,
cached_cost_map_key,
cached_get_model_info_helper,
mapped_model_fill_missing,
model_info_with_pricing_overrides,
runtime_model_cost_registrations,
token_counter,
@ -962,21 +964,17 @@ def select_model_name_for_cost_calc(
"""
1. If custom pricing is true, return received model name
2. If base_model is set (e.g. for azure models), return that
3. If completion response has model set return that
3. If completion response has model set return that, unless only the requested model's cost-map row has rates
4. Check if model is passed in return that
"""
return_model: str | None = None
custom_llm_provider = _get_provider_for_cost_calc(model=model, custom_llm_provider=custom_llm_provider)
completion_response_model: str | None = None
if completion_response is not None:
if isinstance(completion_response, BaseModel):
completion_response_model = getattr(completion_response, "model", None)
elif isinstance(completion_response, dict):
completion_response_model = completion_response.get("model", None)
completion_response_model: Final = _get_response_model(completion_response)
hidden_params: Final[dict | None] = getattr(completion_response, "_hidden_params", None)
provider_response_model: Final = _get_hidden_str_for_cost_calc(hidden_params, "provider_response_model")
requested_model: Final = model or _get_hidden_str_for_cost_calc(hidden_params, "litellm_model_name")
explicit_pricing: Final = custom_pricing is True or base_model is not None
configured_model: Final = model or (
completion_response_model if isinstance(completion_response_model, str) else None
@ -1002,35 +1000,72 @@ def select_model_name_for_cost_calc(
else:
return_model = model
elif base_model is not None or bedrock_pricing_model is not None or provider_response_model is not None:
return_model = base_model if base_model is not None else bedrock_pricing_model or provider_response_model
elif base_model is not None or bedrock_pricing_model is not None:
return_model = base_model if base_model is not None else bedrock_pricing_model
elif provider_response_model is not None:
return _served_or_priced_requested_model(
provider_response_model, requested_model, custom_llm_provider, priced_region
)
elif completion_response_model is None and hidden_params is not None:
if hidden_params.get("model", None) is not None and len(hidden_params["model"]) > 0:
return_model = hidden_params.get("model", model)
if return_model is None and completion_response_model is not None:
return_model = completion_response_model
return _served_or_priced_requested_model(
completion_response_model, requested_model, custom_llm_provider, priced_region
)
if return_model is None and model is not None:
return_model = model
if (
return_model is not None
and custom_llm_provider is not None
and not _model_contains_known_llm_provider(return_model)
): # add provider prefix if not already present, to match model_cost
provider_prefix: Final = (
custom_llm_provider if priced_region is None else f"{custom_llm_provider}/{priced_region}"
)
return_model = _strip_unregistered_leading_segments(f"{provider_prefix}/{return_model}", priced_region)
return return_model
if return_model is None:
return None
return _cost_map_model_name(return_model, custom_llm_provider, priced_region)
_select_model_name_for_cost_calc = select_model_name_for_cost_calc
def _cost_map_model_name(model: str, custom_llm_provider: str | None, region_name: str | None) -> str:
if custom_llm_provider is None or _model_contains_known_llm_provider(model):
return model
provider_prefix: Final = custom_llm_provider if region_name is None else f"{custom_llm_provider}/{region_name}"
return _strip_unregistered_leading_segments(f"{provider_prefix}/{model}", region_name)
def _rule_declares_rates(model: str, custom_llm_provider: str | None) -> bool:
rule_info: Final = unmapped_model_generalization(model, custom_llm_provider)
return rule_info is not None and _cost_map_entry_prices_anything(rule_info)
def _resolves_to_rates(model: str, custom_llm_provider: str | None) -> bool:
key: Final = cached_cost_map_key(model, custom_llm_provider)
if key is None:
return False
entry: Final = _raw_cost_map_entry(key)
if entry is None:
return _rule_declares_rates(model, custom_llm_provider)
fill_missing: Final = mapped_model_fill_missing(key, entry)
return _cost_map_entry_prices_anything(entry if fill_missing is None else {**fill_missing, **entry})
def _served_or_priced_requested_model(
served_model: str, requested_model: str | None, custom_llm_provider: str | None, priced_region: str | None
) -> str:
served: Final = _cost_map_model_name(served_model, custom_llm_provider, priced_region)
if requested_model is None:
return served
requested: Final = _cost_map_model_name(requested_model, custom_llm_provider, None)
if served in (requested, requested.partition("/")[2]):
return served
pricing_provider: Final = _get_provider_for_cost_calc(requested, custom_llm_provider)
if _resolves_to_rates(served, pricing_provider):
return served
return requested if _resolves_to_rates(requested, pricing_provider) else served
def _strip_unregistered_leading_segments(model: str, region_name: str | None) -> str:
"""Resolve a provider-prefixed slash alias like "vertex_ai/vertex/claude-opus-5" to the
registered cost key ("vertex_ai/claude-opus-5"), keeping the model unchanged when it already
@ -3516,7 +3551,7 @@ def _cost_map_entry_declares_pricing(model_name: str, custom_llm_provider: str)
lookups cannot strip that prefix, so a zero-rated override read as declaring nothing, and a
session that should bill nothing fell through to the public rates instead.
"""
resolved: Final = _get_model_info_or_none(model_name, custom_llm_provider)
resolved: Final = _cost_map_model_info(model_name, custom_llm_provider)
entries: Final = (
litellm.model_cost.get(resolved.get("key")) if resolved is not None else None,
litellm.model_cost.get(model_name),
@ -3712,9 +3747,9 @@ def handle_realtime_transcription_cost_calculation(
return 0.0
model_name: Final = _get_transcription_model_name_from_results(results) or litellm_model_name
model_info: Final = _get_model_info_or_none(model_name, custom_llm_provider)
model_info: Final = _cost_map_model_info(model_name, custom_llm_provider)
override_info: Final = (
_get_model_info_or_none(custom_pricing_model, custom_llm_provider) if custom_pricing_model is not None else None
_cost_map_model_info(custom_pricing_model, custom_llm_provider) if custom_pricing_model is not None else None
)
total_cost = 0.0
@ -3745,13 +3780,6 @@ def _get_transcription_model_name_from_results(
return None
def _get_model_info_or_none(model: str, custom_llm_provider: str) -> ModelInfo | None:
try:
return litellm.get_model_info(model=model, custom_llm_provider=custom_llm_provider)
except Exception:
return None
_TranscriptionRateKey: TypeAlias = Literal[
"input_cost_per_second", "input_cost_per_audio_token", "input_cost_per_token", "output_cost_per_token"
]

View file

@ -279,7 +279,7 @@ from litellm.types.utils import (
)
_CALL_TYPE_ENUM_MAP: Final[dict] = {ct.value: ct for ct in CallTypes}
_BACKFILL_MODES: Final = frozenset({"chat", "responses"})
_BACKFILL_MODES: Final[frozenset[str]] = frozenset({"chat", "responses"})
# +-----------------------------------------------+
# | |
@ -6530,6 +6530,7 @@ def _invalidate_model_cost_lowercase_map() -> None:
# Clear LRU caches that depend on model_cost data
_cached_get_model_info.cache_clear()
cached_get_model_info_helper.cache_clear()
cached_cost_map_key.cache_clear()
def _rebuild_model_cost_lowercase_map() -> dict[str, str]:
@ -6755,6 +6756,14 @@ def unmapped_model_generalization(model: str, custom_llm_provider: str | None) -
return generalization[1] if generalization is not None else None
def mapped_model_fill_missing(key: str, model_info: Mapping[str, object]) -> Mapping[str, object] | None:
mode: Final = model_info.get("mode", "chat")
provider: Final = model_info.get("litellm_provider", "")
if not isinstance(mode, str) or mode not in _BACKFILL_MODES or not isinstance(provider, str):
return None
return match_fill_missing_generalizations(key, provider)
def _strip_mantle_region_prefix(model: str) -> str:
from litellm.llms.bedrock_mantle.common_utils import split_mantle_region_prefix
@ -6883,6 +6892,14 @@ def cached_get_model_info_helper(
_cached_get_model_info_helper = cached_get_model_info_helper
@lru_cache(maxsize=DEFAULT_MAX_LRU_CACHE_SIZE)
def cached_cost_map_key(model: str, custom_llm_provider: str | None) -> str | None:
try:
return cached_get_model_info_helper(model=model, custom_llm_provider=custom_llm_provider)["key"]
except Exception:
return None
def get_provider_info(model: str, custom_llm_provider: str | None) -> ProviderSpecificModelInfo | None:
## PROVIDER-SPECIFIC INFORMATION
# if custom_llm_provider == "predibase":
@ -7040,8 +7057,8 @@ def get_model_info_helper(
key: str | None = lookup[0]
_model_info: dict[str, Any] | None = lookup[1]
if _model_info is not None and key is not None and _model_info.get("mode", "chat") in _BACKFILL_MODES:
fill_missing: Final = match_fill_missing_generalizations(key, _model_info.get("litellm_provider", ""))
if _model_info is not None and key is not None:
fill_missing: Final = mapped_model_fill_missing(key, _model_info)
if fill_missing is not None:
_model_info = {
**{k: v for k, v in fill_missing.items() if k not in _model_info},

View file

@ -12320,6 +12320,49 @@ def test_get_model_cost_information_custom_pricing_uses_base_model():
assert result["model_map_key"] != "invoke_test_claude"
@pytest.mark.usefixtures("_vcr_outcome_gate", "drain_logging_worker", "isolate_litellm_state", "setup_and_teardown")
def test_get_model_cost_information_maps_requested_model_when_response_names_an_unmapped_display_name(
monkeypatch: pytest.MonkeyPatch,
) -> None:
requested_model: Final = "fireworks_ai/accounts/fireworks/models/sol-display-test"
monkeypatch.setitem(
litellm.model_cost,
requested_model,
{"litellm_provider": "fireworks_ai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
response: Final = ModelResponse(model="Sol Display Max", choices=[])
response._hidden_params["provider_response_model"] = "Sol Display Max"
response._hidden_params["litellm_model_name"] = requested_model
result: Final = StandardLoggingPayloadSetup.get_model_cost_information(
base_model=None, custom_pricing=False, custom_llm_provider="fireworks_ai", init_response_obj=response
)
assert result["model_map_key"] == requested_model
assert result["model_map_value"] is not None
assert result["model_map_value"]["input_cost_per_token"] == litellm.model_cost[requested_model]["input_cost_per_token"]
@pytest.mark.usefixtures("_vcr_outcome_gate", "drain_logging_worker", "isolate_litellm_state", "setup_and_teardown")
def test_get_model_cost_information_keeps_a_priced_response_model_that_echoes_without_its_provider(
monkeypatch: pytest.MonkeyPatch,
) -> None:
served_row: Final = {"litellm_provider": "mistral", "mode": "chat", "input_cost_per_token": 2e-6, "output_cost_per_token": 5e-6}
requested_row: Final = {"litellm_provider": "mistral", "mode": "chat", "input_cost_per_token": 9e-6, "output_cost_per_token": 9e-6}
monkeypatch.setitem(litellm.model_cost, "mistral/sol-snapshot-test", served_row)
monkeypatch.setitem(litellm.model_cost, "mistral/sol-alias-test", requested_row)
response: Final = ModelResponse(model="sol-snapshot-test", choices=[])
response._hidden_params["litellm_model_name"] = "mistral/sol-alias-test"
result: Final = StandardLoggingPayloadSetup.get_model_cost_information(
base_model=None, custom_pricing=False, custom_llm_provider="mistral", init_response_obj=response
)
assert result["model_map_key"] == "sol-snapshot-test"
assert result["model_map_value"] is not None
assert result["model_map_value"]["input_cost_per_token"] == served_row["input_cost_per_token"]
@pytest.mark.usefixtures("_vcr_outcome_gate", "drain_logging_worker", "isolate_litellm_state", "setup_and_teardown")
def test_standard_logging_payload_uses_deployment_when_no_base_model():
"""metadata["deployment"] is used for cost-map lookup when base_model is not set."""

View file

@ -37,7 +37,7 @@ from litellm.types.llms.xai import RealtimeMeteredUsage
from litellm.types.rerank import RerankResponse
from litellm.types.utils import CacheCreationTokenDetails, CallTypes, Choices, CompletionTokensDetailsWrapper, EmbeddingResponse, ImageObject, ImageResponse, ImageUsage, ImageUsageInputTokensDetails, LiteLLMRealtimeStreamLoggingObject, Message, ModelInfo, ModelResponse, PromptTokensDetails, PromptTokensDetailsWrapper, Usage
from litellm.types.videos.main import VideoObject
from litellm.utils import supports_prompt_caching
from litellm.utils import _invalidate_model_cost_lowercase_map, supports_prompt_caching
import os
import threading
from unittest.mock import MagicMock, patch
@ -424,6 +424,313 @@ def test_completion_cost_prices_rule_only_base_model_from_the_response_model(
assert cost == pytest.approx(usage.prompt_tokens * 0.0000111 + usage.completion_tokens * 0.0000222)
def _token_rates_cost(row: Mapping[str, object], usage: Usage) -> float:
input_rate: Final = row["input_cost_per_token"]
output_rate: Final = row["output_cost_per_token"]
assert isinstance(input_rate, float) and isinstance(output_rate, float), row
return usage.prompt_tokens * input_rate + usage.completion_tokens * output_rate
@pytest.mark.parametrize(
"requested_model",
["accounts/fireworks/models/sol-display-test", "fireworks_ai/accounts/fireworks/models/sol-display-test"],
)
def test_completion_cost_bills_requested_model_when_response_names_an_unmapped_display_name(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch, requested_model: str
) -> None:
monkeypatch.setitem(
litellm.model_cost,
"fireworks_ai/accounts/fireworks/models/sol-display-test",
{"litellm_provider": "fireworks_ai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
cost: Final = completion_cost(
completion_response=ModelResponse(model="Sol Display Max", choices=[], usage=usage),
model=requested_model,
custom_llm_provider="fireworks_ai",
)
assert cost == pytest.approx(
_token_rates_cost(litellm.model_cost["fireworks_ai/accounts/fireworks/models/sol-display-test"], usage)
)
def test_completion_cost_bills_requested_model_when_stream_reports_an_unmapped_display_name(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
requested_model: Final = "fireworks_ai/accounts/fireworks/models/sol-display-test"
monkeypatch.setitem(
litellm.model_cost,
requested_model,
{"litellm_provider": "fireworks_ai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
response: Final = ModelResponse(model="accounts/fireworks/models/sol-display-test", choices=[], usage=usage)
response._hidden_params["provider_response_model"] = "Sol Display Max"
cost: Final = completion_cost(
completion_response=response, model=requested_model, custom_llm_provider="fireworks_ai"
)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost[requested_model], usage))
def test_completion_cost_bills_requested_model_when_response_row_has_no_rates(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setitem(
litellm.model_cost,
"openai/sol-requested-test",
{"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
monkeypatch.setitem(
litellm.model_cost,
"openai/sol-served-snapshot-test",
{"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": None, "output_cost_per_token": None},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
cost: Final = completion_cost(
completion_response=ModelResponse(model="sol-served-snapshot-test", choices=[], usage=usage),
model="openai/sol-requested-test",
custom_llm_provider="openai",
)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost["openai/sol-requested-test"], usage))
def test_completion_cost_bills_priced_response_model_over_priced_requested_model(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setitem(
litellm.model_cost,
"openai/sol-requested-test",
{"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
monkeypatch.setitem(
litellm.model_cost,
"openai/sol-requested-test-2099-01-01",
{"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 5e-6, "output_cost_per_token": 11e-6},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
cost: Final = completion_cost(
completion_response=ModelResponse(model="sol-requested-test-2099-01-01", choices=[], usage=usage),
model="openai/sol-requested-test",
custom_llm_provider="openai",
)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost["openai/sol-requested-test-2099-01-01"], usage))
def test_completion_cost_keeps_response_pricing_when_requested_alias_has_no_rates(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
size_bucket: Final = get_base_model_for_pricing("Sol 70B Display")
monkeypatch.setitem(
litellm.model_cost,
size_bucket,
{"litellm_provider": "fireworks_ai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
cost: Final = completion_cost(
completion_response=ModelResponse(model="Sol 70B Display", choices=[], usage=usage),
model="sol-team-alias",
custom_llm_provider="fireworks_ai",
)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost[size_bucket], usage))
def test_completion_cost_bills_requested_model_priced_only_by_a_rule_when_response_row_has_no_rates(
_local_model_cost_map: None, _priced_claude_family_generalization: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setitem(
litellm.model_cost,
"anthropic/sol-served-snapshot-test",
{"litellm_provider": "anthropic", "mode": "chat", "input_cost_per_token": None, "output_cost_per_token": None},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
cost: Final = completion_cost(
completion_response=ModelResponse(model="sol-served-snapshot-test", choices=[], usage=usage),
model="anthropic/claude-sol-9",
custom_llm_provider="anthropic",
)
assert cost == pytest.approx(usage.prompt_tokens * 0.0000321 + usage.completion_tokens * 0.0000765)
def test_completion_cost_bills_requested_model_from_hidden_params_when_no_model_is_passed(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
requested_model: Final = "fireworks_ai/accounts/fireworks/models/sol-display-test"
monkeypatch.setitem(
litellm.model_cost,
requested_model,
{"litellm_provider": "fireworks_ai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
response: Final = ModelResponse(model="Sol Display Max", choices=[], usage=usage)
response._hidden_params["litellm_model_name"] = requested_model
cost: Final = completion_cost(completion_response=response)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost[requested_model], usage))
def test_completion_cost_bills_requested_model_regional_row_when_response_names_an_unmapped_display_name(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setitem(
litellm.model_cost,
"bedrock/sol-regional-test",
{"litellm_provider": "bedrock", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
monkeypatch.setitem(
litellm.model_cost,
"bedrock/eu-central-1/sol-regional-test",
{"litellm_provider": "bedrock", "mode": "chat", "input_cost_per_token": 5e-6, "output_cost_per_token": 11e-6},
)
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
response: Final = ModelResponse(model="Sol Regional Display", choices=[], usage=usage)
response._hidden_params.update(
{"litellm_model_name": "bedrock/sol-regional-test", "region_name": "eu-central-1", "custom_llm_provider": "bedrock"}
)
cost: Final = completion_cost(completion_response=response)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost["bedrock/eu-central-1/sol-regional-test"], usage))
def test_completion_cost_keeps_served_row_rates_filled_by_a_rule_over_the_requested_alias(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setitem(litellm.model_cost, "sol-fill-snapshot-test", {"litellm_provider": "openai", "mode": "chat"})
monkeypatch.setitem(
litellm.model_cost,
"sol-fill-alias-test",
{"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
filled_rates: Final = {"input_cost_per_token": 5e-6, "output_cost_per_token": 11e-6}
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
response: Final = ModelResponse(model="sol-fill-snapshot-test", choices=[], usage=usage)
response._hidden_params.update({"litellm_model_name": "openai/sol-fill-alias-test", "custom_llm_provider": "openai"})
previous_rules: Final = list(get_fallback_generalization_rules())
set_fallback_generalizations(
[
{
"name": "sol-fill-snapshot-rates",
"pattern": r"^sol-fill-snapshot-test$",
"fill_missing_for_providers": ["openai"],
"model_info": filled_rates,
}
]
)
try:
cost: Final = completion_cost(completion_response=response)
finally:
set_fallback_generalizations(previous_rules)
assert cost == pytest.approx(_token_rates_cost(filled_rates, usage))
_SIZE_BUCKET_CASES: Final = (
pytest.param(
"fireworks_ai",
"fireworks_ai/accounts/fireworks/models/sol-bucket-requested-test",
"Sol Bucket 405B Display",
"fireworks-ai-above-16b",
id="fireworks_ai",
),
pytest.param(
"together_ai",
"together_ai/sol-bucket-requested-test",
"meta-llama/Sol-Bucket-8B-Test",
"together-ai-4.1b-8b",
id="together_ai",
),
)
def _size_bucket_response(served_model: str, requested_model: str, custom_llm_provider: str) -> ModelResponse:
response: Final = ModelResponse(model=served_model, choices=[], usage=_SIZE_BUCKET_USAGE)
response._hidden_params.update({"litellm_model_name": requested_model, "custom_llm_provider": custom_llm_provider})
return response
_SIZE_BUCKET_USAGE: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
@pytest.mark.parametrize(("custom_llm_provider", "requested_model", "served_model", "bucket"), _SIZE_BUCKET_CASES)
def test_completion_cost_bills_requested_listed_rates_over_a_served_size_bucket(
_local_model_cost_map: None,
monkeypatch: pytest.MonkeyPatch,
custom_llm_provider: str,
requested_model: str,
served_model: str,
bucket: str,
) -> None:
monkeypatch.setitem(
litellm.model_cost,
requested_model,
{
"litellm_provider": custom_llm_provider,
"mode": "chat",
"input_cost_per_token": 3e-6,
"output_cost_per_token": 7e-6,
},
)
cost: Final = completion_cost(
completion_response=_size_bucket_response(served_model, requested_model, custom_llm_provider)
)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost[requested_model], _SIZE_BUCKET_USAGE))
assert cost != pytest.approx(_token_rates_cost(litellm.model_cost[bucket], _SIZE_BUCKET_USAGE))
@pytest.mark.parametrize(("custom_llm_provider", "requested_model", "served_model", "bucket"), _SIZE_BUCKET_CASES)
def test_completion_cost_keeps_served_size_bucket_rates_when_the_requested_model_is_unpriced(
_local_model_cost_map: None,
custom_llm_provider: str,
requested_model: str,
served_model: str,
bucket: str,
) -> None:
cost: Final = completion_cost(
completion_response=_size_bucket_response(served_model, requested_model, custom_llm_provider)
)
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost[bucket], _SIZE_BUCKET_USAGE))
def test_completion_cost_bills_a_requested_model_priced_after_an_unpriced_lookup(
_local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
) -> None:
requested_model: Final = "fireworks_ai/accounts/fireworks/models/sol-late-priced-test"
usage: Final = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)
def display_name_response() -> ModelResponse:
response: Final = ModelResponse(model="Sol Late Display", choices=[], usage=usage)
response._hidden_params.update({"litellm_model_name": requested_model, "custom_llm_provider": "fireworks_ai"})
return response
completion_cost(completion_response=display_name_response())
monkeypatch.setitem(
litellm.model_cost,
requested_model,
{"litellm_provider": "fireworks_ai", "mode": "chat", "input_cost_per_token": 3e-6, "output_cost_per_token": 7e-6},
)
_invalidate_model_cost_lowercase_map()
cost: Final = completion_cost(completion_response=display_name_response())
assert cost == pytest.approx(_token_rates_cost(litellm.model_cost[requested_model], usage))
def test_completion_cost_orders_candidates_without_extra_huggingface_config_requests(
_local_model_cost_map: None,
) -> None: