diff --git a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py index 8b94c34942c..66ec5eed4c7 100644 --- a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py +++ b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py @@ -785,7 +785,7 @@ def convert_to_model_response_object( # Preserve provider_specific_fields if already present # in the response (e.g. from proxy passthrough) provider_specific_fields = dict(choice["message"].get("provider_specific_fields", None) or {}) - for f in choice["message"].keys() - _MESSAGE_FIELDS: + for f in choice["message"].keys() - _MESSAGE_FIELDS - {"reasoning"}: provider_specific_fields[f] = choice["message"][f] # Handle reasoning models that display `reasoning_content` within `content` diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index 7f8a38cc8ec..925c5cb80d3 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -198,6 +198,7 @@ def _redacted_provider_specific_fields(fields: Mapping[str, object]) -> Mapping[ return { **fields, **({"reasoning_content": REDACTED_BY_LITELLM} if fields.get("reasoning_content") is not None else {}), + **({"reasoning": REDACTED_BY_LITELLM} if fields.get("reasoning") is not None else {}), **{ key: None for key in ( @@ -206,6 +207,7 @@ def _redacted_provider_specific_fields(fields: Mapping[str, object]) -> Mapping[ "citations", "compaction_blocks", "compaction_delta", + "reasoning_details", ) if key in fields }, diff --git a/tests/unit/litellm_core_utils/llm_response_utils/test_convert_dict_to_response.py b/tests/unit/litellm_core_utils/llm_response_utils/test_convert_dict_to_response.py index 0c601a6b2c8..51b47d8c014 100644 --- a/tests/unit/litellm_core_utils/llm_response_utils/test_convert_dict_to_response.py +++ b/tests/unit/litellm_core_utils/llm_response_utils/test_convert_dict_to_response.py @@ -2873,6 +2873,28 @@ class TestConvertToModelResponseObjectCompletion: assert message.reasoning_content == "2+2=4" assert "reasoning_content" not in (message.provider_specific_fields or {}) + def test_reasoning_not_mirrored_into_provider_specific_fields(self): + response_object = { + "id": "chatcmpl-6", + "model": "openrouter/deepseek/deepseek-r1", + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": {"content": "The answer is 4.", "role": "assistant", "reasoning": "2+2=4"}, + } + ], + "usage": {"prompt_tokens": 5, "completion_tokens": 10, "total_tokens": 15}, + } + + result = convert_to_model_response_object( + response_object=response_object, + model_response_object=ModelResponse(), + ) + message = result.choices[0].message + assert message.reasoning_content == "2+2=4" + assert "reasoning" not in (message.provider_specific_fields or {}) + def test_response_none_raises(self): with pytest.raises(Exception, match="Invalid response object"): convert_to_model_response_object( diff --git a/tests/unit/litellm_core_utils/test_redact_messages.py b/tests/unit/litellm_core_utils/test_redact_messages.py index 6a734ca3b70..0262f30c258 100644 --- a/tests/unit/litellm_core_utils/test_redact_messages.py +++ b/tests/unit/litellm_core_utils/test_redact_messages.py @@ -53,6 +53,8 @@ SIGNATURE: Final = "opaque-thought-signature" def _provider_fields() -> dict[str, object]: return { "reasoning_content": SECRET, + "reasoning": SECRET, + "reasoning_details": [{"type": "reasoning.text", "text": SECRET, "index": 0, "format": "unknown"}], "thinking_blocks": [{"type": "thinking", "thinking": SECRET}], "reasoningContentBlocks": [{"reasoningText": {"text": SECRET}}], "citations": [{"cited_text": SECRET}],