fix(cost): price vertex claude regional endpoints 10% above global

Vertex charges a 10% premium for Claude models served from regional and multi-region endpoints, while our cost map only carried the global endpoint rates. Any deployment pinned to a location such as us-east5 was therefore logged 10% under what Vertex bills.

Adds regional_endpoint_uplift_multiplier to the cost map for the Claude models Google prices that way, threads the deployment's vertex_location into the Vertex cost calculator, and applies the multiplier to every token type, including the cache write and cache read rates. A location of global, or no location at all, keeps the previous pricing.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-08-13 22:03:09 +00:00
parent fa498391a4
commit 322ae4e749
11 changed files with 245 additions and 8 deletions

View file

@ -139,6 +139,14 @@ NUMBER_KEYS: dict[str, JsonSchema] = {
"minimum": 1,
"description": "Multiplier applied to all token costs for US data residency (e.g. 1.10 = +10%).",
},
"regional_endpoint_uplift_multiplier": {
"type": "number",
"minimum": 1,
"description": (
"Multiplier applied to all token costs when the request is served from a regional or "
"multi-region endpoint instead of the global one (e.g. 1.10 = +10%)."
),
},
}
COST_DESCRIPTIONS: dict[str, str] = {

View file

@ -326,6 +326,8 @@ def cost_per_token(
service_tier: str | None = None, # for OpenAI service tier pricing
### DATA RESIDENCY ###
data_residency: str | None = None, # for OpenAI regional-processing uplift (e.g. "eu", "us")
### VERTEX LOCATION ###
vertex_location: str | None = None, # for Vertex AI regional endpoint uplift (e.g. "us-east5")
response: Any | None = None,
### REQUEST MODEL ###
request_model: str | None = None, # original request model for router detection
@ -593,6 +595,7 @@ def cost_per_token(
custom_llm_provider=custom_llm_provider,
usage=usage_block,
service_tier=service_tier,
vertex_location=vertex_location,
)
elif custom_llm_provider == "anthropic":
return anthropic_cost_per_token(model=model, usage=usage_block, service_tier=service_tier)
@ -1134,6 +1137,8 @@ def completion_cost(
service_tier: str | None = None, # for OpenAI service tier pricing
### DATA RESIDENCY ###
data_residency: str | None = None, # for OpenAI regional-processing uplift (e.g. "eu", "us")
### VERTEX LOCATION ###
vertex_location: str | None = None, # for Vertex AI regional endpoint uplift (e.g. "us-east5")
) -> float:
"""
Calculate the cost of a given completion call fot GPT-3.5-turbo, llama2, any litellm supported llm.
@ -1568,6 +1573,7 @@ def completion_cost(
rerank_billed_units=rerank_billed_units,
service_tier=service_tier,
data_residency=data_residency,
vertex_location=vertex_location,
response=completion_response,
request_model=request_model_for_cost,
)
@ -1756,6 +1762,8 @@ def response_cost_calculator(
service_tier: str | None = None, # for OpenAI service tier pricing
### DATA RESIDENCY ###
data_residency: str | None = None, # for OpenAI regional-processing uplift (e.g. "eu", "us")
### VERTEX LOCATION ###
vertex_location: str | None = None, # for Vertex AI regional endpoint uplift (e.g. "us-east5")
) -> float:
"""
Returns
@ -1788,6 +1796,7 @@ def response_cost_calculator(
litellm_logging_obj=litellm_logging_obj,
service_tier=service_tier,
data_residency=data_residency,
vertex_location=vertex_location,
)
return response_cost
except Exception as e:

View file

@ -1484,6 +1484,11 @@ class Logging(LiteLLMLoggingBaseClass):
if hasattr(self, "litellm_params") and self.litellm_params
else None
),
"vertex_location": (
self.litellm_params.get("vertex_location") or self.litellm_params.get("vertex_ai_location")
if hasattr(self, "litellm_params") and self.litellm_params
else None
),
}
except Exception as e: # error creating kwargs for cost calculation
debug_info = StandardLoggingModelCostFailureDebugInformation(

View file

@ -26,6 +26,22 @@ Google AI Studio -> token based pricing
models_without_dynamic_pricing: Final = ["gemini-1.0-pro", "gemini-pro", "gemini-2"]
GLOBAL_VERTEX_LOCATION: Final = "global"
def _regional_endpoint_uplift(model_info: ModelInfo, vertex_location: str | None) -> float:
"""
Vertex bills a flat premium (currently +10%) on every token type when a request is served
from a regional or multi-region endpoint instead of the global one, so the location the
request was routed to decides the rate, not just the model.
"""
if vertex_location is None or vertex_location.lower() == GLOBAL_VERTEX_LOCATION:
return 1.0
multiplier: Final = model_info.get("regional_endpoint_uplift_multiplier")
if multiplier is None:
return 1.0
return float(multiplier)
def cost_router(
model: str,
@ -196,6 +212,7 @@ def cost_per_token(
custom_llm_provider: str,
usage: Usage,
service_tier: str | None = None,
vertex_location: str | None = None,
) -> tuple[float, float]:
"""
Calculates the cost per token for a given model, prompt tokens, and completion tokens.
@ -207,6 +224,8 @@ def cost_per_token(
- completion_tokens: float, the number of output tokens
- service_tier: optional tier derived from Gemini trafficType
("priority" for ON_DEMAND_PRIORITY, "flex" for FLEX/batch).
- vertex_location: optional Vertex location the request was served from
(e.g. "us-east5", "us", "global"), used to apply the regional endpoint uplift.
Returns:
Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd
@ -222,14 +241,17 @@ def cost_per_token(
input_cost_per_token_above_128k_tokens: Final = model_info.get("input_cost_per_token_above_128k_tokens")
output_cost_per_token_above_128k_tokens: Final = model_info.get("output_cost_per_token_above_128k_tokens")
if input_cost_per_token_above_128k_tokens is not None or output_cost_per_token_above_128k_tokens is not None:
return _handle_128k_pricing(
prompt_cost, completion_cost = _handle_128k_pricing(
model_info=model_info,
usage=usage,
)
else:
prompt_cost, completion_cost = generic_cost_per_token(
model=model,
custom_llm_provider=custom_llm_provider,
usage=usage,
service_tier=service_tier,
)
return generic_cost_per_token(
model=model,
custom_llm_provider=custom_llm_provider,
usage=usage,
service_tier=service_tier,
)
uplift: Final = _regional_endpoint_uplift(model_info=model_info, vertex_location=vertex_location)
return prompt_cost * uplift, completion_cost * uplift

View file

@ -37631,6 +37631,7 @@
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 5e-06,
"regional_endpoint_uplift_multiplier": 1.1,
"source": "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/haiku-4-5",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37654,6 +37655,7 @@
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 5e-06,
"regional_endpoint_uplift_multiplier": 1.1,
"source": "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/haiku-4-5",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37869,6 +37871,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37897,6 +37900,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37927,6 +37931,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37957,6 +37962,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37987,6 +37993,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38018,6 +38025,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38049,6 +38057,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38080,6 +38089,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38112,6 +38122,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38144,6 +38155,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38176,6 +38188,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38208,6 +38221,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38243,6 +38257,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"output_cost_per_token_batches": 7.5e-06,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -38267,6 +38282,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38299,6 +38315,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_function_calling": true,
@ -38333,6 +38350,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"output_cost_per_token_batches": 7.5e-06,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -45925,6 +45943,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -45957,6 +45976,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_function_calling": true,

View file

@ -39,7 +39,7 @@ from pydantic import (
field_serializer,
field_validator,
)
from typing_extensions import Required, TypedDict
from typing_extensions import ReadOnly, Required, TypedDict
from litellm._logging import verbose_logger
from litellm._uuid import uuid
@ -244,6 +244,9 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False):
regional_processing_uplift_multiplier_us: (
float | None
) # OpenAI US data-residency uplift multiplier applied to all token costs (e.g. 1.10 = +10%)
regional_endpoint_uplift_multiplier: ReadOnly[
float | None
] # Vertex AI uplift multiplier applied to all token costs on regional/multi-region endpoints
output_cost_per_character: float | None # only for vertex ai models
output_cost_per_audio_token: float | None
output_cost_per_token_above_128k_tokens: float | None # only for vertex ai models

View file

@ -5650,6 +5650,7 @@ def _get_model_info_helper(
regional_processing_uplift_multiplier_us=_model_info.get(
"regional_processing_uplift_multiplier_us", None
),
regional_endpoint_uplift_multiplier=_model_info.get("regional_endpoint_uplift_multiplier", None),
output_cost_per_audio_token=_model_info.get("output_cost_per_audio_token", None),
output_cost_per_character=_model_info.get("output_cost_per_character", None),
output_cost_per_reasoning_token=_model_info.get("output_cost_per_reasoning_token", None),

View file

@ -37631,6 +37631,7 @@
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 5e-06,
"regional_endpoint_uplift_multiplier": 1.1,
"source": "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/haiku-4-5",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37654,6 +37655,7 @@
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 5e-06,
"regional_endpoint_uplift_multiplier": 1.1,
"source": "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude/haiku-4-5",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37869,6 +37871,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37897,6 +37900,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37927,6 +37931,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37957,6 +37962,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -37987,6 +37993,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38018,6 +38025,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38049,6 +38057,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38080,6 +38089,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38112,6 +38122,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38144,6 +38155,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38176,6 +38188,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38208,6 +38221,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 2.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38243,6 +38257,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"output_cost_per_token_batches": 7.5e-06,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -38267,6 +38282,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -38299,6 +38315,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_function_calling": true,
@ -38333,6 +38350,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"output_cost_per_token_batches": 7.5e-06,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -45925,6 +45943,7 @@
"max_tokens": 128000,
"mode": "chat",
"output_cost_per_token": 1e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
@ -45957,6 +45976,7 @@
"max_tokens": 64000,
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"regional_endpoint_uplift_multiplier": 1.1,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_function_calling": true,

View file

@ -505,6 +505,11 @@
"type": "object",
"description": "Provider-internal routing hints (e.g. bedrock_invocation_schema)."
},
"regional_endpoint_uplift_multiplier": {
"type": "number",
"minimum": 1,
"description": "Multiplier applied to all token costs when the request is served from a regional or multi-region endpoint instead of the global one (e.g. 1.10 = +10%)."
},
"regional_processing_uplift_multiplier_eu": {
"type": "number",
"minimum": 1,

View file

@ -4539,3 +4539,39 @@ async def test_restore_correlation_context_works_across_asyncio_task_boundary():
finally:
trace_id_var.set("")
session_id_var.set("")
@pytest.mark.parametrize(
"vertex_location, uplift",
[("global", 1.0), ("us-east5", 1.1)],
)
def test_response_cost_calculator_passes_vertex_location(monkeypatch, vertex_location, uplift):
"""Vertex charges 10% more on regional and multi-region endpoints than on the global one, so the
deployment's vertex_location has to reach the cost calculator or regional spend is undercounted."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
logging_obj = LitellmLogging(
model="vertex_ai/claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hey"}],
stream=False,
call_type="completion",
start_time=time.time(),
litellm_call_id="vertex-location-123",
function_id="test-fn",
)
logging_obj.update_environment_variables(
model="vertex_ai/claude-sonnet-4-6",
user="",
optional_params={},
litellm_params={"vertex_location": vertex_location, "vertex_project": "test-project"},
)
logging_obj.model_call_details["custom_llm_provider"] = "vertex_ai"
response = ModelResponse(
model="claude-sonnet-4-6",
usage=litellm.Usage(prompt_tokens=1000, completion_tokens=200, total_tokens=1200),
)
cost = logging_obj._response_cost_calculator(result=response)
assert cost == pytest.approx((1000 * 3e-6 + 200 * 1.5e-5) * uplift, rel=1e-9)

