feat(proxy): per-key prompt caching toggle via enable_prompt_caching (#36466)

* feat(proxy): per-key prompt caching auto-injection via enable_prompt_caching

Adds a key-level enable_prompt_caching toggle that auto-injects Anthropic
cache_control breakpoints on requests made with that key, without requiring
the gateway-wide enable_anthropic_prompt_caching flag. The flag lives in key
metadata, is stamped onto the request root by add_key_level_controls, rides
kwargs into both the /chat/completions seeding path and the native
/v1/messages path, and reuses every existing gate (anthropic/bedrock only,
supports_prompt_caching, client markers win). Client-supplied body values are
stripped as an untrusted root control field. Includes the Admin UI switch on
key create and key edit plus a read-only settings row, and dedupes the key
edit view's drifted initial-values objects.

* fix(proxy): drop section comment and suppress LIT011 on key-level prompt caching stamp
This commit is contained in:
ryan-crabbe-berri 2026-08-11 11:53:11 -07:00 committed by GitHub
parent 84d6666a59
commit cbf85a015f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 248 additions and 39 deletions

View file

@ -382,19 +382,23 @@ class AnthropicCacheControlHook(CustomPromptManagement):
model: str,
custom_llm_provider: str | None,
tools: list | None = None,
enable_prompt_caching: bool | None = None,
) -> list[CacheControlInjectionPoint]:
"""Default breakpoints when ``litellm.enable_anthropic_prompt_caching`` is on.
Caches the system prompt and the trailing turn, so the stable prefix
(system + tools + history) is reused while the breakpoint advances with
the conversation. Returns [] (stand down) when the flag is off, the
provider does not consume cache_control breakpoints (only anthropic /
bedrock do), the model lacks prompt-caching support, or the request
already carries client-supplied cache_control.
``enable_prompt_caching`` is the per-request override (stamped from key
metadata by the proxy); True turns auto-injection on for this request
even when the global flag is off. Caches the system prompt and the
trailing turn, so the stable prefix (system + tools + history) is
reused while the breakpoint advances with the conversation. Returns []
(stand down) when neither flag is on, the provider does not consume
cache_control breakpoints (only anthropic / bedrock do), the model
lacks prompt-caching support, or the request already carries
client-supplied cache_control.
"""
import litellm
if litellm.enable_anthropic_prompt_caching is not True:
if litellm.enable_anthropic_prompt_caching is not True and enable_prompt_caching is not True:
return []
provider = custom_llm_provider
@ -433,6 +437,7 @@ class AnthropicCacheControlHook(CustomPromptManagement):
model: str,
custom_llm_provider: str | None,
tools: list | None = None,
enable_prompt_caching: bool | None = None,
) -> None:
"""For /chat/completions: resolve the injection points the request should carry.
@ -458,6 +463,7 @@ class AnthropicCacheControlHook(CustomPromptManagement):
model=model,
custom_llm_provider=custom_llm_provider,
tools=tools,
enable_prompt_caching=enable_prompt_caching,
)
if points:
non_default_params["cache_control_injection_points"] = points
@ -478,12 +484,17 @@ class AnthropicCacheControlHook(CustomPromptManagement):
judgment happens once per request; points a prior pass wrote back
carry the judged stamp and are never re-judged (see
``_should_stand_down``). When none are configured but
``litellm.enable_anthropic_prompt_caching`` is on, synthesize default
breakpoints for the native /v1/messages path. Pops the key from kwargs;
``litellm.enable_anthropic_prompt_caching`` or the per-request
``enable_prompt_caching`` kwarg (stamped from key metadata) is on,
synthesize default breakpoints for the native /v1/messages path. Pops
both keys from kwargs;
if remaining (non-message) points exist they are written back so
downstream transforms can handle them.
"""
typed_messages = cast(list[AllMessageValues], messages) # cast-ok: Anthropic-shaped dicts from v1/messages
enable_prompt_caching: Final = cast( # cast-ok: kwargs is untyped; key stamped as bool by the proxy
bool | None, kwargs.pop("enable_prompt_caching", None)
)
configured: Final = cast( # cast-ok: kwargs is untyped; this key only holds the documented injection-point list
list[CacheControlInjectionPoint] | None, kwargs.pop("cache_control_injection_points", None)
)
@ -497,6 +508,7 @@ class AnthropicCacheControlHook(CustomPromptManagement):
tools=tools,
model=model,
custom_llm_provider=custom_llm_provider,
enable_prompt_caching=enable_prompt_caching,
)
if not injection_points:
return messages, system

