Fix/cache token (#28009)

Squash-merged by litellm-agent from escon1004's PR.
This commit is contained in:
escon1004 2026-05-16 11:16:15 +09:00 • committed by Sameer Kankute
parent 67af5e0205
commit 36685849a0
No known key found for this signature in database
8 changed files with 581 additions and 20 deletions

View file

@ -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,
)

View file

@ -69,6 +69,35 @@ else:
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 @@ class DBSpendUpdateWriter:
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:

View file

@ -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):

View file

@ -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)

View file

@ -654,12 +654,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
<Card>
<Title>Input Tokens</Title>
<Text className="text-2xl font-bold mt-2 text-blue-600">
{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()}
</Text>
</Card>
<Card>

View file

@ -52,6 +52,8 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
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<AdvancedSettingsProps> = ({
>
<TextInput />
</Form.Item>
<Form.Item
label="Cache Read Cost (per 1M tokens)"
name="cache_read_input_token_cost"
rules={[{ validator: validateNumber }]}
tooltip="If left blank, defaults to Input Cost."
className="mb-4"
>
<TextInput placeholder="Defaults to Input Cost if blank" />
</Form.Item>
<Form.Item
label="Cache Write Cost (per 1M tokens)"
name="cache_creation_input_token_cost"
rules={[{ validator: validateNumber }]}
tooltip="If left blank, defaults to Input Cost (the backend falls back to input_cost_per_token when no cache-write rate is set)."
className="mb-4"
>
<TextInput placeholder="Defaults to Input Cost if blank" />
</Form.Item>
</>
) : (
<Form.Item

View file

@ -45,6 +45,39 @@ export const prepareModelAddRequest = async (formValues: Record<string, any>, 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<string, any>, 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);
}

View file

@ -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({
)}
</div>
<div>
<Text className="font-medium">Cache Read Cost (per 1M tokens)</Text>
{isEditing ? (
<Form.Item
name="cache_read_cost"
className="mb-0"
tooltip="If left blank on save, defaults to Input Cost."
>
<NumericalInput placeholder="Defaults to Input Cost if blank" />
</Form.Item>
) : (
<div className="mt-1 p-2 bg-gray-50 rounded">
{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"}
</div>
)}
</div>
<div>
<Text className="font-medium">Cache Write Cost (per 1M tokens)</Text>
{isEditing ? (
<Form.Item
name="cache_write_cost"
className="mb-0"
tooltip="If left blank on save, defaults to Input Cost (backend falls back to input_cost_per_token)."
>
<NumericalInput placeholder="Defaults to Input Cost if blank" />
</Form.Item>
) : (
<div className="mt-1 p-2 bg-gray-50 rounded">
{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"}
</div>
)}
</div>
<div>
<Text className="font-medium">API Base</Text>
{isEditing ? (