From d64f277c54d444284bdbcd16e7fd1073b0a3b50e Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com> Date: Sat, 26 Sep 2026 09:50:57 -0400 Subject: [PATCH] [Bugfix] Fix Anthropic Thinking Disabled with P/D (#58786) Signed-off-by: Robert Shaw Co-authored-by: Robert Shaw --- .../test_anthropic_messages_conversion.py | 22 +++++++++++++++++++ vllm/entrypoints/anthropic/protocol.py | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py b/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py index 5790b9b082..a6cfb15d60 100644 --- a/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py +++ b/tests/entrypoints/anthropic/test_anthropic_messages_conversion.py @@ -1804,6 +1804,28 @@ class TestThinkingConfig: thinking=thinking, ) + def test_pd_prefill_leg_skips_budget_check(self): + """P/D sidecars resend the request to the prefill node with + max_tokens=1; the budget is enforced on the decode leg instead.""" + thinking = {"type": "enabled", "budget_tokens": 1024} + request = AnthropicMessagesRequest( + model="test-model", + max_tokens=1, + messages=[{"role": "user", "content": "Hello"}], + thinking=thinking, + kv_transfer_params={"do_remote_decode": True}, + ) + + assert _convert(request).thinking_token_budget == 1024 + with pytest.raises(ValidationError): + AnthropicMessagesRequest( + model="test-model", + max_tokens=1, + messages=[{"role": "user", "content": "Hello"}], + thinking=thinking, + kv_transfer_params={"do_remote_decode": False}, + ) + def test_adaptive_pins_nothing_and_keeps_effort_ceiling(self): """`adaptive` lets the model choose depth, so only the ceiling from output_config.effort should survive.""" diff --git a/vllm/entrypoints/anthropic/protocol.py b/vllm/entrypoints/anthropic/protocol.py index 48202831fe..9bd40a9161 100644 --- a/vllm/entrypoints/anthropic/protocol.py +++ b/vllm/entrypoints/anthropic/protocol.py @@ -233,6 +233,10 @@ class AnthropicMessagesRequest(BaseModel): @model_validator(mode="after") def validate_thinking_budget(self) -> "AnthropicMessagesRequest": + # P/D prefill legs are sent with max_tokens=1 and never decode; the + # decode leg carries the client's max_tokens and is still checked. + if self.kv_transfer_params and self.kv_transfer_params.get("do_remote_decode"): + return self if ( isinstance(self.thinking, AnthropicThinkingConfigEnabled) and self.thinking.budget_tokens >= self.max_tokens