From 81a42d37b1d76a34ce75b6871bba57a30cddc812 Mon Sep 17 00:00:00 2001 From: Cao Qian Date: Sat, 1 Aug 2026 00:36:42 -0700 Subject: [PATCH] [Frontend] Add cache_salt support to Anthropic Messages API (#49498) Signed-off-by: aeon-x --- .../test_anthropic_messages_conversion.py | 23 +++++++++++++++++++ vllm/entrypoints/anthropic/protocol.py | 11 +++++++++ vllm/entrypoints/anthropic/serving.py | 1 + 3 files changed, 35 insertions(+) diff --git a/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py b/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py index 94f5bb6068e..9e988650573 100644 --- a/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py +++ b/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py @@ -1385,3 +1385,26 @@ class TestMessagesFullConverter: assert len(result.content) == 1 assert result.content[0].type == "text" assert result.content[0].text == "" + + +# ====================================================================== +# cache_salt pass-through (Issue #46688) +# ====================================================================== + + +class TestCacheSalt: + def test_cache_salt_passed_through(self): + """cache_salt on the Anthropic request reaches the converted + ChatCompletionRequest so prefix-cache isolation works via /v1/messages.""" + request = _make_request( + [{"role": "user", "content": "Hello"}], + cache_salt="tenant-abc-secret-salt", + ) + result = _convert(request) + assert result.cache_salt == "tenant-abc-secret-salt" + + def test_cache_salt_defaults_to_none(self): + """Omitting cache_salt leaves it unset (unchanged default behavior).""" + request = _make_request([{"role": "user", "content": "Hello"}]) + result = _convert(request) + assert result.cache_salt is None diff --git a/vllm/entrypoints/anthropic/protocol.py b/vllm/entrypoints/anthropic/protocol.py index a470ab65409..a67845bb433 100644 --- a/vllm/entrypoints/anthropic/protocol.py +++ b/vllm/entrypoints/anthropic/protocol.py @@ -133,6 +133,17 @@ class AnthropicMessagesRequest(BaseModel): top_p: float | None = None # vLLM-specific fields that are not in Anthropic spec + cache_salt: str | None = Field( + default=None, + description=( + "If specified, the prefix cache will be salted with the provided " + "string to prevent an attacker to guess prompts in multi-user " + "environments. The salt should be random, protected from " + "access by 3rd parties, and long enough to be " + "unpredictable (e.g., 43 characters base64-encoded, corresponding " + "to 256 bit)." + ), + ) kv_transfer_params: dict[str, Any] | None = Field( default=None, description="KVTransfer parameters used for disaggregated serving.", diff --git a/vllm/entrypoints/anthropic/serving.py b/vllm/entrypoints/anthropic/serving.py index d516a7530b9..7c3b0597b5c 100644 --- a/vllm/entrypoints/anthropic/serving.py +++ b/vllm/entrypoints/anthropic/serving.py @@ -486,6 +486,7 @@ class AnthropicServingMessages(OpenAIServingChat): temperature=anthropic_request.temperature, top_p=anthropic_request.top_p, top_k=anthropic_request.top_k, + cache_salt=anthropic_request.cache_salt, kv_transfer_params=anthropic_request.kv_transfer_params, ec_transfer_params=anthropic_request.ec_transfer_params, chat_template_kwargs=anthropic_request.chat_template_kwargs,