View file

@ -504,6 +504,7 @@ async def acompletion(
model=model,
custom_llm_provider=cast(str | None, custom_llm_provider), # cast-ok: read from untyped kwargs
tools=tools,
enable_prompt_caching=cast(bool | None, kwargs.get("enable_prompt_caching")), # cast-ok: untyped kwargs
)
if isinstance(litellm_logging_obj, LiteLLMLoggingObj) and (
@ -5105,6 +5106,7 @@ def completion(
model=model,
custom_llm_provider=cast(str | None, kwargs.get("custom_llm_provider")), # cast-ok: untyped kwargs
tools=tools,
enable_prompt_caching=cast(bool | None, kwargs.get("enable_prompt_caching")), # cast-ok: untyped kwargs
)
if isinstance(litellm_logging_obj, LiteLLMLoggingObj) and (

View file

@ -1108,6 +1108,7 @@ class KeyRequestBase(GenerateRequestBase):
budget_id: str | None = None
tags: list[str] | None = None
disable_global_guardrails: bool | None = None
enable_prompt_caching: bool | None = None
throttle_on_budget_exceeded: bool | None = None
enforced_params: list[str] | None = None
allowed_routes: list | None = []
@ -4124,6 +4125,7 @@ LiteLLM_ManagementEndpoint_MetadataFields: Final = [
"enforced_batch_output_expires_after",
"enforced_file_expires_after",
"throttle_on_budget_exceeded",
"enable_prompt_caching",
]
LiteLLM_ManagementEndpoint_MetadataFields_Premium: Final = [

View file

@ -201,6 +201,7 @@ _UNTRUSTED_ROOT_CONTROL_FIELDS: Final = (
"mock_tool_calls",
"disable_global_guardrails",
"disable_global_guardrail",
"enable_prompt_caching",
"opted_out_global_guardrails",
"applied_guardrails",
"applied_policies",
@ -1333,6 +1334,9 @@ class LiteLLMProxyRequestSetup:
if "disable_fallbacks" in key_metadata and isinstance(key_metadata["disable_fallbacks"], bool):
data["disable_fallbacks"] = key_metadata["disable_fallbacks"]
if isinstance(key_metadata.get("enable_prompt_caching"), bool):
data["enable_prompt_caching"] = key_metadata["enable_prompt_caching"] # rebind-ok: data is an out-param
## KEY-LEVEL METADATA
data = LiteLLMProxyRequestSetup.add_management_endpoint_metadata_to_request_metadata(
data=data,

View file

@ -1593,6 +1593,7 @@ async def generate_key_fn(
- policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules.
- disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
- throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely.
- enable_prompt_caching: Optional[bool] - Auto-inject prompt caching breakpoints (Anthropic cache_control markers) on requests made with this key. Anthropic and Bedrock Claude models only.
- permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false}
- model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}}}. IF null or {} then no model specific budget.
- budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}.
@ -2693,6 +2694,7 @@ async def update_key_fn(
- policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules.
- disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
- throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely.
- enable_prompt_caching: Optional[bool] - Auto-inject prompt caching breakpoints (Anthropic cache_control markers) on requests made with this key. Anthropic and Bedrock Claude models only.
- prompts: Optional[List[str]] - List of prompts that the key is allowed to use.
- blocked: Optional[bool] - Whether the key is blocked
- aliases: Optional[dict] - Model aliases for the key - [Docs](https://litellm.vercel.app/docs/proxy/virtual_keys#model-aliases)

View file

@ -3467,6 +3467,7 @@ all_litellm_params = (
"caching_groups",
"ttl",
"cache",
"enable_prompt_caching",
"no-log",
"base_model",
"stream_timeout",

View file

@ -1728,6 +1728,87 @@ class TestEnableAnthropicPromptCaching:
assert result_msgs[-1]["content"][-1]["cache_control"] == {"type": "ephemeral"}
assert "cache_control" not in result_msgs[0]["content"][-1]
class TestPerKeyEnablePromptCaching:
"""Per-request enable_prompt_caching override (stamped from key metadata) with the global flag off."""
MESSAGES: List[AllMessageValues] = [
{"role": "system", "content": "a long system prompt"},
{"role": "user", "content": "latest turn"},
]
def _points(self, enable_prompt_caching, model="claude-sonnet-4-5", provider="anthropic", messages=None):
return AnthropicCacheControlHook.get_default_injection_points(
messages=copy.deepcopy(self.MESSAGES) if messages is None else messages,
system=None,
model=model,
custom_llm_provider=provider,
enable_prompt_caching=enable_prompt_caching,
)
def test_true_injects_with_global_flag_off(self):
assert litellm.enable_anthropic_prompt_caching is False
assert self._points(True) == [
{"location": "message", "role": "system", "index": None, "control": {"type": "ephemeral"}},
{"location": "message", "role": None, "index": -1, "control": {"type": "ephemeral"}},
]
@pytest.mark.parametrize("enable_prompt_caching", [False, None])
def test_false_and_none_fall_back_to_global_flag(self, enable_prompt_caching):
assert self._points(enable_prompt_caching) == []
def test_false_does_not_suppress_global_flag(self, monkeypatch):
monkeypatch.setattr(litellm, "enable_anthropic_prompt_caching", True)
assert [p["index"] for p in self._points(False)] == [None, -1]
def test_provider_gate_still_applies(self):
assert self._points(True, model="gpt-4o", provider="openai") == []
def test_unsupported_model_gate_still_applies(self):
assert self._points(True, model="anthropic.claude-3-5-sonnet-20240620-v1:0", provider="bedrock") == []
def test_client_markers_still_win(self):
messages = [
{"role": "system", "content": [{"type": "text", "text": "s", "cache_control": {"type": "ephemeral"}}]},
{"role": "user", "content": "latest turn"},
]
assert self._points(True, messages=messages) == []
def test_seed_injects_with_global_flag_off(self):
params: dict = {}
AnthropicCacheControlHook.maybe_seed_default_injection_points(
non_default_params=params,
messages=copy.deepcopy(self.MESSAGES),
model="claude-sonnet-4-5",
custom_llm_provider="anthropic",
enable_prompt_caching=True,
)
assert [p["index"] for p in params["cache_control_injection_points"]] == [None, -1]
def test_v1_messages_injects_and_pops_flag_from_kwargs(self):
kwargs: dict = {"enable_prompt_caching": True}
result_msgs, result_sys = AnthropicCacheControlHook.maybe_inject_cache_control(
[{"role": "user", "content": [{"type": "text", "text": "latest"}]}],
"a system prompt",
kwargs,
model="claude-sonnet-4-5",
custom_llm_provider="anthropic",
)
assert result_sys == [{"type": "text", "text": "a system prompt", "cache_control": {"type": "ephemeral"}}]
assert result_msgs[-1]["content"][-1]["cache_control"] == {"type": "ephemeral"}
assert "enable_prompt_caching" not in kwargs
def test_v1_messages_pops_flag_even_when_noop(self):
kwargs: dict = {"enable_prompt_caching": True}
AnthropicCacheControlHook.maybe_inject_cache_control(
[{"role": "user", "content": [{"type": "text", "text": "hi"}]}],
None,
kwargs,
model="gpt-4o",
custom_llm_provider="openai",
)
assert "enable_prompt_caching" not in kwargs
def test_v1_messages_is_noop_when_disabled(self):
messages = [{"role": "user", "content": [{"type": "text", "text": "hi"}]}]
result_msgs, result_sys = AnthropicCacheControlHook.maybe_inject_cache_control(

View file

@ -1733,6 +1733,21 @@ async def test_update_service_account_works_with_team_id():
await prepare_key_update_data(data=data, existing_key_row=existing_key)
@pytest.mark.asyncio
@pytest.mark.parametrize("flag_value", [True, False])
async def test_update_key_enable_prompt_caching_folds_into_metadata(flag_value):
"""Top-level enable_prompt_caching on /key/update lands in key metadata, including flipping back to False."""
data = UpdateKeyRequest(key="sk-1", enable_prompt_caching=flag_value)
existing_key = LiteLLM_VerificationToken(
token="hashed", metadata={"enable_prompt_caching": not flag_value}
)
updated = await prepare_key_update_data(data=data, existing_key_row=existing_key)
assert updated["metadata"]["enable_prompt_caching"] is flag_value
assert "enable_prompt_caching" not in {k for k in updated if k != "metadata"}
@pytest.mark.asyncio
async def test_update_preserves_service_account_id_when_metadata_replaced():
"""

View file

@ -688,6 +688,7 @@ async def test_add_litellm_data_to_request_strips_user_control_fields():
"mock_response": "free response",
"mock_tool_calls": [{"id": "call_1"}],
"disable_global_guardrails": True,
"enable_prompt_caching": True,
"routing_decision": {"cause": "forged", "routed_model": "spoofed"},
"metadata": copy.deepcopy(malicious_metadata),
"litellm_metadata": copy.deepcopy(malicious_metadata),
@ -705,6 +706,7 @@ async def test_add_litellm_data_to_request_strips_user_control_fields():
assert "mock_response" not in updated
assert "mock_tool_calls" not in updated
assert "disable_global_guardrails" not in updated
assert "enable_prompt_caching" not in updated
assert "routing_decision" not in updated
stripped_keys = {
@ -741,6 +743,42 @@ async def test_add_litellm_data_to_request_strips_user_control_fields():
assert "pillar_response_headers" not in snapshot_body["metadata"]
@pytest.mark.asyncio
@pytest.mark.parametrize(
"key_value, expected",
[(True, True), (False, False), ("yes", None), (None, None)],
)
async def test_key_metadata_enable_prompt_caching_promoted_to_request_root(key_value, expected):
"""Key metadata enable_prompt_caching is stamped onto the request root (bools only), even when the client spoofs it."""
request_mock = MagicMock(spec=Request)
request_mock.url.path = "/v1/chat/completions"
request_mock.url = MagicMock()
request_mock.url.__str__.return_value = "http://localhost/v1/chat/completions"
request_mock.method = "POST"
request_mock.query_params = {}
request_mock.headers = {"Content-Type": "application/json"}
request_mock.client = MagicMock()
request_mock.client.host = "127.0.0.1"
data = {
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "hello"}],
"enable_prompt_caching": "spoofed-by-client",
}
key_metadata = {} if key_value is None else {"enable_prompt_caching": key_value}
updated = await add_litellm_data_to_request(
data=data,
request=request_mock,
user_api_key_dict=UserAPIKeyAuth(api_key="hashed-key", metadata=key_metadata),
proxy_config=MagicMock(),
general_settings={},
version="test-version",
)
assert updated.get("enable_prompt_caching") == expected
@pytest.mark.asyncio
@pytest.mark.parametrize(
"control_field",

View file

@ -1191,6 +1191,21 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
>
<Switch checkedChildren="Yes" unCheckedChildren="No" />
</Form.Item>
<Form.Item
className="mt-4"
label={
<span>
Enable Prompt Caching{" "}
<Tooltip title="Automatically add prompt caching breakpoints (cache_control markers) to requests made with this key, cutting input cost on repeated prompts. Applies to Anthropic and Bedrock Claude models; requests that already set their own cache_control markers are left untouched.">
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="enable_prompt_caching"
valuePropName="checked"
>
<Switch checkedChildren="Yes" unCheckedChildren="No" />
</Form.Item>
<Form.Item
label={
<span>

View file

@ -410,6 +410,36 @@ describe("KeyEditView", () => {
});
});
it("should initialize and submit enable_prompt_caching from key metadata", async () => {
const onSubmitMock = vi.fn().mockResolvedValue(undefined);
const keyDataWithPromptCaching = {
...MOCK_KEY_DATA,
metadata: { ...MOCK_KEY_DATA.metadata, enable_prompt_caching: true },
};
renderWithProviders(
<KeyEditView
keyData={keyDataWithPromptCaching}
onCancel={() => {}}
onSubmit={onSubmitMock}
accessToken={"test-token"}
userID={"test-user"}
userRole={"admin"}
premiumUser={false}
/>,
);
await waitFor(() => {
expect(screen.getByText("Enable Prompt Caching")).toBeInTheDocument();
});
await userEvent.click(screen.getByRole("button", { name: /save changes/i }));
await waitFor(() => {
expect(onSubmitMock).toHaveBeenCalledWith(expect.objectContaining({ enable_prompt_caching: true }));
});
});
it("should disable models field when management routes are selected", async () => {
const keyDataWithManagementRoutes = {
...MOCK_KEY_DATA,

View file

@ -150,6 +150,7 @@ export function KeyEditView({
guardrails: keyData.metadata?.guardrails,
disable_global_guardrails: keyData.metadata?.disable_global_guardrails || false,
throttle_on_budget_exceeded: keyData.metadata?.throttle_on_budget_exceeded || false,
enable_prompt_caching: keyData.metadata?.enable_prompt_caching || false,
...estimateFields(keyData.metadata),
prompts: keyData.metadata?.prompts,
tags: keyData.metadata?.tags,
@ -178,36 +179,8 @@ export function KeyEditView({
};
useEffect(() => {
form.setFieldsValue({
...keyData,
token: keyData.token || keyData.token_id,
budget_duration: canonicalBudgetDuration(keyData.budget_duration),
metadata: formatMetadataForDisplay(stripTagsFromMetadata(keyData.metadata)),
guardrails: keyData.metadata?.guardrails,
disable_global_guardrails: keyData.metadata?.disable_global_guardrails || false,
prompts: keyData.metadata?.prompts,
tags: keyData.metadata?.tags,
vector_stores: keyData.object_permission?.vector_stores || [],
mcp_servers_and_groups: {
servers: keyData.object_permission?.mcp_servers || [],
accessGroups: keyData.object_permission?.mcp_access_groups || [],
toolsets: keyData.object_permission?.mcp_toolsets || [],
},
mcp_tool_permissions: keyData.object_permission?.mcp_tool_permissions || {},
throttle_on_budget_exceeded: keyData.metadata?.throttle_on_budget_exceeded || false,
...estimateFields(keyData.metadata),
logging_settings: extractLoggingSettings(keyData.metadata),
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
: [],
access_group_ids: keyData.access_group_ids || [],
auto_rotate: keyData.auto_rotate || false,
...(keyData.rotation_interval && { rotation_interval: keyData.rotation_interval }),
allowed_routes:
Array.isArray(keyData.allowed_routes) && keyData.allowed_routes.length > 0
? keyData.allowed_routes.join(", ")
: "",
});
form.setFieldsValue(initialValues);
// eslint-disable-next-line react-hooks/exhaustive-deps -- initialValues is rebuilt from keyData every render; depending on it would re-run each render
}, [keyData, form]);
// Sync auto-rotation state with form values
@ -532,6 +505,21 @@ export function KeyEditView({
<Switch checkedChildren="Yes" unCheckedChildren="No" />
</Form.Item>
<Form.Item
label={
<span>
Enable Prompt Caching{" "}
<Tooltip title="Automatically add prompt caching breakpoints (cache_control markers) to requests made with this key, cutting input cost on repeated prompts. Applies to Anthropic and Bedrock Claude models; requests that already set their own cache_control markers are left untouched.">
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="enable_prompt_caching"
valuePropName="checked"
>
<Switch checkedChildren="Yes" unCheckedChildren="No" />
</Form.Item>
<Form.Item label="Max Parallel Requests" name="max_parallel_requests">
<NumericalInput min={0} />
</Form.Item>

View file

@ -782,6 +782,13 @@ export default function KeyInfoView({
<Text>{currentKeyData.expires ? formatTimestamp(currentKeyData.expires) : "Never"}</Text>
</div>
{Boolean(currentKeyData.metadata?.enable_prompt_caching) && (
<div>
<Text className="font-medium">Prompt Caching</Text>
<Text>Enabled (auto-injects cache_control markers on Anthropic and Bedrock Claude requests)</Text>
</div>
)}
<AutoRotationView
autoRotate={currentKeyData.auto_rotate}
rotationInterval={currentKeyData.rotation_interval}

View file

@ -6781,6 +6781,7 @@ export interface paths {
* - policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules.
* - disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
* - throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely.
* - enable_prompt_caching: Optional[bool] - Auto-inject prompt caching breakpoints (Anthropic cache_control markers) on requests made with this key. Anthropic and Bedrock Claude models only.
* - permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false}
* - model_max_budget: Optional[Dict[str, BudgetConfig]] - Model-specific budgets {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}}}. IF null or {} then no model specific budget.
* - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}.
@ -7240,6 +7241,7 @@ export interface paths {
* - policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules.
* - disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
* - throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely.
* - enable_prompt_caching: Optional[bool] - Auto-inject prompt caching breakpoints (Anthropic cache_control markers) on requests made with this key. Anthropic and Bedrock Claude models only.
* - prompts: Optional[List[str]] - List of prompts that the key is allowed to use.
* - blocked: Optional[bool] - Whether the key is blocked
* - aliases: Optional[dict] - Model aliases for the key - [Docs](https://litellm.vercel.app/docs/proxy/virtual_keys#model-aliases)
@ -24987,6 +24989,8 @@ export interface components {
disable_global_guardrails?: boolean | null;
/** Duration */
duration?: string | null;
/** Enable Prompt Caching */
enable_prompt_caching?: boolean | null;
/** Enforced Params */
enforced_params?: string[] | null;
/** Guardrails */
@ -25147,6 +25151,8 @@ export interface components {
disable_global_guardrails?: boolean | null;
/** Duration */
duration?: string | null;
/** Enable Prompt Caching */
enable_prompt_caching?: boolean | null;
/** Enforced Params */
enforced_params?: string[] | null;
/** Expires */
@ -29620,6 +29626,8 @@ export interface components {
disable_global_guardrails?: boolean | null;
/** Duration */
duration?: string | null;
/** Enable Prompt Caching */
enable_prompt_caching?: boolean | null;
/** Enforced Params */
enforced_params?: string[] | null;
/** Expires */
@ -31465,6 +31473,8 @@ export interface components {
disable_global_guardrails?: boolean | null;
/** Duration */
duration?: string | null;
/** Enable Prompt Caching */
enable_prompt_caching?: boolean | null;
/** Enforced Params */
enforced_params?: string[] | null;
/** Grace Period */
@ -33860,6 +33870,8 @@ export interface components {
disable_global_guardrails?: boolean | null;
/** Duration */
duration?: string | null;
/** Enable Prompt Caching */
enable_prompt_caching?: boolean | null;
/** Enforced Params */
enforced_params?: string[] | null;
/** Guardrails */