Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions litellm/cost_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,45 @@ def _get_additional_costs(
return None


def _recorded_cache_storage_token_hours(litellm_logging_obj: LitellmLoggingObject | None) -> object:
return litellm_logging_obj.context_cache_storage_token_hours if litellm_logging_obj else 0.0


def _context_cache_storage_cost(
litellm_logging_obj: LitellmLoggingObject | None,
*,
requested_model: str | None,
completion_response: object | None,
base_model: str | None,
custom_llm_provider: str | None,
router_model_id: str | None,
region_name: str | None,
) -> float:
token_hours: Final = _recorded_cache_storage_token_hours(litellm_logging_obj)
if not isinstance(token_hours, int | float) or token_hours <= 0:
return 0.0
underlying_model: Final = select_model_name_for_cost_calc(
model=requested_model,
completion_response=completion_response,
base_model=base_model,
custom_llm_provider=custom_llm_provider,
region_name=region_name,
)
rate: Final = next(
(
price
for source in (
_deployment_model_info(litellm_logging_obj, custom_pricing=True, router_model_id=router_model_id),
_cost_map_model_info(underlying_model, custom_llm_provider) if underlying_model else None,
)
if source is not None
and isinstance(price := source.get("cache_storage_cost_per_token_per_hour"), int | float)
),
0.0,
)
return token_hours * rate


def _transcription_usage_has_token_details(
usage_block: Usage | None,
) -> bool:
Expand Down Expand Up @@ -1482,6 +1521,7 @@ def completion_cost(
region_name=region_name,
)

requested_model: Final = model
potential_model_names: Final = [
selected_model,
_get_response_model(completion_response),
Expand Down Expand Up @@ -1871,6 +1911,20 @@ def completion_cost(
)
else:
additional_costs = None
_storage_response: ModelResponse | None = (
completion_response if isinstance(completion_response, ModelResponse) else None
)
_storage_provider: str | None = custom_llm_provider if isinstance(custom_llm_provider, str) else None
_storage_region: str | None = region_name if isinstance(region_name, str) else None
cache_storage_cost: float = _context_cache_storage_cost(
Comment thread
greptile-apps[bot] marked this conversation as resolved.
litellm_logging_obj,
requested_model=requested_model,
completion_response=_storage_response,
base_model=base_model,
custom_llm_provider=_storage_provider,
router_model_id=router_model_id,
region_name=_storage_region,
)

_final_cost = prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar
cost_for_built_in_tools = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
Expand All @@ -1883,6 +1937,12 @@ def completion_cost(
_final_cost += cost_for_built_in_tools
if additional_costs:
_final_cost += sum(additional_costs.values())
if cache_storage_cost > 0:
_final_cost += cache_storage_cost
if additional_costs is None:
additional_costs = {"cache_storage_cost": cache_storage_cost}
else:
additional_costs["cache_storage_cost"] = cache_storage_cost

original_cost = _final_cost
if litellm.cost_discount_config:
Expand Down
4 changes: 4 additions & 0 deletions litellm/litellm_core_utils/litellm_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,10 @@ class Logging(LiteLLMLoggingBaseClass):
classifier_input: Mapping[str, JsonValue] | None = None
baseline_cache_context: "BaselineCacheContext | None" = None
baseline_observation: "CapturedBaselineObservation | None" = None
context_cache_storage_token_hours: float = 0.0

def record_context_cache_storage(self, token_hours: float) -> None:
self.context_cache_storage_token_hours = token_hours

@property
def _defer_async_logging(self) -> bool:
Expand Down
56 changes: 56 additions & 0 deletions litellm/llms/vertex_ai/context_caching/storage_cost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from datetime import datetime
from typing import Final

from pydantic import BaseModel, ConfigDict, ValidationError

DEFAULT_CONTEXT_CACHE_TTL_SECONDS: Final = 3600.0
_SECONDS_PER_HOUR: Final = 3600.0


class _CachedContentUsage(BaseModel):
model_config = ConfigDict(frozen=True)

totalTokenCount: int = 0


class _CreatedCachedContent(BaseModel):
model_config = ConfigDict(frozen=True)

usageMetadata: _CachedContentUsage = _CachedContentUsage()
createTime: datetime | None = None
expireTime: datetime | None = None


def _reported_lifetime_seconds(created: _CreatedCachedContent) -> float | None:
if created.createTime is None or created.expireTime is None:
return None
seconds: Final = (created.expireTime - created.createTime).total_seconds()
return seconds if seconds > 0 else None


def _requested_ttl_seconds(requested_ttl: str | None) -> float | None:
if requested_ttl is None or not requested_ttl.endswith("s"):
return None
try:
seconds: Final = float(requested_ttl[:-1])
except ValueError:
return None
return seconds if seconds > 0 else None


def context_cache_storage_token_hours(created_cached_content: object, requested_ttl: str | None) -> float:
"""Token-hours a newly created cachedContents resource is billed for over its whole TTL.

The lifetime comes from the create response (expireTime - createTime), then the TTL that was
requested, then the provider default of one hour.
"""
try:
created: Final = _CreatedCachedContent.model_validate(created_cached_content)
except ValidationError:
return 0.0
lifetime_seconds: Final = (
_reported_lifetime_seconds(created)
or _requested_ttl_seconds(requested_ttl)
or DEFAULT_CONTEXT_CACHE_TTL_SECONDS
)
return created.usageMetadata.totalTokenCount * lifetime_seconds / _SECONDS_PER_HOUR
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from ..common_utils import VertexAIError, get_vertex_base_url
from ..vertex_llm_base import VertexBase
from .storage_cost import context_cache_storage_token_hours
from .transformation import (
cached_messages_end_on_supported_turn,
separate_cached_messages,
Expand Down Expand Up @@ -425,6 +426,9 @@ def check_and_create_cache(
raise VertexAIError(status_code=408, message="Timeout error occurred.")

raw_response_cached: Final = response.json()
logging_obj.record_context_cache_storage(
context_cache_storage_token_hours(raw_response_cached, cached_content_request_body.get("ttl"))
)
cached_content_response_obj: Final = VertexAICachedContentResponseObject(
name=raw_response_cached.get("name"), model=raw_response_cached.get("model")
)
Expand Down Expand Up @@ -581,6 +585,9 @@ async def async_check_and_create_cache(
raise VertexAIError(status_code=408, message="Timeout error occurred.")

raw_response_cached: Final = response.json()
logging_obj.record_context_cache_storage(
context_cache_storage_token_hours(raw_response_cached, cached_content_request_body.get("ttl"))
)
cached_content_response_obj: Final = VertexAICachedContentResponseObject(
name=raw_response_cached.get("name"), model=raw_response_cached.get("model")
)
Expand Down
29 changes: 27 additions & 2 deletions litellm/model_prices_and_context_window_backup.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"sample_spec": {
"cache_storage_cost_per_token_per_hour": 0.0,
"code_interpreter_cost_per_session": 0.0,
"computer_use_input_cost_per_1k_tokens": 0.0,
"computer_use_output_cost_per_1k_tokens": 0.0,
Expand Down Expand Up @@ -27091,6 +27092,7 @@
"cache_read_input_token_cost": 3e-08,
"cache_read_input_token_cost_flex": 3e-08,
"cache_read_input_token_cost_priority": 5.4e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_token": 3e-07,
"litellm_provider": "vertex_ai-language-models",
Expand Down Expand Up @@ -27442,6 +27444,7 @@
},
"gemini-3.1-flash-lite-preview": {
"cache_read_input_token_cost": 2.5e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 2.5e-07,
"litellm_provider": "vertex_ai-language-models",
Expand Down Expand Up @@ -27496,6 +27499,7 @@
"cache_read_input_token_cost_batches": 1.25e-08,
"cache_read_input_token_cost_flex": 1.25e-08,
"cache_read_input_token_cost_priority": 4.5e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 2.5e-07,
"input_cost_per_token_batches": 1.25e-07,
Expand Down Expand Up @@ -27557,6 +27561,7 @@
"cache_read_input_token_cost_batches": 1.5e-08,
"cache_read_input_token_cost_flex": 1.5e-08,
"cache_read_input_token_cost_priority": 5.4e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_token": 3e-07,
"input_cost_per_token_batches": 1.5e-07,
"input_cost_per_token_flex": 1.5e-07,
Expand Down Expand Up @@ -27651,6 +27656,7 @@
"cache_read_input_token_cost": 1e-08,
"cache_read_input_token_cost_flex": 1e-08,
"cache_read_input_token_cost_priority": 1.8e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_audio_token": 3e-07,
"input_cost_per_token": 1e-07,
"litellm_provider": "vertex_ai-language-models",
Expand Down Expand Up @@ -27939,6 +27945,7 @@
"cache_read_input_token_cost_above_200k_tokens_priority": 4.5e-07,
"cache_read_input_token_cost_flex": 1.25e-07,
"cache_read_input_token_cost_priority": 2.25e-07,
"cache_storage_cost_per_token_per_hour": 4.5e-06,
"input_cost_per_token": 1.25e-06,
"input_cost_per_token_above_200k_tokens": 2.5e-06,
"litellm_provider": "vertex_ai-language-models",
Expand Down Expand Up @@ -28041,6 +28048,7 @@
"cache_read_input_token_cost_priority": 3.6e-07,
"cache_read_input_token_cost_above_200k_tokens_priority": 7.2e-07,
"cache_read_input_token_cost_flex": 2e-07,
"cache_storage_cost_per_token_per_hour": 4.5e-06,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
Expand Down Expand Up @@ -28152,6 +28160,7 @@
"output_cost_per_token_above_200k_tokens_priority": 3.24e-05,
"cache_read_input_token_cost_priority": 3.6e-07,
"cache_read_input_token_cost_above_200k_tokens_priority": 7.2e-07,
"cache_storage_cost_per_token_per_hour": 4.5e-06,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
Expand Down Expand Up @@ -28202,6 +28211,7 @@
"input_cost_per_audio_token_priority": 1.8e-06,
"output_cost_per_token_priority": 5.4e-06,
"cache_read_input_token_cost_priority": 9e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
Expand Down Expand Up @@ -28274,7 +28284,8 @@
"output_cost_per_token_batches": 4.5e-06,
"input_cost_per_token_flex": 7.5e-07,
"output_cost_per_token_flex": 4.5e-06,
"cache_read_input_token_cost_flex": 7.5e-08
"cache_read_input_token_cost_flex": 7.5e-08,
"cache_storage_cost_per_token_per_hour": 1e-06
},
"vertex_ai/gemini-3.6-flash": {
"prompt_cache_min_tokens": 4096,
Expand Down Expand Up @@ -28326,6 +28337,7 @@
"input_cost_per_token_priority": 1.35e-06,
"output_cost_per_token_priority": 6.75e-06,
"cache_read_input_token_cost_priority": 1.35e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"deprecation_date": "2026-11-19",
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
Expand Down Expand Up @@ -28386,6 +28398,7 @@
"input_cost_per_token_priority": 1.35e-06,
"output_cost_per_token_priority": 6.75e-06,
"cache_read_input_token_cost_priority": 1.35e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"deprecation_date": "2027-01-28",
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
Expand Down Expand Up @@ -28446,6 +28459,7 @@
"input_cost_per_token_priority": 1.35e-06,
"output_cost_per_token_priority": 6.75e-06,
"cache_read_input_token_cost_priority": 1.35e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
Expand All @@ -28459,6 +28473,7 @@
"cache_read_input_token_cost_batches": 7.5e-08,
"cache_read_input_token_cost_flex": 7.5e-08,
"cache_read_input_token_cost_priority": 2.7e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_token": 1.5e-06,
"input_cost_per_token_batches": 7.5e-07,
"input_cost_per_token_flex": 7.5e-07,
Expand Down Expand Up @@ -28592,6 +28607,7 @@
"cache_read_input_token_cost_priority": 3.6e-07,
"cache_read_input_token_cost_above_200k_tokens_priority": 7.2e-07,
"cache_read_input_token_cost_flex": 2e-07,
"cache_storage_cost_per_token_per_hour": 4.5e-06,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
Expand Down Expand Up @@ -30449,6 +30465,7 @@
"input_cost_per_audio_token_priority": 1.8e-06,
"output_cost_per_token_priority": 5.4e-06,
"cache_read_input_token_cost_priority": 9e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
Expand Down Expand Up @@ -30552,7 +30569,8 @@
"output_cost_per_token_batches": 4.5e-06,
"input_cost_per_token_flex": 7.5e-07,
"output_cost_per_token_flex": 4.5e-06,
"cache_read_input_token_cost_flex": 7.5e-08
"cache_read_input_token_cost_flex": 7.5e-08,
"cache_storage_cost_per_token_per_hour": 1e-06
},
"gemini-3.6-flash": {
"prompt_cache_min_tokens": 4096,
Expand Down Expand Up @@ -30604,6 +30622,7 @@
"input_cost_per_token_priority": 1.35e-06,
"output_cost_per_token_priority": 6.75e-06,
"cache_read_input_token_cost_priority": 1.35e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"deprecation_date": "2026-11-19",
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
Expand Down Expand Up @@ -30664,6 +30683,7 @@
"input_cost_per_token_priority": 1.35e-06,
"output_cost_per_token_priority": 6.75e-06,
"cache_read_input_token_cost_priority": 1.35e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"deprecation_date": "2027-01-28",
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
Expand Down Expand Up @@ -30724,6 +30744,7 @@
"input_cost_per_token_priority": 1.35e-06,
"output_cost_per_token_priority": 6.75e-06,
"cache_read_input_token_cost_priority": 1.35e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
Expand All @@ -30737,6 +30758,7 @@
"cache_read_input_token_cost_batches": 7.5e-08,
"cache_read_input_token_cost_flex": 7.5e-08,
"cache_read_input_token_cost_priority": 2.7e-07,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_token": 1.5e-06,
"input_cost_per_token_batches": 7.5e-07,
"input_cost_per_token_flex": 7.5e-07,
Expand Down Expand Up @@ -50905,6 +50927,7 @@
},
"vertex_ai/gemini-3.1-flash-lite-preview": {
"cache_read_input_token_cost": 2.5e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 2.5e-07,
"litellm_provider": "vertex_ai-language-models",
Expand Down Expand Up @@ -50959,6 +50982,7 @@
"cache_read_input_token_cost_batches": 1.25e-08,
"cache_read_input_token_cost_flex": 1.25e-08,
"cache_read_input_token_cost_priority": 4.5e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 2.5e-07,
"input_cost_per_token_batches": 1.25e-07,
Expand Down Expand Up @@ -51021,6 +51045,7 @@
"cache_read_input_token_cost_batches": 1.5e-08,
"cache_read_input_token_cost_flex": 1.5e-08,
"cache_read_input_token_cost_priority": 5.4e-08,
"cache_storage_cost_per_token_per_hour": 1e-06,
"input_cost_per_token": 3e-07,
"input_cost_per_token_batches": 1.5e-07,
"input_cost_per_token_flex": 1.5e-07,
Expand Down
1 change: 1 addition & 0 deletions litellm/types/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ class ModelInfo(MirroredPricingParams):
cost_per_ptu_per_hour: float | None = None
ptu_effective_from: datetime.datetime | None = None
ptu_effective_to: datetime.datetime | None = None
cache_storage_cost_per_token_per_hour: float | None = None

# when tag-based routing's "!" or "&" constraints eliminate every deployment
# in this model group, fall back to the default-tagged pool instead of
Expand Down
Loading
Loading