mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
Revert "feat(cost_breakdown): add granular cost breakdown for web search, cache read/write, and token tiers"
This reverts commit 2c468b775a.
This commit is contained in:
parent
2c468b775a
commit
6f56852cf1
5 changed files with 23 additions and 688 deletions
|
|
@ -28,7 +28,6 @@ from litellm.litellm_core_utils.llm_cost_calc.utils import (
|
|||
_parse_prompt_tokens_details,
|
||||
calculate_cost_component,
|
||||
generic_cost_per_token,
|
||||
generic_cost_per_token_with_breakdown,
|
||||
get_billable_input_tokens,
|
||||
select_cost_metric_for_model,
|
||||
)
|
||||
|
|
@ -942,22 +941,6 @@ def _store_cost_breakdown_in_logging_obj(
|
|||
margin_percent: Optional[float] = None,
|
||||
margin_fixed_amount: Optional[float] = None,
|
||||
margin_total_amount: Optional[float] = None,
|
||||
input_cost_text: Optional[float] = None,
|
||||
input_cost_cache_read: Optional[float] = None,
|
||||
input_cost_cache_creation: Optional[float] = None,
|
||||
input_cost_audio: Optional[float] = None,
|
||||
output_cost_text: Optional[float] = None,
|
||||
output_cost_reasoning: Optional[float] = None,
|
||||
output_cost_audio: Optional[float] = None,
|
||||
input_tokens_text: Optional[int] = None,
|
||||
input_tokens_cache_read: Optional[int] = None,
|
||||
input_tokens_cache_creation: Optional[int] = None,
|
||||
input_tokens_audio: Optional[int] = None,
|
||||
output_tokens_text: Optional[int] = None,
|
||||
output_tokens_reasoning: Optional[int] = None,
|
||||
output_tokens_audio: Optional[int] = None,
|
||||
above_128k_tokens: Optional[bool] = None,
|
||||
above_200k_tokens: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Helper function to store cost breakdown in the logging object.
|
||||
|
|
@ -975,22 +958,12 @@ def _store_cost_breakdown_in_logging_obj(
|
|||
margin_percent: Margin percentage applied (0.10 = 10%)
|
||||
margin_fixed_amount: Fixed margin amount in USD
|
||||
margin_total_amount: Total margin added in USD
|
||||
input_cost_text: Cost for non-cached text input tokens
|
||||
input_cost_cache_read: Cost for cache read/hit tokens
|
||||
input_cost_cache_creation: Cost for cache creation/write tokens
|
||||
input_cost_audio: Cost for audio input tokens
|
||||
output_cost_text: Cost for text output tokens
|
||||
output_cost_reasoning: Cost for reasoning output tokens
|
||||
output_cost_audio: Cost for audio output tokens
|
||||
input_tokens_*: Token counts for each input category
|
||||
output_tokens_*: Token counts for each output category
|
||||
above_128k_tokens: Whether above-128K token pricing was used
|
||||
above_200k_tokens: Whether above-200K token pricing was used
|
||||
"""
|
||||
if litellm_logging_obj is None:
|
||||
return
|
||||
|
||||
try:
|
||||
# Store the cost breakdown
|
||||
litellm_logging_obj.set_cost_breakdown(
|
||||
input_cost=prompt_tokens_cost_usd_dollar,
|
||||
output_cost=completion_tokens_cost_usd_dollar,
|
||||
|
|
@ -1003,26 +976,11 @@ def _store_cost_breakdown_in_logging_obj(
|
|||
margin_percent=margin_percent,
|
||||
margin_fixed_amount=margin_fixed_amount,
|
||||
margin_total_amount=margin_total_amount,
|
||||
input_cost_text=input_cost_text,
|
||||
input_cost_cache_read=input_cost_cache_read,
|
||||
input_cost_cache_creation=input_cost_cache_creation,
|
||||
input_cost_audio=input_cost_audio,
|
||||
output_cost_text=output_cost_text,
|
||||
output_cost_reasoning=output_cost_reasoning,
|
||||
output_cost_audio=output_cost_audio,
|
||||
input_tokens_text=input_tokens_text,
|
||||
input_tokens_cache_read=input_tokens_cache_read,
|
||||
input_tokens_cache_creation=input_tokens_cache_creation,
|
||||
input_tokens_audio=input_tokens_audio,
|
||||
output_tokens_text=output_tokens_text,
|
||||
output_tokens_reasoning=output_tokens_reasoning,
|
||||
output_tokens_audio=output_tokens_audio,
|
||||
above_128k_tokens=above_128k_tokens,
|
||||
above_200k_tokens=above_200k_tokens,
|
||||
)
|
||||
|
||||
except Exception as breakdown_error:
|
||||
verbose_logger.debug(f"Error storing cost breakdown: {str(breakdown_error)}")
|
||||
# Don't fail the main cost calculation if breakdown storage fails
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -1577,49 +1535,6 @@ def completion_cost( # noqa: PLR0915
|
|||
margin_fixed_amount = 0.0
|
||||
margin_total_amount = 0.0
|
||||
|
||||
# Compute granular breakdown if we have the usage object
|
||||
_granular_kwargs: dict = {}
|
||||
verbose_logger.debug(
|
||||
"Granular breakdown check: usage_obj=%s, provider=%s, model=%s",
|
||||
type(cost_per_token_usage_object).__name__ if cost_per_token_usage_object else None,
|
||||
custom_llm_provider,
|
||||
model,
|
||||
)
|
||||
if (
|
||||
cost_per_token_usage_object is not None
|
||||
and custom_llm_provider is not None
|
||||
):
|
||||
try:
|
||||
_breakdown = generic_cost_per_token_with_breakdown(
|
||||
model=model,
|
||||
usage=cost_per_token_usage_object,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
service_tier=service_tier,
|
||||
)
|
||||
_granular_kwargs = dict(
|
||||
input_cost_text=_breakdown["input_breakdown"]["text_cost"],
|
||||
input_cost_cache_read=_breakdown["input_breakdown"]["cache_read_cost"],
|
||||
input_cost_cache_creation=_breakdown["input_breakdown"]["cache_creation_cost"],
|
||||
input_cost_audio=_breakdown["input_breakdown"]["audio_cost"],
|
||||
output_cost_text=_breakdown["output_breakdown"]["text_cost"],
|
||||
output_cost_reasoning=_breakdown["output_breakdown"]["reasoning_cost"],
|
||||
output_cost_audio=_breakdown["output_breakdown"]["audio_cost"],
|
||||
input_tokens_text=_breakdown["prompt_tokens_details"]["text_tokens"],
|
||||
input_tokens_cache_read=_breakdown["prompt_tokens_details"]["cache_hit_tokens"],
|
||||
input_tokens_cache_creation=_breakdown["prompt_tokens_details"]["cache_creation_tokens"],
|
||||
input_tokens_audio=_breakdown["prompt_tokens_details"]["audio_tokens"],
|
||||
output_tokens_text=_breakdown["completion_tokens_details_parsed"]["text_tokens"],
|
||||
output_tokens_reasoning=_breakdown["completion_tokens_details_parsed"]["reasoning_tokens"],
|
||||
output_tokens_audio=_breakdown["completion_tokens_details_parsed"]["audio_tokens"],
|
||||
above_128k_tokens=_breakdown["above_128k_tokens"],
|
||||
above_200k_tokens=_breakdown["above_200k_tokens"],
|
||||
)
|
||||
except Exception as _granular_err:
|
||||
verbose_logger.debug(
|
||||
"Error computing granular cost breakdown: %s",
|
||||
str(_granular_err),
|
||||
)
|
||||
|
||||
# Store cost breakdown in logging object if available
|
||||
if litellm_logging_obj is not None:
|
||||
_store_cost_breakdown_in_logging_obj(
|
||||
|
|
@ -1635,7 +1550,6 @@ def completion_cost( # noqa: PLR0915
|
|||
margin_percent=margin_percent,
|
||||
margin_fixed_amount=margin_fixed_amount,
|
||||
margin_total_amount=margin_total_amount,
|
||||
**_granular_kwargs,
|
||||
)
|
||||
|
||||
return _final_cost
|
||||
|
|
|
|||
|
|
@ -1322,22 +1322,6 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
margin_percent: Optional[float] = None,
|
||||
margin_fixed_amount: Optional[float] = None,
|
||||
margin_total_amount: Optional[float] = None,
|
||||
input_cost_text: Optional[float] = None,
|
||||
input_cost_cache_read: Optional[float] = None,
|
||||
input_cost_cache_creation: Optional[float] = None,
|
||||
input_cost_audio: Optional[float] = None,
|
||||
output_cost_text: Optional[float] = None,
|
||||
output_cost_reasoning: Optional[float] = None,
|
||||
output_cost_audio: Optional[float] = None,
|
||||
input_tokens_text: Optional[int] = None,
|
||||
input_tokens_cache_read: Optional[int] = None,
|
||||
input_tokens_cache_creation: Optional[int] = None,
|
||||
input_tokens_audio: Optional[int] = None,
|
||||
output_tokens_text: Optional[int] = None,
|
||||
output_tokens_reasoning: Optional[int] = None,
|
||||
output_tokens_audio: Optional[int] = None,
|
||||
above_128k_tokens: Optional[bool] = None,
|
||||
above_200k_tokens: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Helper method to store cost breakdown in the logging object.
|
||||
|
|
@ -1347,24 +1331,13 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
output_cost: Cost of output/completion tokens
|
||||
cost_for_built_in_tools_cost_usd_dollar: Cost of built-in tools
|
||||
total_cost: Total cost of request
|
||||
additional_costs: Free-form additional costs dict
|
||||
additional_costs: Free-form additional costs dict (e.g., {"azure_model_router_flat_cost": 0.00014})
|
||||
original_cost: Cost before discount
|
||||
discount_percent: Discount percentage (0.05 = 5%)
|
||||
discount_amount: Discount amount in USD
|
||||
margin_percent: Margin percentage applied (0.10 = 10%)
|
||||
margin_fixed_amount: Fixed margin amount in USD
|
||||
margin_total_amount: Total margin added in USD
|
||||
input_cost_text: Cost for non-cached text input tokens
|
||||
input_cost_cache_read: Cost for cache read/hit tokens
|
||||
input_cost_cache_creation: Cost for cache creation/write tokens
|
||||
input_cost_audio: Cost for audio input tokens
|
||||
output_cost_text: Cost for text output tokens
|
||||
output_cost_reasoning: Cost for reasoning output tokens
|
||||
output_cost_audio: Cost for audio output tokens
|
||||
input_tokens_*: Token counts for each input category
|
||||
output_tokens_*: Token counts for each output category
|
||||
above_128k_tokens: Whether above-128K token pricing was used
|
||||
above_200k_tokens: Whether above-200K token pricing was used
|
||||
"""
|
||||
|
||||
self.cost_breakdown = CostBreakdown(
|
||||
|
|
@ -1398,129 +1371,6 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
if margin_total_amount is not None:
|
||||
self.cost_breakdown["margin_total_amount"] = margin_total_amount
|
||||
|
||||
# Store granular input cost breakdown
|
||||
if input_cost_text is not None:
|
||||
self.cost_breakdown["input_cost_text"] = input_cost_text
|
||||
if input_cost_cache_read is not None:
|
||||
self.cost_breakdown["input_cost_cache_read"] = input_cost_cache_read
|
||||
if input_cost_cache_creation is not None:
|
||||
self.cost_breakdown["input_cost_cache_creation"] = input_cost_cache_creation
|
||||
if input_cost_audio is not None:
|
||||
self.cost_breakdown["input_cost_audio"] = input_cost_audio
|
||||
|
||||
# Store granular output cost breakdown
|
||||
if output_cost_text is not None:
|
||||
self.cost_breakdown["output_cost_text"] = output_cost_text
|
||||
if output_cost_reasoning is not None:
|
||||
self.cost_breakdown["output_cost_reasoning"] = output_cost_reasoning
|
||||
if output_cost_audio is not None:
|
||||
self.cost_breakdown["output_cost_audio"] = output_cost_audio
|
||||
|
||||
# Store token counts
|
||||
if input_tokens_text is not None:
|
||||
self.cost_breakdown["input_tokens_text"] = input_tokens_text
|
||||
if input_tokens_cache_read is not None:
|
||||
self.cost_breakdown["input_tokens_cache_read"] = input_tokens_cache_read
|
||||
if input_tokens_cache_creation is not None:
|
||||
self.cost_breakdown["input_tokens_cache_creation"] = input_tokens_cache_creation
|
||||
if input_tokens_audio is not None:
|
||||
self.cost_breakdown["input_tokens_audio"] = input_tokens_audio
|
||||
if output_tokens_text is not None:
|
||||
self.cost_breakdown["output_tokens_text"] = output_tokens_text
|
||||
if output_tokens_reasoning is not None:
|
||||
self.cost_breakdown["output_tokens_reasoning"] = output_tokens_reasoning
|
||||
if output_tokens_audio is not None:
|
||||
self.cost_breakdown["output_tokens_audio"] = output_tokens_audio
|
||||
|
||||
# Store pricing tier indicators
|
||||
if above_128k_tokens is not None:
|
||||
self.cost_breakdown["above_128k_tokens"] = above_128k_tokens
|
||||
if above_200k_tokens is not None:
|
||||
self.cost_breakdown["above_200k_tokens"] = above_200k_tokens
|
||||
|
||||
def _populate_granular_cost_breakdown(self, result: Any) -> None:
|
||||
"""
|
||||
Compute and store granular cost breakdown (cache read/write, reasoning,
|
||||
web search, etc.) when it wasn't set by completion_cost (e.g., when
|
||||
response_cost was pre-computed in _hidden_params).
|
||||
"""
|
||||
try:
|
||||
from litellm.litellm_core_utils.llm_cost_calc.utils import (
|
||||
generic_cost_per_token_with_breakdown,
|
||||
)
|
||||
from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
|
||||
StandardBuiltInToolCostTracking,
|
||||
)
|
||||
from litellm.responses.utils import ResponseAPILoggingUtils
|
||||
|
||||
usage_obj = getattr(result, "usage", None)
|
||||
if usage_obj is None:
|
||||
return
|
||||
|
||||
# Ensure usage is in the chat usage format (with prompt_tokens)
|
||||
if isinstance(usage_obj, dict):
|
||||
if ResponseAPILoggingUtils._is_response_api_usage(usage_obj):
|
||||
usage_obj = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage_obj)
|
||||
elif "prompt_tokens" not in usage_obj:
|
||||
return
|
||||
elif hasattr(usage_obj, "input_tokens") and not hasattr(usage_obj, "prompt_tokens"):
|
||||
if ResponseAPILoggingUtils._is_response_api_usage(usage_obj):
|
||||
usage_obj = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(usage_obj)
|
||||
else:
|
||||
return
|
||||
|
||||
if not hasattr(usage_obj, "prompt_tokens"):
|
||||
return
|
||||
|
||||
custom_llm_provider = self.model_call_details.get("custom_llm_provider")
|
||||
model = self.model
|
||||
if custom_llm_provider is None or model is None:
|
||||
return
|
||||
|
||||
breakdown = generic_cost_per_token_with_breakdown(
|
||||
model=model,
|
||||
usage=usage_obj,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
# Compute tool costs
|
||||
tool_cost = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
|
||||
model=model,
|
||||
response_object=result,
|
||||
usage=usage_obj,
|
||||
standard_built_in_tools_params=self.standard_built_in_tools_params,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
total_cost = breakdown["prompt_cost"] + breakdown["completion_cost"] + tool_cost
|
||||
|
||||
self.set_cost_breakdown(
|
||||
input_cost=breakdown["prompt_cost"],
|
||||
output_cost=breakdown["completion_cost"],
|
||||
total_cost=total_cost,
|
||||
cost_for_built_in_tools_cost_usd_dollar=tool_cost,
|
||||
input_cost_text=breakdown["input_breakdown"]["text_cost"],
|
||||
input_cost_cache_read=breakdown["input_breakdown"]["cache_read_cost"],
|
||||
input_cost_cache_creation=breakdown["input_breakdown"]["cache_creation_cost"],
|
||||
input_cost_audio=breakdown["input_breakdown"]["audio_cost"],
|
||||
output_cost_text=breakdown["output_breakdown"]["text_cost"],
|
||||
output_cost_reasoning=breakdown["output_breakdown"]["reasoning_cost"],
|
||||
output_cost_audio=breakdown["output_breakdown"]["audio_cost"],
|
||||
input_tokens_text=breakdown["prompt_tokens_details"]["text_tokens"],
|
||||
input_tokens_cache_read=breakdown["prompt_tokens_details"]["cache_hit_tokens"],
|
||||
input_tokens_cache_creation=breakdown["prompt_tokens_details"]["cache_creation_tokens"],
|
||||
input_tokens_audio=breakdown["prompt_tokens_details"]["audio_tokens"],
|
||||
output_tokens_text=breakdown["completion_tokens_details_parsed"]["text_tokens"],
|
||||
output_tokens_reasoning=breakdown["completion_tokens_details_parsed"]["reasoning_tokens"],
|
||||
output_tokens_audio=breakdown["completion_tokens_details_parsed"]["audio_tokens"],
|
||||
above_128k_tokens=breakdown["above_128k_tokens"],
|
||||
above_200k_tokens=breakdown["above_200k_tokens"],
|
||||
)
|
||||
except Exception as e:
|
||||
verbose_logger.debug(
|
||||
"Failed to populate granular cost breakdown: %s", str(e)
|
||||
)
|
||||
|
||||
def _response_cost_calculator(
|
||||
self,
|
||||
result: Union[
|
||||
|
|
@ -1802,10 +1652,6 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
result=logging_result
|
||||
)
|
||||
|
||||
# Populate granular cost breakdown if not already set by completion_cost
|
||||
if self.cost_breakdown is None:
|
||||
self._populate_granular_cost_breakdown(logging_result)
|
||||
|
||||
self.model_call_details["standard_logging_object"] = (
|
||||
self._build_standard_logging_payload(logging_result, start_time, end_time)
|
||||
)
|
||||
|
|
@ -5364,21 +5210,6 @@ def _extract_response_obj_and_hidden_params(
|
|||
return response_obj, hidden_params
|
||||
|
||||
|
||||
def _compute_cost_breakdown_fallback(
|
||||
logging_obj: "Logging", response_obj: Any
|
||||
) -> Optional[CostBreakdown]:
|
||||
"""
|
||||
Compute granular cost breakdown when it wasn't set by the completion_cost
|
||||
path (e.g., when response_cost was pre-computed in _hidden_params).
|
||||
"""
|
||||
try:
|
||||
logging_obj._populate_granular_cost_breakdown(response_obj)
|
||||
return logging_obj.cost_breakdown
|
||||
except Exception as e:
|
||||
verbose_logger.debug("_compute_cost_breakdown_fallback failed: %s", str(e))
|
||||
return None
|
||||
|
||||
|
||||
def get_standard_logging_object_payload(
|
||||
kwargs: Optional[dict],
|
||||
init_response_obj: Union[Any, BaseModel, dict],
|
||||
|
|
@ -5543,7 +5374,7 @@ def get_standard_logging_object_payload(
|
|||
metadata=clean_metadata,
|
||||
cache_key=clean_hidden_params["cache_key"],
|
||||
response_cost=response_cost,
|
||||
cost_breakdown=logging_obj.cost_breakdown if logging_obj.cost_breakdown is not None else _compute_cost_breakdown_fallback(logging_obj, init_response_obj),
|
||||
cost_breakdown=logging_obj.cost_breakdown,
|
||||
total_tokens=usage_dict.get("total_tokens", 0),
|
||||
prompt_tokens=usage_dict.get("prompt_tokens", 0),
|
||||
completion_tokens=usage_dict.get("completion_tokens", 0),
|
||||
|
|
|
|||
|
|
@ -538,21 +538,6 @@ def _parse_completion_tokens_details(usage: Usage) -> CompletionTokensDetailsRes
|
|||
)
|
||||
|
||||
|
||||
class InputCostBreakdownResult(TypedDict):
|
||||
total: float
|
||||
text_cost: float
|
||||
cache_read_cost: float
|
||||
cache_creation_cost: float
|
||||
audio_cost: float
|
||||
|
||||
|
||||
class OutputCostBreakdownResult(TypedDict):
|
||||
total: float
|
||||
text_cost: float
|
||||
reasoning_cost: float
|
||||
audio_cost: float
|
||||
|
||||
|
||||
def _calculate_input_cost(
|
||||
prompt_tokens_details: PromptTokensDetailsResult,
|
||||
model_info: ModelInfo,
|
||||
|
|
@ -565,58 +550,34 @@ def _calculate_input_cost(
|
|||
"""
|
||||
Calculates the input cost for a given model, prompt tokens, and completion tokens.
|
||||
"""
|
||||
result = _calculate_input_cost_breakdown(
|
||||
prompt_tokens_details=prompt_tokens_details,
|
||||
model_info=model_info,
|
||||
prompt_base_cost=prompt_base_cost,
|
||||
cache_read_cost=cache_read_cost,
|
||||
cache_creation_cost=cache_creation_cost,
|
||||
cache_creation_cost_above_1hr=cache_creation_cost_above_1hr,
|
||||
service_tier=service_tier,
|
||||
)
|
||||
return result["total"]
|
||||
prompt_cost = float(prompt_tokens_details["text_tokens"]) * prompt_base_cost
|
||||
|
||||
|
||||
def _calculate_input_cost_breakdown(
|
||||
prompt_tokens_details: PromptTokensDetailsResult,
|
||||
model_info: ModelInfo,
|
||||
prompt_base_cost: float,
|
||||
cache_read_cost: float,
|
||||
cache_creation_cost: float,
|
||||
cache_creation_cost_above_1hr: float,
|
||||
service_tier: Optional[str] = None,
|
||||
) -> InputCostBreakdownResult:
|
||||
"""
|
||||
Calculates the input cost with a granular breakdown by token type.
|
||||
"""
|
||||
text_cost = float(prompt_tokens_details["text_tokens"]) * prompt_base_cost
|
||||
|
||||
### CACHE READ COST
|
||||
_cache_read_cost = float(prompt_tokens_details["cache_hit_tokens"]) * cache_read_cost
|
||||
### CACHE READ COST - Now uses tiered pricing
|
||||
prompt_cost += float(prompt_tokens_details["cache_hit_tokens"]) * cache_read_cost
|
||||
|
||||
### AUDIO COST
|
||||
_audio_cost = 0.0
|
||||
if prompt_tokens_details["audio_tokens"]:
|
||||
audio_cost_key = _get_service_tier_cost_key(
|
||||
"input_cost_per_audio_token", service_tier
|
||||
)
|
||||
_audio_cost = calculate_cost_component(
|
||||
prompt_cost += calculate_cost_component(
|
||||
model_info, audio_cost_key, prompt_tokens_details["audio_tokens"]
|
||||
)
|
||||
|
||||
### IMAGE TOKEN COST (folded into text_cost for simplicity)
|
||||
### IMAGE TOKEN COST
|
||||
if prompt_tokens_details["image_tokens"]:
|
||||
# For image token costs:
|
||||
# First check if input_cost_per_image_token is available. If not, default to generic input_cost_per_token.
|
||||
image_token_cost_key = "input_cost_per_image_token"
|
||||
if model_info.get(image_token_cost_key) is None:
|
||||
image_token_cost_key = "input_cost_per_token"
|
||||
text_cost += calculate_cost_component(
|
||||
prompt_cost += calculate_cost_component(
|
||||
model_info, image_token_cost_key, prompt_tokens_details["image_tokens"]
|
||||
)
|
||||
|
||||
### CACHE WRITING COST
|
||||
_cache_creation_cost = 0.0
|
||||
### CACHE WRITING COST - Now uses tiered pricing
|
||||
if prompt_tokens_details["cache_creation_tokens"] or prompt_tokens_details["cache_creation_token_details"] is not None:
|
||||
_cache_creation_cost = calculate_cache_writing_cost(
|
||||
prompt_cost += calculate_cache_writing_cost(
|
||||
cache_creation_tokens=prompt_tokens_details["cache_creation_tokens"],
|
||||
cache_creation_token_details=prompt_tokens_details[
|
||||
"cache_creation_token_details"
|
||||
|
|
@ -625,35 +586,27 @@ def _calculate_input_cost_breakdown(
|
|||
cache_creation_cost=cache_creation_cost,
|
||||
)
|
||||
|
||||
### CHARACTER COST (folded into text_cost)
|
||||
### CHARACTER COST
|
||||
if prompt_tokens_details["character_count"]:
|
||||
text_cost += calculate_cost_component(
|
||||
prompt_cost += calculate_cost_component(
|
||||
model_info, "input_cost_per_character", prompt_tokens_details["character_count"]
|
||||
)
|
||||
|
||||
### IMAGE COUNT COST (folded into text_cost)
|
||||
### IMAGE COUNT COST
|
||||
if prompt_tokens_details["image_count"]:
|
||||
text_cost += calculate_cost_component(
|
||||
prompt_cost += calculate_cost_component(
|
||||
model_info, "input_cost_per_image", prompt_tokens_details["image_count"]
|
||||
)
|
||||
|
||||
### VIDEO LENGTH COST (folded into text_cost)
|
||||
### VIDEO LENGTH COST
|
||||
if prompt_tokens_details["video_length_seconds"]:
|
||||
text_cost += calculate_cost_component(
|
||||
prompt_cost += calculate_cost_component(
|
||||
model_info,
|
||||
"input_cost_per_video_per_second",
|
||||
prompt_tokens_details["video_length_seconds"],
|
||||
)
|
||||
|
||||
total = text_cost + _cache_read_cost + _cache_creation_cost + _audio_cost
|
||||
|
||||
return InputCostBreakdownResult(
|
||||
total=total,
|
||||
text_cost=text_cost,
|
||||
cache_read_cost=_cache_read_cost,
|
||||
cache_creation_cost=_cache_creation_cost,
|
||||
audio_cost=_audio_cost,
|
||||
)
|
||||
return prompt_cost
|
||||
|
||||
|
||||
def generic_cost_per_token( # noqa: PLR0915
|
||||
|
|
@ -816,176 +769,6 @@ def generic_cost_per_token( # noqa: PLR0915
|
|||
return prompt_cost, completion_cost
|
||||
|
||||
|
||||
class CostPerTokenBreakdown(TypedDict):
|
||||
prompt_cost: float
|
||||
completion_cost: float
|
||||
input_breakdown: InputCostBreakdownResult
|
||||
output_breakdown: OutputCostBreakdownResult
|
||||
prompt_tokens_details: PromptTokensDetailsResult
|
||||
completion_tokens_details_parsed: CompletionTokensDetailsResult
|
||||
above_128k_tokens: bool
|
||||
above_200k_tokens: bool
|
||||
|
||||
|
||||
def generic_cost_per_token_with_breakdown(
|
||||
model: str,
|
||||
usage: Usage,
|
||||
custom_llm_provider: str,
|
||||
service_tier: Optional[str] = None,
|
||||
) -> CostPerTokenBreakdown:
|
||||
"""
|
||||
Same as generic_cost_per_token but returns a detailed breakdown of costs
|
||||
by token type (text, cache read, cache creation, reasoning, audio, etc.).
|
||||
"""
|
||||
model_info = get_model_info(model=model, custom_llm_provider=custom_llm_provider)
|
||||
|
||||
prompt_tokens_details = PromptTokensDetailsResult(
|
||||
cache_hit_tokens=0,
|
||||
cache_creation_tokens=0,
|
||||
cache_creation_token_details=None,
|
||||
text_tokens=usage.prompt_tokens,
|
||||
audio_tokens=0,
|
||||
image_tokens=0,
|
||||
character_count=0,
|
||||
image_count=0,
|
||||
video_length_seconds=0.0,
|
||||
)
|
||||
if usage.prompt_tokens_details:
|
||||
prompt_tokens_details = _parse_prompt_tokens_details(usage)
|
||||
|
||||
cache_hit = prompt_tokens_details["cache_hit_tokens"]
|
||||
text_tokens = prompt_tokens_details["text_tokens"]
|
||||
audio_tokens = prompt_tokens_details["audio_tokens"]
|
||||
cache_creation = prompt_tokens_details["cache_creation_tokens"]
|
||||
image_tokens = prompt_tokens_details["image_tokens"]
|
||||
|
||||
total_details = text_tokens + cache_hit + audio_tokens + cache_creation + image_tokens
|
||||
has_double_counting = cache_hit > 0 and total_details > usage.prompt_tokens
|
||||
|
||||
if (text_tokens == 0 and prompt_tokens_details["image_count"] == 0) or has_double_counting:
|
||||
text_tokens = (
|
||||
usage.prompt_tokens
|
||||
- cache_hit
|
||||
- audio_tokens
|
||||
- cache_creation
|
||||
- image_tokens
|
||||
)
|
||||
prompt_tokens_details["text_tokens"] = text_tokens
|
||||
|
||||
(
|
||||
prompt_base_cost,
|
||||
completion_base_cost,
|
||||
cache_creation_cost,
|
||||
cache_creation_cost_above_1hr,
|
||||
cache_read_cost,
|
||||
) = _get_token_base_cost(
|
||||
model_info=model_info, usage=usage, service_tier=service_tier
|
||||
)
|
||||
|
||||
# Detect pricing tier
|
||||
above_128k = False
|
||||
above_200k = False
|
||||
threshold_keys = [
|
||||
k for k in model_info
|
||||
if k.startswith("input_cost_per_token_above_")
|
||||
and not any(k.endswith(f"_{st.value}") for st in ServiceTier)
|
||||
]
|
||||
for key in sorted(threshold_keys, reverse=True):
|
||||
value = model_info.get(key)
|
||||
if value is not None:
|
||||
try:
|
||||
threshold_str = key.split("_above_")[1].split("_tokens")[0]
|
||||
threshold = float(threshold_str.replace("k", "")) * (
|
||||
1000 if "k" in threshold_str else 1
|
||||
)
|
||||
if usage.prompt_tokens > threshold:
|
||||
if threshold >= 200000:
|
||||
above_200k = True
|
||||
if threshold >= 128000:
|
||||
above_128k = True
|
||||
break
|
||||
except (IndexError, ValueError):
|
||||
continue
|
||||
|
||||
input_breakdown = _calculate_input_cost_breakdown(
|
||||
prompt_tokens_details=prompt_tokens_details,
|
||||
model_info=model_info,
|
||||
prompt_base_cost=prompt_base_cost,
|
||||
cache_read_cost=cache_read_cost,
|
||||
cache_creation_cost=cache_creation_cost,
|
||||
cache_creation_cost_above_1hr=cache_creation_cost_above_1hr,
|
||||
service_tier=service_tier,
|
||||
)
|
||||
|
||||
# Output cost breakdown
|
||||
out_text_tokens = 0
|
||||
out_audio_tokens = 0
|
||||
out_reasoning_tokens = 0
|
||||
out_image_tokens = 0
|
||||
is_text_tokens_total = False
|
||||
if usage.completion_tokens_details is not None:
|
||||
ctd = _parse_completion_tokens_details(usage)
|
||||
out_audio_tokens = ctd["audio_tokens"]
|
||||
out_text_tokens = ctd["text_tokens"]
|
||||
out_reasoning_tokens = ctd["reasoning_tokens"]
|
||||
out_image_tokens = ctd["image_tokens"]
|
||||
|
||||
has_token_breakdown = out_image_tokens > 0 or out_audio_tokens > 0 or out_reasoning_tokens > 0
|
||||
if out_text_tokens == 0:
|
||||
if has_token_breakdown:
|
||||
out_text_tokens = max(
|
||||
0,
|
||||
usage.completion_tokens - out_reasoning_tokens - out_audio_tokens - out_image_tokens,
|
||||
)
|
||||
else:
|
||||
out_text_tokens = usage.completion_tokens
|
||||
is_text_tokens_total = True
|
||||
|
||||
out_text_cost = float(out_text_tokens) * completion_base_cost
|
||||
out_audio_cost = 0.0
|
||||
out_reasoning_cost = 0.0
|
||||
|
||||
if not is_text_tokens_total and out_audio_tokens > 0:
|
||||
_ocpat = _get_cost_per_unit(model_info, "output_cost_per_audio_token", None)
|
||||
_ocpat = _ocpat if _ocpat is not None else completion_base_cost
|
||||
out_audio_cost = float(out_audio_tokens) * _ocpat
|
||||
|
||||
if not is_text_tokens_total and out_reasoning_tokens > 0:
|
||||
_ocprt = _get_cost_per_unit(model_info, "output_cost_per_reasoning_token", None)
|
||||
_ocprt = _ocprt if _ocprt is not None else completion_base_cost
|
||||
out_reasoning_cost = float(out_reasoning_tokens) * _ocprt
|
||||
|
||||
if not is_text_tokens_total and out_image_tokens > 0:
|
||||
_ocpit = _get_cost_per_unit(model_info, "output_cost_per_image_token", None)
|
||||
_ocpit = _ocpit if _ocpit is not None else completion_base_cost
|
||||
out_text_cost += float(out_image_tokens) * _ocpit
|
||||
|
||||
output_breakdown = OutputCostBreakdownResult(
|
||||
total=out_text_cost + out_reasoning_cost + out_audio_cost,
|
||||
text_cost=out_text_cost,
|
||||
reasoning_cost=out_reasoning_cost,
|
||||
audio_cost=out_audio_cost,
|
||||
)
|
||||
|
||||
completion_tokens_details_parsed = CompletionTokensDetailsResult(
|
||||
audio_tokens=out_audio_tokens,
|
||||
text_tokens=out_text_tokens,
|
||||
reasoning_tokens=out_reasoning_tokens,
|
||||
image_tokens=out_image_tokens,
|
||||
)
|
||||
|
||||
return CostPerTokenBreakdown(
|
||||
prompt_cost=input_breakdown["total"],
|
||||
completion_cost=output_breakdown["total"],
|
||||
input_breakdown=input_breakdown,
|
||||
output_breakdown=output_breakdown,
|
||||
prompt_tokens_details=prompt_tokens_details,
|
||||
completion_tokens_details_parsed=completion_tokens_details_parsed,
|
||||
above_128k_tokens=above_128k,
|
||||
above_200k_tokens=above_200k,
|
||||
)
|
||||
|
||||
|
||||
def calculate_image_response_cost_from_usage(
|
||||
model: str,
|
||||
image_response: ImageResponse,
|
||||
|
|
|
|||
|
|
@ -2736,30 +2736,6 @@ class CostBreakdown(TypedDict, total=False):
|
|||
margin_fixed_amount: float # Fixed margin amount in USD (optional)
|
||||
margin_total_amount: float # Total margin added in USD (optional)
|
||||
|
||||
# Granular input cost breakdown
|
||||
input_cost_text: float # Cost for non-cached text input tokens
|
||||
input_cost_cache_read: float # Cost for cache read/hit tokens
|
||||
input_cost_cache_creation: float # Cost for cache creation/write tokens
|
||||
input_cost_audio: float # Cost for audio input tokens
|
||||
|
||||
# Granular output cost breakdown
|
||||
output_cost_text: float # Cost for text output tokens
|
||||
output_cost_reasoning: float # Cost for reasoning output tokens
|
||||
output_cost_audio: float # Cost for audio output tokens
|
||||
|
||||
# Token counts for granular breakdown
|
||||
input_tokens_text: int # Number of non-cached text input tokens
|
||||
input_tokens_cache_read: int # Number of cache read/hit tokens
|
||||
input_tokens_cache_creation: int # Number of cache creation/write tokens
|
||||
input_tokens_audio: int # Number of audio input tokens
|
||||
output_tokens_text: int # Number of text output tokens
|
||||
output_tokens_reasoning: int # Number of reasoning output tokens
|
||||
output_tokens_audio: int # Number of audio output tokens
|
||||
|
||||
# Pricing tier indicator
|
||||
above_128k_tokens: bool # Whether above-128K token pricing was used
|
||||
above_200k_tokens: bool # Whether above-200K token pricing was used
|
||||
|
||||
|
||||
class StandardLoggingPayloadStatusFields(TypedDict, total=False):
|
||||
"""Status fields for easy filtering and analytics"""
|
||||
|
|
|
|||
|
|
@ -14,30 +14,6 @@ export interface CostBreakdown {
|
|||
margin_percent?: number;
|
||||
margin_fixed_amount?: number;
|
||||
margin_total_amount?: number;
|
||||
|
||||
// Granular input cost breakdown
|
||||
input_cost_text?: number;
|
||||
input_cost_cache_read?: number;
|
||||
input_cost_cache_creation?: number;
|
||||
input_cost_audio?: number;
|
||||
|
||||
// Granular output cost breakdown
|
||||
output_cost_text?: number;
|
||||
output_cost_reasoning?: number;
|
||||
output_cost_audio?: number;
|
||||
|
||||
// Token counts for granular breakdown
|
||||
input_tokens_text?: number;
|
||||
input_tokens_cache_read?: number;
|
||||
input_tokens_cache_creation?: number;
|
||||
input_tokens_audio?: number;
|
||||
output_tokens_text?: number;
|
||||
output_tokens_reasoning?: number;
|
||||
output_tokens_audio?: number;
|
||||
|
||||
// Pricing tier indicators
|
||||
above_128k_tokens?: boolean;
|
||||
above_200k_tokens?: boolean;
|
||||
}
|
||||
|
||||
interface CostBreakdownViewerProps {
|
||||
|
|
@ -100,24 +76,6 @@ export const CostBreakdownViewer: React.FC<CostBreakdownViewerProps> = ({
|
|||
const originalCost = isCached ? 0 : costBreakdown?.original_cost;
|
||||
const totalCost = isCached ? 0 : (costBreakdown?.total_cost ?? totalSpend);
|
||||
|
||||
const hasGranularInput =
|
||||
!isCached &&
|
||||
costBreakdown &&
|
||||
(costBreakdown.input_cost_cache_read !== undefined ||
|
||||
costBreakdown.input_cost_cache_creation !== undefined ||
|
||||
costBreakdown.input_cost_audio !== undefined);
|
||||
|
||||
const hasGranularOutput =
|
||||
!isCached &&
|
||||
costBreakdown &&
|
||||
(costBreakdown.output_cost_reasoning !== undefined ||
|
||||
costBreakdown.output_cost_audio !== undefined);
|
||||
|
||||
const hasPricingTier =
|
||||
!isCached &&
|
||||
costBreakdown &&
|
||||
(costBreakdown.above_128k_tokens || costBreakdown.above_200k_tokens);
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow w-full max-w-full overflow-hidden mb-6">
|
||||
<Collapse
|
||||
|
|
@ -139,18 +97,7 @@ export const CostBreakdownViewer: React.FC<CostBreakdownViewerProps> = ({
|
|||
),
|
||||
children: (
|
||||
<div className="p-6 space-y-4">
|
||||
{/* Pricing tier badge */}
|
||||
{hasPricingTier && (
|
||||
<div className="mb-2">
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
{costBreakdown?.above_200k_tokens
|
||||
? "Above 200K Token Pricing"
|
||||
: "Above 128K Token Pricing"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input Cost Section */}
|
||||
{/* Step 1: Base Token Costs */}
|
||||
<div className="space-y-2 max-w-2xl">
|
||||
<div className="flex text-sm">
|
||||
<span className="text-gray-600 font-medium w-1/3">Input Cost:</span>
|
||||
|
|
@ -163,76 +110,6 @@ export const CostBreakdownViewer: React.FC<CostBreakdownViewerProps> = ({
|
|||
)}
|
||||
</span>
|
||||
</div>
|
||||
{/* Granular input breakdown */}
|
||||
{hasGranularInput && (
|
||||
<div className="ml-6 space-y-1 border-l-2 border-gray-200 pl-3">
|
||||
{costBreakdown.input_cost_text !== undefined && costBreakdown.input_cost_text > 0 && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Text Tokens:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(costBreakdown.input_cost_text)}
|
||||
{costBreakdown.input_tokens_text !== undefined && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.input_tokens_text.toLocaleString()} tokens)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{costBreakdown.input_cost_cache_read !== undefined && costBreakdown.input_cost_cache_read > 0 && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Cache Read:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(costBreakdown.input_cost_cache_read)}
|
||||
{costBreakdown.input_tokens_cache_read !== undefined && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.input_tokens_cache_read.toLocaleString()} tokens)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{costBreakdown.input_tokens_cache_read !== undefined && costBreakdown.input_tokens_cache_read > 0 && (costBreakdown.input_cost_cache_read === undefined || costBreakdown.input_cost_cache_read === 0) && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Cache Read:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(0)}
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.input_tokens_cache_read.toLocaleString()} tokens — free)
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{costBreakdown.input_cost_cache_creation !== undefined && costBreakdown.input_cost_cache_creation > 0 && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Cache Write:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(costBreakdown.input_cost_cache_creation)}
|
||||
{costBreakdown.input_tokens_cache_creation !== undefined && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.input_tokens_cache_creation.toLocaleString()} tokens)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{costBreakdown.input_cost_audio !== undefined && costBreakdown.input_cost_audio > 0 && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Audio:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(costBreakdown.input_cost_audio)}
|
||||
{costBreakdown.input_tokens_audio !== undefined && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.input_tokens_audio.toLocaleString()} tokens)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Output Cost */}
|
||||
<div className="flex text-sm">
|
||||
<span className="text-gray-600 font-medium w-1/3">Output Cost:</span>
|
||||
<span className="text-gray-900">
|
||||
|
|
@ -244,55 +121,9 @@ export const CostBreakdownViewer: React.FC<CostBreakdownViewerProps> = ({
|
|||
)}
|
||||
</span>
|
||||
</div>
|
||||
{/* Granular output breakdown */}
|
||||
{hasGranularOutput && (
|
||||
<div className="ml-6 space-y-1 border-l-2 border-gray-200 pl-3">
|
||||
{costBreakdown.output_cost_text !== undefined && costBreakdown.output_cost_text > 0 && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Text Tokens:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(costBreakdown.output_cost_text)}
|
||||
{costBreakdown.output_tokens_text !== undefined && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.output_tokens_text.toLocaleString()} tokens)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{costBreakdown.output_cost_reasoning !== undefined && costBreakdown.output_cost_reasoning > 0 && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Reasoning:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(costBreakdown.output_cost_reasoning)}
|
||||
{costBreakdown.output_tokens_reasoning !== undefined && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.output_tokens_reasoning.toLocaleString()} tokens)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{costBreakdown.output_cost_audio !== undefined && costBreakdown.output_cost_audio > 0 && (
|
||||
<div className="flex text-xs">
|
||||
<span className="text-gray-500 w-1/3">Audio:</span>
|
||||
<span className="text-gray-700">
|
||||
{formatCost(costBreakdown.output_cost_audio)}
|
||||
{costBreakdown.output_tokens_audio !== undefined && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
({costBreakdown.output_tokens_audio.toLocaleString()} tokens)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Web Search / Tool Usage Cost */}
|
||||
{costBreakdown?.tool_usage_cost !== undefined && costBreakdown.tool_usage_cost > 0 && (
|
||||
<div className="flex text-sm">
|
||||
<span className="text-gray-600 font-medium w-1/3">Web Search / Tool Cost:</span>
|
||||
<span className="text-gray-600 font-medium w-1/3">Tool Usage Cost:</span>
|
||||
<span className="text-gray-900">{formatCost(costBreakdown.tool_usage_cost)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue