From a76e224a5fcbae311c52b1fec337dc2beb47e73b Mon Sep 17 00:00:00 2001 From: ksk2023 <154514711+ksk2023@users.noreply.github.com> Date: Wed, 26 Aug 2026 22:06:06 +0800 Subject: [PATCH 1/3] fix(cost_calculator): resolve real cost key when model_name alias contains '/' When a router-facing model_name alias contains a '/' whose leading segment is not a registered provider (e.g. 'vertex/claude-opus-5' for deployment 'vertex_ai/claude-opus-5'), _select_model_name_for_cost_calc re-prefixed it into a non-existent key ('vertex_ai/vertex/claude-opus-5'), so cost lookup silently priced every streamed request at $0 - token counts were recorded, no error raised, budgets never tripped. After prefixing, walk the alias tail and return the first assembly that exists in litellm.model_cost ('vertex_ai/claude-opus-5'). Provider/region segments in the head are preserved, and an alias that resolves to no known key keeps the previous behavior (no crash, legacy double-prefix). Fixes #38069 --- litellm/cost_calculator.py | 43 +++++++++++ tests/test_litellm/test_cost_calculator.py | 85 ++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 6536941a094..98a872a1603 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -799,9 +799,52 @@ def _select_model_name_for_cost_calc( else: return_model = f"{custom_llm_provider}/{return_model}" + # A router-facing model_name alias may itself contain a "/" (e.g. "vertex/claude-opus-5") + # whose leading segment is NOT a registered provider, so the alias was re-prefixed above + # into a non-existent key like "vertex_ai/vertex/claude-opus-5". Strip unregistered + # leading segments until the assembled name is a known model_cost key (or nothing + # strippable remains), so cost lookup resolves to the real model instead of pricing + # the request at $0. See #38069. + stripped = _strip_unregistered_leading_segments(return_model, region_name) + if stripped is not None: + return_model = stripped + return return_model +def _strip_unregistered_leading_segments(model: str, region_name: str | None) -> str | None: + """Find the real cost-map key hidden inside a re-prefixed alias. + + After the provider (and optional region) was prepended to a router-facing alias that + itself contains "/" segments (e.g. "vertex_ai/vertex/claude-opus-5"), walk the + remaining segments: keep the provider/region head, then try successively shorter + tails — "vertex_ai/vertex/claude-opus-5", "vertex_ai/claude-opus-5" — returning the + first that exists in `litellm.model_cost`. Segments that are themselves providers or + regions are never dropped from the head, so "vertex_ai/us-east-1/model" keeps its region. + + Returns None if no candidate resolves; the caller keeps its original assembly. + """ + + segments = model.split("/") + # head = leading provider (+ optional region) that must be preserved + head_len = 1 + if region_name is not None and len(segments) > 1 and segments[1] == region_name: + head_len = 2 + + head = segments[:head_len] + tail = segments[head_len:] + while tail: + candidate = "/".join(head + tail) + if candidate in litellm.model_cost: + return candidate + # only strip a leading tail segment that is NOT itself meaningful: + # a provider/region segment inside the tail (e.g. "azure_ai/o3") stays + if tail[0] in LlmProvidersSet or tail[0] == region_name: + return None + tail = tail[1:] + return None + + @lru_cache(maxsize=DEFAULT_MAX_LRU_CACHE_SIZE) def _model_contains_known_llm_provider(model: str) -> bool: """ diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 1f1c9be973f..14c587ccd14 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -3781,3 +3781,88 @@ def test_completion_cost_prices_anthropic_shaped_cache_read_tokens(_local_model_ ) assert cost == pytest.approx(3 * 4e-6 + 4014 * 4e-7 + 5 * 2e-5, rel=1e-9) + + +def test_select_model_name_strips_unregistered_alias_prefix(_local_model_cost_map): + """A router-facing model_name alias containing "/" whose leading segment is NOT a + registered provider must not be double-prefixed into a non-existent cost key. + + Regression test for #38069: alias "vertex/claude-opus-5" (real deployment + "vertex_ai/claude-opus-5") was re-prefixed into "vertex_ai/vertex/claude-opus-5", + silently pricing every streamed request at $0. + """ + + from litellm.cost_calculator import _select_model_name_for_cost_calc + + response = litellm.ModelResponse( + id="x", + choices=[ + { + "index": 0, + "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop", + } + ], + model="vertex/claude-opus-5", + ) + response._hidden_params = {} + + selected = _select_model_name_for_cost_calc( + model=None, + completion_response=response, + custom_llm_provider="vertex_ai", + ) + + assert selected == "vertex_ai/claude-opus-5" + + +def test_completion_cost_nonzero_for_slash_alias_model_name(_local_model_cost_map): + """End-to-end cost through a "/"-containing alias must price above zero (#38069).""" + + response = litellm.ModelResponse( + id="x", + choices=[ + { + "index": 0, + "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop", + } + ], + model="vertex/claude-opus-5", + ) + response._hidden_params = {"custom_llm_provider": "vertex_ai"} + response.usage = litellm.Usage(prompt_tokens=100, completion_tokens=50) + + cost = litellm.completion_cost( + completion_response=response, + custom_llm_provider="vertex_ai", + ) + + assert cost == pytest.approx(100 * 5e-6 + 50 * 2.5e-5, rel=1e-9) + + +def test_select_model_name_unresolvable_alias_unchanged(_local_model_cost_map): + """An alias that resolves to no known cost key keeps the legacy double-prefixed name.""" + + from litellm.cost_calculator import _select_model_name_for_cost_calc + + response = litellm.ModelResponse( + id="x", + choices=[ + { + "index": 0, + "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop", + } + ], + model="team/nonsense-model", + ) + response._hidden_params = {} + + selected = _select_model_name_for_cost_calc( + model=None, + completion_response=response, + custom_llm_provider="vertex_ai", + ) + + assert selected == "vertex_ai/team/nonsense-model" From c33454fdec73751610b3d823bf2285360d3af43a Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:22:45 -0700 Subject: [PATCH 2/3] fix(cost_calculator): keep custom-priced router ids when resolving slash aliases --- litellm/cost_calculator.py | 57 +++++++--------------- tests/test_litellm/test_cost_calculator.py | 48 ++++++++++++++++++ 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 98a872a1603..1e13cb9307e 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -799,50 +799,27 @@ def _select_model_name_for_cost_calc( else: return_model = f"{custom_llm_provider}/{return_model}" - # A router-facing model_name alias may itself contain a "/" (e.g. "vertex/claude-opus-5") - # whose leading segment is NOT a registered provider, so the alias was re-prefixed above - # into a non-existent key like "vertex_ai/vertex/claude-opus-5". Strip unregistered - # leading segments until the assembled name is a known model_cost key (or nothing - # strippable remains), so cost lookup resolves to the real model instead of pricing - # the request at $0. See #38069. - stripped = _strip_unregistered_leading_segments(return_model, region_name) - if stripped is not None: - return_model = stripped + return_model = _strip_unregistered_leading_segments(return_model, region_name) return return_model -def _strip_unregistered_leading_segments(model: str, region_name: str | None) -> str | None: - """Find the real cost-map key hidden inside a re-prefixed alias. - - After the provider (and optional region) was prepended to a router-facing alias that - itself contains "/" segments (e.g. "vertex_ai/vertex/claude-opus-5"), walk the - remaining segments: keep the provider/region head, then try successively shorter - tails — "vertex_ai/vertex/claude-opus-5", "vertex_ai/claude-opus-5" — returning the - first that exists in `litellm.model_cost`. Segments that are themselves providers or - regions are never dropped from the head, so "vertex_ai/us-east-1/model" keeps its region. - - Returns None if no candidate resolves; the caller keeps its original assembly. - """ - - segments = model.split("/") - # head = leading provider (+ optional region) that must be preserved - head_len = 1 - if region_name is not None and len(segments) > 1 and segments[1] == region_name: - head_len = 2 - - head = segments[:head_len] - tail = segments[head_len:] - while tail: - candidate = "/".join(head + tail) - if candidate in litellm.model_cost: - return candidate - # only strip a leading tail segment that is NOT itself meaningful: - # a provider/region segment inside the tail (e.g. "azure_ai/o3") stays - if tail[0] in LlmProvidersSet or tail[0] == region_name: - return None - tail = tail[1:] - return None +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 + resolves downstream (custom-priced router ids) or no stripped candidate is registered (#38069).""" + segments: Final = model.split("/") + if "/".join(segments[1:]) in litellm.model_cost: + return model + head_len: Final = 2 if region_name is not None and len(segments) > 2 and segments[1] == region_name else 1 + head: Final = "/".join(segments[:head_len]) + tail: Final = segments[head_len:] + strippable: Final = next( + (index for index, segment in enumerate(tail) if segment in LlmProvidersSet or segment == region_name), + len(tail), + ) + candidates: Final = tuple(f"{head}/{'/'.join(tail[start:])}" for start in range(min(strippable, len(tail) - 1) + 1)) + return next((candidate for candidate in candidates if candidate in litellm.model_cost), model) @lru_cache(maxsize=DEFAULT_MAX_LRU_CACHE_SIZE) diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 14c587ccd14..9aa627b2b23 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -3866,3 +3866,51 @@ def test_select_model_name_unresolvable_alias_unchanged(_local_model_cost_map): ) assert selected == "vertex_ai/team/nonsense-model" + + +def test_completion_cost_keeps_custom_priced_slash_router_id(_local_model_cost_map): + """A custom-priced router id containing "/" keeps its custom pricing instead of being + rewritten to the built-in key its suffix happens to match.""" + + from litellm.cost_calculator import _select_model_name_for_cost_calc + + litellm.register_model( + model_cost={ + "vertex/claude-opus-5": { + "input_cost_per_token": 7e-6, + "output_cost_per_token": 8e-6, + "litellm_provider": "vertex_ai", + } + } + ) + + selected = _select_model_name_for_cost_calc( + model="vertex_ai/claude-opus-5", + completion_response=None, + custom_pricing=True, + custom_llm_provider="vertex_ai", + router_model_id="vertex/claude-opus-5", + ) + assert selected == "vertex_ai/vertex/claude-opus-5" + + response = litellm.ModelResponse( + id="x", + choices=[ + { + "index": 0, + "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop", + } + ], + model="vertex/claude-opus-5", + ) + response._hidden_params = {"custom_llm_provider": "vertex_ai"} + response.usage = litellm.Usage(prompt_tokens=100, completion_tokens=50) + + cost = litellm.completion_cost( + completion_response=response, + custom_llm_provider="vertex_ai", + custom_pricing=True, + router_model_id="vertex/claude-opus-5", + ) + assert cost == pytest.approx(100 * 7e-6 + 50 * 8e-6, rel=1e-9) From 951cef1e98a9c17b775b31bcd9cb7bb257fc7bb3 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:10:05 -0700 Subject: [PATCH 3/3] fix(cost_calculator): strip duplicated region segment from alias cost keys --- litellm/cost_calculator.py | 7 ++---- tests/test_litellm/test_cost_calculator.py | 28 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 903b2c15a10..4cd292b2416 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -810,11 +810,8 @@ def _strip_unregistered_leading_segments(model: str, region_name: str | None) -> head_len: Final = 2 if region_name is not None and len(segments) > 2 and segments[1] == region_name else 1 head: Final = "/".join(segments[:head_len]) tail: Final = segments[head_len:] - strippable: Final = next( - (index for index, segment in enumerate(tail) if segment in LlmProvidersSet or segment == region_name), - len(tail), - ) - candidates: Final = tuple(f"{head}/{'/'.join(tail[start:])}" for start in range(min(strippable, len(tail) - 1) + 1)) + strippable: Final = next((index for index, segment in enumerate(tail) if segment in LlmProvidersSet), len(tail)) + candidates: Final = (f"{head}/{'/'.join(tail[start:])}" for start in range(min(strippable, len(tail) - 1) + 1)) return next((candidate for candidate in candidates if candidate in litellm.model_cost), model) diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 178c059462b..dc2fbe3ed73 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -3942,6 +3942,34 @@ def test_select_model_name_strips_unregistered_alias_prefix(_local_model_cost_ma assert selected == "vertex_ai/claude-opus-5" +def test_select_model_name_strips_duplicated_region_segment(_local_model_cost_map): + """A "region/model" alias whose leading segment repeats the request's region must + resolve to the region-priced cost key instead of keeping the region segment twice.""" + + from litellm.cost_calculator import _select_model_name_for_cost_calc + + response = litellm.ModelResponse( + id="x", + choices=[ + { + "index": 0, + "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop", + } + ], + model="us-east-1/anthropic.claude-v2:1", + ) + response._hidden_params = {"region_name": "us-east-1"} + + selected = _select_model_name_for_cost_calc( + model=None, + completion_response=response, + custom_llm_provider="bedrock", + ) + + assert selected == "bedrock/us-east-1/anthropic.claude-v2:1" + + def test_completion_cost_nonzero_for_slash_alias_model_name(_local_model_cost_map): """End-to-end cost through a "/"-containing alias must price above zero (#38069)."""