"""Live e2e: DeepSeek reasoner honors a request to turn reasoning OFF. DeepSeek's reasoner defaults thinking ON and surfaces the chain as ``message.reasoning_content``. Two documented ways to disable it are ``reasoning_effort="none"`` and ``thinking={"type": "disabled"}``. The DeepSeek param mapper (``litellm/llms/deepseek/chat/transformation.py`` ``map_openai_params``) forwards both as ``thinking={"type": "disabled"}`` so the outbound body carries a real disable signal and the reasoning model returns no ``reasoning_content``. This is the behavior tracked by LIT-3686 / GH #27453. The control case proves the model and path work (reasoning is returned when nothing asks to disable it), so the two disable assertions are meaningful. Requires DEEPSEEK_API_KEY on the proxy (tests/e2e/.env). No skip gate: once the proxy is up, a failure here is real, per the suite's hard-fail contract. """ from __future__ import annotations import pytest from e2e_config import unique_marker from e2e_http import unwrap from lifecycle import ResourceManager from models import ChatBody, ChatMessage, ChatResponse, LiteLLMParamsBody, ThinkingParam from passthrough_client import PassthroughClient pytestmark = pytest.mark.e2e REASONER = "deepseek/deepseek-v4-pro" PROMPT = "What is 17 + 26? Answer with just the number." def _register_reasoner(client: PassthroughClient, resources: ResourceManager) -> str: model = f"e2e-deepseek-reasoner-{unique_marker()}" model_id = client.proxy.create_model( model, LiteLLMParamsBody(model=REASONER, api_key="os.environ/DEEPSEEK_API_KEY"), ) resources.defer(lambda: client.proxy.delete_model(model_id)) return model def _reasoning_content(response: ChatResponse) -> str | None: assert response.choices, f"reasoner returned no choices: {response}" message = response.choices[0].message assert message is not None, f"reasoner choice has no message: {response}" return message.reasoning_content class TestDeepSeekReasoningDisable: def test_reasoner_returns_reasoning_by_default( self, client: PassthroughClient, resources: ResourceManager ) -> None: model = _register_reasoner(client, resources) key = resources.key() response = unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=PROMPT)], max_tokens=64, ), ) ) reasoning = _reasoning_content(response) assert reasoning, ( "control case: the reasoning model returned no reasoning_content with no " f"disable param, so the disable assertions below can't be trusted: {response}" ) def test_reasoning_effort_none_disables_reasoning( self, client: PassthroughClient, resources: ResourceManager ) -> None: model = _register_reasoner(client, resources) key = resources.key() response = unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=PROMPT)], max_tokens=64, reasoning_effort="none", ), ) ) assert not _reasoning_content(response), ( "reasoning_effort='none' must disable reasoning, but reasoning_content " f"is still present: {response}" ) def test_thinking_disabled_disables_reasoning( self, client: PassthroughClient, resources: ResourceManager ) -> None: model = _register_reasoner(client, resources) key = resources.key() response = unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=PROMPT)], max_tokens=64, thinking=ThinkingParam(type="disabled"), ), ) ) assert not _reasoning_content(response), ( "thinking={'type': 'disabled'} must disable reasoning, but " f"reasoning_content is still present: {response}" )