From df27beac96c3cc2b76e73d7abc6d6d36000c2223 Mon Sep 17 00:00:00 2001 From: GavinZ Date: Mon, 26 Oct 2026 12:44:02 +0800 Subject: [PATCH] test(deepseek): cover reasoning_content backfill on anthropic endpoint Nine mock-only unit tests for the Anthropic-compat reasoning_content backfill: placeholder injection, value preservation, provider_specific_fields promotion, non-assistant skips, empty-list handling, mutation safety, the disabled-thinking guard, and the _thinking_mode_active predicate. --- ...pseek_anthropic_messages_transformation.py | 139 +++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/tests/unit/llms/deepseek/messages/test_deepseek_anthropic_messages_transformation.py b/tests/unit/llms/deepseek/messages/test_deepseek_anthropic_messages_transformation.py index 0afe57a001f..71f9d63d8d8 100644 --- a/tests/unit/llms/deepseek/messages/test_deepseek_anthropic_messages_transformation.py +++ b/tests/unit/llms/deepseek/messages/test_deepseek_anthropic_messages_transformation.py @@ -120,7 +120,15 @@ def test_deepseek_anthropic_messages_headers_use_deepseek_key(): assert headers["content-type"] == "application/json" -def test_deepseek_anthropic_messages_preserves_thinking_and_sanitizes_custom_tools(): +def test_deepseek_anthropic_messages_preserves_thinking_sanitizes_tools_and_backfills_reasoning_content(): + """ + Thinking-mode request through the DeepSeek anthropic endpoint: + - thinking blocks in assistant history are preserved (untouched) + - custom tool discriminator is stripped (existing behaviour) + - assistant messages missing `reasoning_content` get a placeholder + backfill so DeepSeek does not reject the request with + "The reasoning_content in the thinking mode must be passed back" + """ config = DeepSeekAnthropicMessagesConfig() messages = [ { @@ -179,11 +187,138 @@ def test_deepseek_anthropic_messages_preserves_thinking_and_sanitizes_custom_too headers={}, ) - assert request["messages"] == messages + # thinking param preserved assert request["thinking"] == {"type": "enabled", "budget_tokens": 1024} + + # tools sanitized (custom stripped, hosted tool kept) assert request["tools"][0] == { "name": "get_weather", "description": "Get weather", "input_schema": {"type": "object"}, } assert request["tools"][1]["type"] == "web_search_20260209" + + # user + tool_result messages unchanged + assert request["messages"][0] == messages[0] + assert request["messages"][2] == messages[2] + + # assistant message: thinking block content preserved, reasoning_content backfilled + assistant = request["messages"][1] + assert assistant["content"] == messages[1]["content"] + assert assistant["reasoning_content"] == " " + + +# --------------------------------------------------------------------------- +# reasoning_content backfill unit tests +# --------------------------------------------------------------------------- + + +def test_fill_reasoning_content_injects_placeholder_when_missing(): + config = DeepSeekAnthropicMessagesConfig() + out = config._fill_reasoning_content( + [ + {"role": "user", "content": "hi"}, + {"role": "assistant", "content": "hello"}, + ] + ) + assert out[1]["reasoning_content"] == " " + assert "reasoning_content" not in out[0] + + +def test_fill_reasoning_content_preserves_existing_value(): + config = DeepSeekAnthropicMessagesConfig() + out = config._fill_reasoning_content( + [ + { + "role": "assistant", + "content": "x", + "reasoning_content": "real chain", + } + ] + ) + assert out[0]["reasoning_content"] == "real chain" + + +def test_fill_reasoning_content_promotes_from_provider_specific_fields(): + config = DeepSeekAnthropicMessagesConfig() + out = config._fill_reasoning_content( + [ + { + "role": "assistant", + "content": "x", + "provider_specific_fields": { + "reasoning_content": "stored chain", + "other": "keep", + }, + } + ] + ) + assert out[0]["reasoning_content"] == "stored chain" + assert "reasoning_content" not in out[0]["provider_specific_fields"] + assert out[0]["provider_specific_fields"]["other"] == "keep" + + +def test_fill_reasoning_content_skips_non_assistant_messages(): + config = DeepSeekAnthropicMessagesConfig() + out = config._fill_reasoning_content( + [ + {"role": "user", "content": "hi"}, + {"role": "system", "content": "sys"}, + ] + ) + assert "reasoning_content" not in out[0] + assert "reasoning_content" not in out[1] + + +def test_fill_reasoning_content_handles_empty_list(): + config = DeepSeekAnthropicMessagesConfig() + assert config._fill_reasoning_content([]) == [] + + +def test_fill_reasoning_content_does_not_mutate_input(): + config = DeepSeekAnthropicMessagesConfig() + src = [{"role": "assistant", "content": "x"}] + src_snapshot = dict(src[0]) + config._fill_reasoning_content(src) + assert src[0] == src_snapshot + + +def test_transform_does_not_backfill_when_thinking_disabled(): + """Non-thinking requests must not get spurious reasoning_content injection.""" + config = DeepSeekAnthropicMessagesConfig() + out = config.transform_anthropic_messages_request( + model="deepseek-v4-pro", + messages=[ + {"role": "user", "content": "hi"}, + {"role": "assistant", "content": [{"type": "text", "text": "hello"}]}, + ], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params=GenericLiteLLMParams(), + headers={}, + ) + assert "reasoning_content" not in out["messages"][1] + + +def test_thinking_mode_active_returns_true_only_when_explicitly_enabled(): + """Guard must fire ONLY on `thinking: {type: enabled}`, not other shapes.""" + config = DeepSeekAnthropicMessagesConfig() + # explicit enabled -> fill + assert config._thinking_mode_active( + model="deepseek-v4-pro", + optional_params={"thinking": {"type": "enabled", "budget_tokens": 1024}}, + ) is True + # type=disabled -> no fill + assert config._thinking_mode_active( + model="deepseek-v4-pro", + optional_params={"thinking": {"type": "disabled"}}, + ) is False + # thinking absent -> no fill (most common non-thinking case) + assert config._thinking_mode_active( + model="deepseek-v4-pro", + optional_params={"max_tokens": 100}, + ) is False + # thinking present but not a dict (defensive) -> no fill + assert config._thinking_mode_active( + model="deepseek-v4-pro", + optional_params={"thinking": None}, + ) is False