diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 9b4dd80265c3..d411c0822fa4 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -173,17 +173,45 @@ def _cost_per_token_custom_pricing_helper( prompt_tokens: float = 0, completion_tokens: float = 0, response_time_ms: Optional[float] = 0.0, + cached_tokens: float = 0, + cache_creation_tokens: float = 0, ### CUSTOM PRICING ### custom_cost_per_token: Optional[CostPerToken] = None, custom_cost_per_second: Optional[float] = None, ) -> Optional[Tuple[float, float]]: - """Internal helper function for calculating cost, if custom pricing given""" + """Internal helper function for calculating cost, if custom pricing given. + + prompt_tokens is assumed to include both cached_tokens and cache_creation_tokens + (OpenAI-compatible convention). Anthropic-style usage where prompt_tokens excludes + cache tokens is handled at the caller (cost_per_token) before invoking this helper. + """ if custom_cost_per_token is None and custom_cost_per_second is None: return None if custom_cost_per_token is not None: - input_cost = custom_cost_per_token["input_cost_per_token"] * prompt_tokens - output_cost = custom_cost_per_token["output_cost_per_token"] * completion_tokens + input_cost_per_token = custom_cost_per_token["input_cost_per_token"] + output_cost_per_token = custom_cost_per_token["output_cost_per_token"] + + cache_read_input_token_cost = custom_cost_per_token.get( + "cache_read_input_token_cost", + input_cost_per_token, + ) + cache_creation_input_token_cost = custom_cost_per_token.get( + "cache_creation_input_token_cost", + input_cost_per_token, + ) + + regular_prompt_tokens = max( + prompt_tokens - cached_tokens - cache_creation_tokens, + 0, + ) + + input_cost = ( + regular_prompt_tokens * input_cost_per_token + + cached_tokens * cache_read_input_token_cost + + cache_creation_tokens * cache_creation_input_token_cost + ) + output_cost = completion_tokens * output_cost_per_token return input_cost, output_cost elif custom_cost_per_second is not None: output_cost = custom_cost_per_second * response_time_ms / 1000 # type: ignore @@ -323,10 +351,56 @@ def cost_per_token( # noqa: PLR0915 ) ## CUSTOM PRICING ## + # Normalize cache token counts across providers: + # - OpenAI-compatible: usage.prompt_tokens_details.cached_tokens + # (prompt_tokens already INCLUDES cached_tokens) + # - Anthropic: usage.cache_read_input_tokens / cache_creation_input_tokens + # (prompt_tokens does NOT include these — adjust before calling helper) + _cache_read_tokens: float = 0 + _cache_creation_tokens: float = 0 + _is_anthropic_style = False + + if usage_object is not None: + _pt_details = getattr(usage_object, "prompt_tokens_details", None) + if _pt_details is not None: + _cache_read_tokens = float(getattr(_pt_details, "cached_tokens", 0) or 0) + # OpenAI-compatible providers report cache-write tokens under + # either `cache_creation_tokens` or `cache_write_tokens` (kimi-k2 + # uses the latter). Mirror db_spend_update_writer to stay symmetric. + _cache_creation_tokens = float( + getattr(_pt_details, "cache_creation_tokens", 0) + or getattr(_pt_details, "cache_write_tokens", 0) + or 0 + ) + + _anthropic_read = getattr(usage_object, "cache_read_input_tokens", None) + _anthropic_create = getattr(usage_object, "cache_creation_input_tokens", None) + if _anthropic_read or _anthropic_create: + _is_anthropic_style = True + if _anthropic_read: + _cache_read_tokens = float(_anthropic_read) + if _anthropic_create: + _cache_creation_tokens = float(_anthropic_create) + + if not _cache_read_tokens and cache_read_input_tokens: + _cache_read_tokens = float(cache_read_input_tokens) + _is_anthropic_style = True + if not _cache_creation_tokens and cache_creation_input_tokens: + _cache_creation_tokens = float(cache_creation_input_tokens) + _is_anthropic_style = True + + # Anthropic reports prompt_tokens as input_tokens (excluding cache tokens). + # Adjust so the helper's "prompt_tokens includes cache tokens" invariant holds. + _normalized_prompt_tokens = float(prompt_tokens) + if _is_anthropic_style: + _normalized_prompt_tokens += _cache_read_tokens + _cache_creation_tokens + response_cost = _cost_per_token_custom_pricing_helper( - prompt_tokens=prompt_tokens, + prompt_tokens=_normalized_prompt_tokens, completion_tokens=completion_tokens, response_time_ms=response_time_ms, + cached_tokens=_cache_read_tokens, + cache_creation_tokens=_cache_creation_tokens, custom_cost_per_second=custom_cost_per_second, custom_cost_per_token=custom_cost_per_token, ) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 7611c9c96922..e7f14df5294d 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -69,6 +69,35 @@ ProxyLogging = Any +def _extract_cache_read_tokens(usage_obj: dict) -> int: + """ + Anthropic: top-level cache_read_input_tokens field. + OpenAI-compatible (moonshotai, openai, deepseek, etc.): prompt_tokens_details.cached_tokens. + """ + explicit = usage_obj.get("cache_read_input_tokens", 0) or 0 + if explicit: + return int(explicit) + details = usage_obj.get("prompt_tokens_details") or {} + return int(details.get("cached_tokens", 0) or 0) + + +def _extract_cache_creation_tokens(usage_obj: dict) -> int: + """ + Anthropic: top-level cache_creation_input_tokens field. + OpenAI-compatible (kimi-k2 etc.): prompt_tokens_details.cache_write_tokens + or prompt_tokens_details.cache_creation_tokens. + """ + explicit = usage_obj.get("cache_creation_input_tokens", 0) or 0 + if explicit: + return int(explicit) + details = usage_obj.get("prompt_tokens_details") or {} + return int( + details.get("cache_write_tokens", 0) + or details.get("cache_creation_tokens", 0) + or 0 + ) + + class DBSpendUpdateWriter: """ Module responsible for @@ -1992,12 +2021,8 @@ async def _common_add_spend_log_transaction_to_daily_transaction( api_requests=1, successful_requests=1 if request_status == "success" else 0, failed_requests=1 if request_status != "success" else 0, - cache_read_input_tokens=usage_obj.get("cache_read_input_tokens", 0) - or 0, - cache_creation_input_tokens=usage_obj.get( - "cache_creation_input_tokens", 0 - ) - or 0, + cache_read_input_tokens=_extract_cache_read_tokens(usage_obj), + cache_creation_input_tokens=_extract_cache_creation_tokens(usage_obj), ) return daily_transaction except Exception as e: diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 400edcac8891..6084f14e2df9 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -107,9 +107,13 @@ class LiteLLMCommonStrings(Enum): SupportedCacheControls = ["ttl", "s-maxage", "no-cache", "no-store"] -class CostPerToken(TypedDict): - input_cost_per_token: float - output_cost_per_token: float +class CostPerToken(TypedDict, total=False): + # Required base rates — kept under total=False so we can mark them + # Required individually while leaving the cache rates NotRequired. + input_cost_per_token: Required[float] + output_cost_per_token: Required[float] + cache_read_input_token_cost: float + cache_creation_input_token_cost: float class ProviderField(TypedDict): diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index d5c1132c5fdd..1be4abbec6ef 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -2057,3 +2057,317 @@ def test_openrouter_gemini_3_1_flash_lite_preview_pricing(): assert model_info["output_cost_per_token"] == 1.5e-06 assert model_info["max_input_tokens"] == 1048576 assert model_info["max_output_tokens"] == 65536 + + +def test_custom_pricing_applies_cache_read_input_cost(): + """ + Bug 1 reproduction: custom_cost_per_token with cache_read_input_token_cost + should bill cached prompt tokens at the cache rate, not the full input rate. + """ + usage = Usage( + prompt_tokens=6074, + completion_tokens=285, + total_tokens=6359, + prompt_tokens_details=PromptTokensDetailsWrapper( + cached_tokens=3456, + audio_tokens=0, + ), + ) + + response = ModelResponse( + id="test-id", + created=1234567890, + model="openai/gpt-5.4", + object="chat.completion", + choices=[], + usage=usage, + ) + + cost = litellm.completion_cost( + completion_response=response, + model="openai/gpt-5.4", + custom_llm_provider="openai", + custom_cost_per_token={ + "input_cost_per_token": 0.0000025, + "output_cost_per_token": 0.000015, + "cache_read_input_token_cost": 0.00000025, + }, + ) + + expected = (6074 - 3456) * 0.0000025 + 3456 * 0.00000025 + 285 * 0.000015 + + assert cost == pytest.approx(expected) + + +def test_custom_pricing_applies_cache_creation_input_cost_via_prompt_details(): + """ + OpenAI-compatible providers report cache-write tokens under + prompt_tokens_details.cache_creation_tokens. The custom-pricing helper must + bill those at cache_creation_input_token_cost, not the full input rate. + """ + pt_details = PromptTokensDetailsWrapper(cached_tokens=1000, audio_tokens=0) + pt_details.cache_creation_tokens = 500 + + usage = Usage( + prompt_tokens=4000, + completion_tokens=100, + total_tokens=4100, + prompt_tokens_details=pt_details, + ) + + response = ModelResponse( + id="test-id", + created=1234567890, + model="openai/gpt-5.4", + object="chat.completion", + choices=[], + usage=usage, + ) + + cost = litellm.completion_cost( + completion_response=response, + model="openai/gpt-5.4", + custom_llm_provider="openai", + custom_cost_per_token={ + "input_cost_per_token": 0.0000025, + "output_cost_per_token": 0.000015, + "cache_read_input_token_cost": 0.00000025, + "cache_creation_input_token_cost": 0.000003125, + }, + ) + + expected = ( + (4000 - 1000 - 500) * 0.0000025 + + 1000 * 0.00000025 + + 500 * 0.000003125 + + 100 * 0.000015 + ) + + assert cost == pytest.approx(expected) + + +def test_custom_pricing_applies_cache_creation_input_cost_via_cache_write_tokens_alias(): + """ + Some OpenAI-compatible providers (e.g. kimi-k2) emit cache-write tokens as + `cache_write_tokens` rather than `cache_creation_tokens`. The cost + calculator must mirror db_spend_update_writer and accept either name — + otherwise daily aggregation counts the tokens but the per-request cost + bills them at the full input rate. + + Drives `cost_per_token` directly with a SimpleNamespace usage stub so the + `cache_write_tokens` alias survives the call (Pydantic's Usage init + rebuilds prompt_tokens_details and drops dynamic attributes). + """ + from types import SimpleNamespace + + from litellm.cost_calculator import cost_per_token + + pt_details = SimpleNamespace(cached_tokens=1000, cache_write_tokens=500) + usage_stub = SimpleNamespace( + prompt_tokens=4000, + completion_tokens=100, + total_tokens=4100, + prompt_tokens_details=pt_details, + cache_read_input_tokens=None, + cache_creation_input_tokens=None, + ) + + prompt_cost, completion_cost = cost_per_token( + model="moonshotai/kimi-k2", + prompt_tokens=4000, + completion_tokens=100, + custom_llm_provider="openai", + usage_object=usage_stub, + custom_cost_per_token={ + "input_cost_per_token": 0.0000025, + "output_cost_per_token": 0.000015, + "cache_read_input_token_cost": 0.00000025, + "cache_creation_input_token_cost": 0.000003125, + }, + ) + + expected_prompt = ( + (4000 - 1000 - 500) * 0.0000025 + 1000 * 0.00000025 + 500 * 0.000003125 + ) + expected_completion = 100 * 0.000015 + + assert prompt_cost == pytest.approx(expected_prompt) + assert completion_cost == pytest.approx(expected_completion) + + +# --------------------------------------------------------------------------- +# Bug 2 — db_spend_update_writer cache token extraction helpers. +# --------------------------------------------------------------------------- + + +def test_extract_cache_read_tokens_anthropic_top_level(): + from litellm.proxy.db.db_spend_update_writer import _extract_cache_read_tokens + + usage_obj = { + "prompt_tokens": 100, + "cache_read_input_tokens": 80, + "prompt_tokens_details": {"cached_tokens": 80}, + } + # Anthropic top-level value should win over prompt_tokens_details fallback. + assert _extract_cache_read_tokens(usage_obj) == 80 + + +def test_extract_cache_read_tokens_openai_compatible_fallback(): + from litellm.proxy.db.db_spend_update_writer import _extract_cache_read_tokens + + # Anthropic field absent — fall back to prompt_tokens_details.cached_tokens. + usage_obj = { + "prompt_tokens": 22583, + "prompt_tokens_details": {"cached_tokens": 22016}, + } + assert _extract_cache_read_tokens(usage_obj) == 22016 + + +def test_extract_cache_read_tokens_zero_when_missing(): + from litellm.proxy.db.db_spend_update_writer import _extract_cache_read_tokens + + assert _extract_cache_read_tokens({}) == 0 + assert _extract_cache_read_tokens({"cache_read_input_tokens": None}) == 0 + assert ( + _extract_cache_read_tokens({"prompt_tokens_details": {"cached_tokens": None}}) + == 0 + ) + + +def test_extract_cache_creation_tokens_anthropic_top_level(): + from litellm.proxy.db.db_spend_update_writer import ( + _extract_cache_creation_tokens, + ) + + usage_obj = { + "prompt_tokens": 100, + "cache_creation_input_tokens": 50, + "prompt_tokens_details": {"cache_write_tokens": 50}, + } + # Anthropic top-level should short-circuit the fallback. + assert _extract_cache_creation_tokens(usage_obj) == 50 + + +def test_extract_cache_creation_tokens_openai_cache_write_alias(): + from litellm.proxy.db.db_spend_update_writer import ( + _extract_cache_creation_tokens, + ) + + # kimi-k2 emits cache_write_tokens. + usage_obj = { + "prompt_tokens": 1000, + "prompt_tokens_details": {"cache_write_tokens": 200}, + } + assert _extract_cache_creation_tokens(usage_obj) == 200 + + +def test_extract_cache_creation_tokens_openai_cache_creation_alias(): + from litellm.proxy.db.db_spend_update_writer import ( + _extract_cache_creation_tokens, + ) + + # Other OpenAI-compatible providers emit cache_creation_tokens. + usage_obj = { + "prompt_tokens": 1000, + "prompt_tokens_details": {"cache_creation_tokens": 300}, + } + assert _extract_cache_creation_tokens(usage_obj) == 300 + + +def test_extract_cache_creation_tokens_zero_when_missing(): + from litellm.proxy.db.db_spend_update_writer import ( + _extract_cache_creation_tokens, + ) + + assert _extract_cache_creation_tokens({}) == 0 + assert _extract_cache_creation_tokens({"cache_creation_input_tokens": None}) == 0 + assert ( + _extract_cache_creation_tokens( + {"prompt_tokens_details": {"cache_write_tokens": None}} + ) + == 0 + ) + + +def test_custom_pricing_anthropic_style_cache_tokens_not_double_counted(): + """ + Anthropic providers report cache tokens at the top level of Usage, and + `prompt_tokens` EXCLUDES them. The helper expects `prompt_tokens` to + include cache tokens, so cost_per_token must adjust before invoking it — + otherwise regular_prompt_tokens goes negative and clamps to 0. + """ + usage = Usage( + prompt_tokens=2000, + completion_tokens=100, + total_tokens=2100, + cache_read_input_tokens=1500, + cache_creation_input_tokens=300, + ) + + response = ModelResponse( + id="test-id", + created=1234567890, + model="anthropic/claude-3-5-sonnet", + object="chat.completion", + choices=[], + usage=usage, + ) + + cost = litellm.completion_cost( + completion_response=response, + model="anthropic/claude-3-5-sonnet", + custom_llm_provider="anthropic", + custom_cost_per_token={ + "input_cost_per_token": 0.000003, + "output_cost_per_token": 0.000015, + "cache_read_input_token_cost": 0.0000003, + "cache_creation_input_token_cost": 0.00000375, + }, + ) + + # Anthropic prompt_tokens=2000 excludes cache. After normalization the + # helper sees 2000 + 1500 + 300 = 3800, of which 2000 are uncached. + expected = 2000 * 0.000003 + 1500 * 0.0000003 + 300 * 0.00000375 + 100 * 0.000015 + + assert cost == pytest.approx(expected) + + +def test_custom_pricing_without_cache_keys_preserves_legacy_behavior(): + """ + Backward compatibility: when custom_cost_per_token omits both cache rates, + cached tokens must be billed at input_cost_per_token (matching the pre-fix + behavior) so existing callers see no change. + """ + usage = Usage( + prompt_tokens=1000, + completion_tokens=100, + total_tokens=1100, + prompt_tokens_details=PromptTokensDetailsWrapper( + cached_tokens=400, + audio_tokens=0, + ), + ) + + response = ModelResponse( + id="test-id", + created=1234567890, + model="openai/gpt-5.4", + object="chat.completion", + choices=[], + usage=usage, + ) + + cost = litellm.completion_cost( + completion_response=response, + model="openai/gpt-5.4", + custom_llm_provider="openai", + custom_cost_per_token={ + "input_cost_per_token": 0.0000025, + "output_cost_per_token": 0.000015, + }, + ) + + # All 1000 prompt tokens billed at input rate, regardless of cached_tokens. + expected = 1000 * 0.0000025 + 100 * 0.000015 + + assert cost == pytest.approx(expected) diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx index 50dd5e78f37f..41dfdb21d1b2 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx @@ -654,12 +654,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { Input Tokens - {Math.max( - 0, - (userSpendData.metadata?.total_prompt_tokens || 0) - - (userSpendData.metadata?.total_cache_read_input_tokens || 0) - - (userSpendData.metadata?.total_cache_creation_input_tokens || 0) - ).toLocaleString()} + {(userSpendData.metadata?.total_prompt_tokens || 0).toLocaleString()} diff --git a/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx b/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx index 8ae90c1cbbcb..554fe86bf669 100644 --- a/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx +++ b/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx @@ -52,6 +52,8 @@ const AdvancedSettings: React.FC = ({ form.setFieldsValue({ input_cost_per_token: undefined, output_cost_per_token: undefined, + cache_read_input_token_cost: undefined, + cache_creation_input_token_cost: undefined, input_cost_per_second: undefined, }); } @@ -211,6 +213,24 @@ const AdvancedSettings: React.FC = ({ > + + + + + + ) : ( , ac if (formValues.output_cost_per_token !== undefined && formValues.output_cost_per_token !== null && formValues.output_cost_per_token !== "") { formValues.output_cost_per_token = Number(formValues.output_cost_per_token) / 1000000; } + + // Cache Read Cost: if blank, default to Input Cost (already token-unit converted above) + if ( + formValues.cache_read_input_token_cost !== undefined && + formValues.cache_read_input_token_cost !== null && + formValues.cache_read_input_token_cost !== "" + ) { + formValues.cache_read_input_token_cost = + Number(formValues.cache_read_input_token_cost) / 1000000; + } else if ( + formValues.input_cost_per_token !== undefined && + formValues.input_cost_per_token !== null && + formValues.input_cost_per_token !== "" + ) { + formValues.cache_read_input_token_cost = Number(formValues.input_cost_per_token); + } else { + delete formValues.cache_read_input_token_cost; + } + + // Cache Write Cost: explicit value if provided, else leave unset so the + // backend keeps the model-level default (per-second pricing, model_prices + // entries, etc.). Sending 0 here would overwrite that default. + // The backend falls back to input_cost_per_token when this key is absent. + if ( + formValues.cache_creation_input_token_cost !== undefined && + formValues.cache_creation_input_token_cost !== null && + formValues.cache_creation_input_token_cost !== "" + ) { + formValues.cache_creation_input_token_cost = + Number(formValues.cache_creation_input_token_cost) / 1000000; + } else { + delete formValues.cache_creation_input_token_cost; + } // Keep input_cost_per_second as is, no conversion needed // Iterate through the key-value pairs in formValues @@ -119,7 +152,13 @@ export const prepareModelAddRequest = async (formValues: Record, ac } // Handle the pricing fields - else if (key === "input_cost_per_token" || key === "output_cost_per_token" || key === "input_cost_per_second") { + else if ( + key === "input_cost_per_token" || + key === "output_cost_per_token" || + key === "input_cost_per_second" || + key === "cache_read_input_token_cost" || + key === "cache_creation_input_token_cost" + ) { if (value !== undefined && value !== null && value !== "") { litellmParamsObj[key] = Number(value); } diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index 95a43862de51..5ed4c0468b84 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -263,6 +263,34 @@ export default function ModelInfoView({ updatedLitellmParams.output_cost_per_token = Number(values.output_cost) / 1_000_000; } + // Cache Read Cost: explicit value if provided, else fall back to input cost (when input cost touched). + if (form.isFieldTouched("cache_read_cost") || form.isFieldTouched("input_cost")) { + if ( + values.cache_read_cost !== undefined && + values.cache_read_cost !== null && + values.cache_read_cost !== "" + ) { + updatedLitellmParams.cache_read_input_token_cost = Number(values.cache_read_cost) / 1_000_000; + } else if (updatedLitellmParams.input_cost_per_token !== undefined) { + updatedLitellmParams.cache_read_input_token_cost = updatedLitellmParams.input_cost_per_token; + } + } + + // Cache Write Cost: explicit value if provided, else clear the override + // so the backend falls back to the model-level default. Sending 0 here + // would persist a zero rate even when the user intended to unset it. + if (form.isFieldTouched("cache_write_cost")) { + if ( + values.cache_write_cost !== undefined && + values.cache_write_cost !== null && + values.cache_write_cost !== "" + ) { + updatedLitellmParams.cache_creation_input_token_cost = Number(values.cache_write_cost) / 1_000_000; + } else { + delete updatedLitellmParams.cache_creation_input_token_cost; + } + } + if (values.litellm_credential_name) { updatedLitellmParams.litellm_credential_name = values.litellm_credential_name; } else { @@ -638,6 +666,22 @@ export default function ModelInfoView({ output_cost: localModelData.litellm_params?.output_cost_per_token ? localModelData.litellm_params.output_cost_per_token * 1_000_000 : localModelData.model_info?.output_cost_per_token * 1_000_000 || null, + cache_read_cost: + localModelData.litellm_params?.cache_read_input_token_cost !== undefined && + localModelData.litellm_params?.cache_read_input_token_cost !== null + ? localModelData.litellm_params.cache_read_input_token_cost * 1_000_000 + : localModelData.model_info?.cache_read_input_token_cost !== undefined && + localModelData.model_info?.cache_read_input_token_cost !== null + ? localModelData.model_info.cache_read_input_token_cost * 1_000_000 + : null, + cache_write_cost: + localModelData.litellm_params?.cache_creation_input_token_cost !== undefined && + localModelData.litellm_params?.cache_creation_input_token_cost !== null + ? localModelData.litellm_params.cache_creation_input_token_cost * 1_000_000 + : localModelData.model_info?.cache_creation_input_token_cost !== undefined && + localModelData.model_info?.cache_creation_input_token_cost !== null + ? localModelData.model_info.cache_creation_input_token_cost * 1_000_000 + : null, cache_control: localModelData.litellm_params?.cache_control_injection_points ? true : false, cache_control_injection_points: localModelData.litellm_params?.cache_control_injection_points || [], model_access_group: Array.isArray(localModelData.model_info?.access_groups) @@ -725,6 +769,52 @@ export default function ModelInfoView({ )} +
+ Cache Read Cost (per 1M tokens) + {isEditing ? ( + + + + ) : ( +
+ {localModelData?.litellm_params?.cache_read_input_token_cost !== undefined && + localModelData?.litellm_params?.cache_read_input_token_cost !== null + ? (localModelData.litellm_params.cache_read_input_token_cost * 1_000_000).toFixed(4) + : localModelData?.model_info?.cache_read_input_token_cost !== undefined && + localModelData?.model_info?.cache_read_input_token_cost !== null + ? (localModelData.model_info.cache_read_input_token_cost * 1_000_000).toFixed(4) + : "Not Set"} +
+ )} +
+ +
+ Cache Write Cost (per 1M tokens) + {isEditing ? ( + + + + ) : ( +
+ {localModelData?.litellm_params?.cache_creation_input_token_cost !== undefined && + localModelData?.litellm_params?.cache_creation_input_token_cost !== null + ? (localModelData.litellm_params.cache_creation_input_token_cost * 1_000_000).toFixed(4) + : localModelData?.model_info?.cache_creation_input_token_cost !== undefined && + localModelData?.model_info?.cache_creation_input_token_cost !== null + ? (localModelData.model_info.cache_creation_input_token_cost * 1_000_000).toFixed(4) + : "Not Set"} +
+ )} +
+
API Base {isEditing ? (