View file

@ -3530,3 +3530,111 @@ def test_completion_cost_prices_anthropic_shaped_cache_read_tokens():
)
assert cost == pytest.approx(3 * 5e-6 + 4014 * 5e-7 + 5 * 3e-5, rel=1e-9)
def _vertex_claude_usage() -> Usage:
return Usage(
prompt_tokens=3500,
completion_tokens=200,
total_tokens=3700,
cache_creation_input_tokens=500,
cache_read_input_tokens=2000,
)
def test_vertex_claude_regional_endpoint_uplift_applied(monkeypatch):
"""Regression: Vertex bills regional and multi-region endpoints 10% above the global
endpoint, so a deployment pinned to vertex_location=us-east5 was undercharged by 10%
on every token type while we priced it at global rates."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
usage = _vertex_claude_usage()
global_prompt, global_completion = cost_per_token(
model="claude-sonnet-4-6",
custom_llm_provider="vertex_ai",
usage_object=usage,
vertex_location="global",
)
regional_prompt, regional_completion = cost_per_token(
model="claude-sonnet-4-6",
custom_llm_provider="vertex_ai",
usage_object=usage,
vertex_location="us-east5",
)
expected_global_prompt = 1000 * 3e-6 + 500 * 3.75e-6 + 2000 * 3e-7
assert global_prompt == pytest.approx(expected_global_prompt, rel=1e-9)
assert global_completion == pytest.approx(200 * 1.5e-5, rel=1e-9)
assert regional_prompt == pytest.approx(global_prompt * 1.1, rel=1e-9)
assert regional_completion == pytest.approx(global_completion * 1.1, rel=1e-9)
def test_vertex_claude_no_uplift_without_location(monkeypatch):
"""An unknown location must not change pricing, so existing global-endpoint spend stays put."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
usage = _vertex_claude_usage()
baseline = cost_per_token(
model="claude-opus-4-7",
custom_llm_provider="vertex_ai",
usage_object=usage,
)
assert baseline == cost_per_token(
model="claude-opus-4-7",
custom_llm_provider="vertex_ai",
usage_object=usage,
vertex_location="GLOBAL",
)
regional = cost_per_token(
model="claude-opus-4-7",
custom_llm_provider="vertex_ai",
usage_object=usage,
vertex_location="us",
)
assert regional[0] == pytest.approx(baseline[0] * 1.1, rel=1e-9)
assert regional[1] == pytest.approx(baseline[1] * 1.1, rel=1e-9)
def test_vertex_gemini_unaffected_by_location(monkeypatch):
"""Only Vertex Claude models carry the regional uplift, Gemini pricing is location independent."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
usage = Usage(prompt_tokens=1000, completion_tokens=200, total_tokens=1200)
assert cost_per_token(
model="gemini-2.5-pro",
custom_llm_provider="vertex_ai",
usage_object=usage,
vertex_location="us-east5",
) == cost_per_token(
model="gemini-2.5-pro",
custom_llm_provider="vertex_ai",
usage_object=usage,
)
def test_completion_cost_applies_vertex_regional_uplift(monkeypatch):
"""End-to-end: the location on the deployment's litellm_params must reach the cost calculator."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
response = ModelResponse(
model="claude-sonnet-4-6",
usage=Usage(prompt_tokens=1000, completion_tokens=200, total_tokens=1200),
)
global_cost = completion_cost(
completion_response=response,
model="claude-sonnet-4-6",
custom_llm_provider="vertex_ai",
)
regional_cost = completion_cost(
completion_response=response,
model="claude-sonnet-4-6",
custom_llm_provider="vertex_ai",
vertex_location="europe-west1",
)
assert global_cost == pytest.approx(1000 * 3e-6 + 200 * 1.5e-5, rel=1e-9)
assert regional_cost == pytest.approx(global_cost * 1.1, rel=1e-9)