From 46fdb48e96d77fe99f7897254b7ebe85f9822b25 Mon Sep 17 00:00:00 2001 From: misinierijon4-debug Date: Wed, 19 Aug 2026 14:55:52 +0200 Subject: [PATCH 1/2] fix(llm_translation): handle dict reasoning_effort in Ollama and Bedrock transformation --- .../bedrock/chat/converse_transformation.py | 10 ++-- litellm/llms/ollama/chat/transformation.py | 12 +++-- .../llms/ollama/completion/transformation.py | 12 +++-- .../ollama/test_ollama_chat_transformation.py | 48 +++++++++++++++++++ 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 85918d40e12..8e7cf5e6e92 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -918,10 +918,12 @@ class AmazonConverseConfig(BaseConfig): litellm.verbose_logger.warning(DROP_UNSUPPORTED_ADAPTIVE_THINKING_WARNING, model) else: optional_params["thinking"] = value - elif param == "reasoning_effort" and isinstance(value, str): - self._handle_reasoning_effort_parameter( - model=model, reasoning_effort=value, optional_params=optional_params - ) + elif param == "reasoning_effort": + effort_value = value.get("effort") if isinstance(value, dict) else value + if isinstance(effort_value, str): + self._handle_reasoning_effort_parameter( + model=model, reasoning_effort=effort_value, optional_params=optional_params + ) elif param == "output_config" and isinstance(value, dict): mapped_output_config = dict(value) normalize_bedrock_opus_output_config_effort(model=model, output_config=mapped_output_config) diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py index d6aa1f1743b..2b48ec41cf8 100644 --- a/litellm/llms/ollama/chat/transformation.py +++ b/litellm/llms/ollama/chat/transformation.py @@ -147,7 +147,7 @@ class OllamaChatConfig(BaseConfig): non_default_params: dict, optional_params: dict, model: str, - drop_params: bool, + drop_params: bool = False, ) -> dict: for param, value in non_default_params.items(): if param == "max_tokens" or param == "max_completion_tokens": @@ -170,10 +170,12 @@ class OllamaChatConfig(BaseConfig): if value.get("json_schema") and value["json_schema"].get("schema"): optional_params["format"] = value["json_schema"]["schema"] if param == "reasoning_effort" and value is not None: - if model.startswith("gpt-oss"): - optional_params["think"] = value - else: - optional_params["think"] = value in {"low", "medium", "high"} + effort_value = value.get("effort") if isinstance(value, dict) else value + if effort_value is not None: + if model.startswith("gpt-oss"): + optional_params["think"] = effort_value + else: + optional_params["think"] = effort_value in {"low", "medium", "high"} ### FUNCTION CALLING LOGIC ### # Ollama 0.4+ supports native tool calling - pass tools directly # and let Ollama handle model capability detection diff --git a/litellm/llms/ollama/completion/transformation.py b/litellm/llms/ollama/completion/transformation.py index 65edd5cb718..83432ee79d5 100644 --- a/litellm/llms/ollama/completion/transformation.py +++ b/litellm/llms/ollama/completion/transformation.py @@ -160,7 +160,7 @@ class OllamaConfig(BaseConfig): non_default_params: dict, optional_params: dict, model: str, - drop_params: bool, + drop_params: bool = False, ) -> dict: for param, value in non_default_params.items(): if param == "max_tokens" or param == "max_completion_tokens": @@ -178,10 +178,12 @@ class OllamaConfig(BaseConfig): elif param == "stop": optional_params["stop"] = value elif param == "reasoning_effort" and value is not None: - if model.startswith("gpt-oss"): - optional_params["think"] = value - else: - optional_params["think"] = value in {"low", "medium", "high"} + effort_value = value.get("effort") if isinstance(value, dict) else value + if effort_value is not None: + if model.startswith("gpt-oss"): + optional_params["think"] = effort_value + else: + optional_params["think"] = effort_value in {"low", "medium", "high"} elif param == "response_format" and isinstance(value, dict): if value["type"] == "json_object": optional_params["format"] = "json" diff --git a/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py b/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py index 906c51d8064..ef0d9a1ecba 100644 --- a/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py +++ b/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py @@ -906,3 +906,51 @@ class TestOllamaToolCallTransformation: assert tool_msg["content"] == "Sunny, 72°F" assert "tool_call_id" in tool_msg, "tool_call_id must be forwarded to Ollama" assert tool_msg["tool_call_id"] == "call_abc123" + + +class TestOllamaReasoningEffortMapping: + """ + Test reasoning_effort mapping with string and dict formats (Responses API bridge). + Regression: https://github.com/BerriAI/litellm/issues/37452 + """ + + def test_map_openai_params_reasoning_effort_string(self): + config = OllamaChatConfig() + # "low", "medium", "high" map to True for standard models + assert config.map_openai_params({"reasoning_effort": "low"}, {}, "qwen3") == {"think": True} + assert config.map_openai_params({"reasoning_effort": "medium"}, {}, "qwen3") == {"think": True} + assert config.map_openai_params({"reasoning_effort": "high"}, {}, "qwen3") == {"think": True} + assert config.map_openai_params({"reasoning_effort": "none"}, {}, "qwen3") == {"think": False} + + def test_map_openai_params_reasoning_effort_dict_with_effort(self): + config = OllamaChatConfig() + result = config.map_openai_params( + {"reasoning_effort": {"effort": "medium", "summary": "auto"}}, {}, "qwen3" + ) + assert result == {"think": True} + + result_low = config.map_openai_params( + {"reasoning_effort": {"effort": "low"}}, {}, "qwen3" + ) + assert result_low == {"think": True} + + result_none = config.map_openai_params( + {"reasoning_effort": {"effort": "none"}}, {}, "qwen3" + ) + assert result_none == {"think": False} + + def test_map_openai_params_reasoning_effort_dict_summary_only(self): + config = OllamaChatConfig() + # When only summary is present without effort, no "think" param is set + result = config.map_openai_params( + {"reasoning_effort": {"summary": "auto"}}, {}, "qwen3" + ) + assert "think" not in result + + def test_map_openai_params_reasoning_effort_gpt_oss(self): + config = OllamaChatConfig() + result = config.map_openai_params( + {"reasoning_effort": {"effort": "medium"}}, {}, "gpt-oss-123" + ) + assert result == {"think": "medium"} + From 7493af5528892d6c34394616c7be33f81c7fcb53 Mon Sep 17 00:00:00 2001 From: misinierijon4-debug Date: Wed, 19 Aug 2026 15:28:34 +0200 Subject: [PATCH 2/2] test(ollama): cover completion reasoning effort mapping --- litellm/llms/ollama/chat/transformation.py | 2 +- .../llms/ollama/completion/transformation.py | 2 +- .../ollama/test_ollama_chat_transformation.py | 29 ++++++------------- .../test_ollama_completion_transformation.py | 24 +++++++++++++++ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py index 2b48ec41cf8..901dd579ade 100644 --- a/litellm/llms/ollama/chat/transformation.py +++ b/litellm/llms/ollama/chat/transformation.py @@ -147,7 +147,7 @@ class OllamaChatConfig(BaseConfig): non_default_params: dict, optional_params: dict, model: str, - drop_params: bool = False, + drop_params: bool, ) -> dict: for param, value in non_default_params.items(): if param == "max_tokens" or param == "max_completion_tokens": diff --git a/litellm/llms/ollama/completion/transformation.py b/litellm/llms/ollama/completion/transformation.py index 83432ee79d5..26faa491ea6 100644 --- a/litellm/llms/ollama/completion/transformation.py +++ b/litellm/llms/ollama/completion/transformation.py @@ -160,7 +160,7 @@ class OllamaConfig(BaseConfig): non_default_params: dict, optional_params: dict, model: str, - drop_params: bool = False, + drop_params: bool, ) -> dict: for param, value in non_default_params.items(): if param == "max_tokens" or param == "max_completion_tokens": diff --git a/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py b/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py index ef0d9a1ecba..f94b4290de1 100644 --- a/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py +++ b/tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py @@ -916,41 +916,30 @@ class TestOllamaReasoningEffortMapping: def test_map_openai_params_reasoning_effort_string(self): config = OllamaChatConfig() - # "low", "medium", "high" map to True for standard models - assert config.map_openai_params({"reasoning_effort": "low"}, {}, "qwen3") == {"think": True} - assert config.map_openai_params({"reasoning_effort": "medium"}, {}, "qwen3") == {"think": True} - assert config.map_openai_params({"reasoning_effort": "high"}, {}, "qwen3") == {"think": True} - assert config.map_openai_params({"reasoning_effort": "none"}, {}, "qwen3") == {"think": False} + assert config.map_openai_params({"reasoning_effort": "low"}, {}, "qwen3", False) == {"think": True} + assert config.map_openai_params({"reasoning_effort": "medium"}, {}, "qwen3", False) == {"think": True} + assert config.map_openai_params({"reasoning_effort": "high"}, {}, "qwen3", False) == {"think": True} + assert config.map_openai_params({"reasoning_effort": "none"}, {}, "qwen3", False) == {"think": False} def test_map_openai_params_reasoning_effort_dict_with_effort(self): config = OllamaChatConfig() result = config.map_openai_params( - {"reasoning_effort": {"effort": "medium", "summary": "auto"}}, {}, "qwen3" + {"reasoning_effort": {"effort": "medium", "summary": "auto"}}, {}, "qwen3", False ) assert result == {"think": True} - result_low = config.map_openai_params( - {"reasoning_effort": {"effort": "low"}}, {}, "qwen3" - ) + result_low = config.map_openai_params({"reasoning_effort": {"effort": "low"}}, {}, "qwen3", False) assert result_low == {"think": True} - result_none = config.map_openai_params( - {"reasoning_effort": {"effort": "none"}}, {}, "qwen3" - ) + result_none = config.map_openai_params({"reasoning_effort": {"effort": "none"}}, {}, "qwen3", False) assert result_none == {"think": False} def test_map_openai_params_reasoning_effort_dict_summary_only(self): config = OllamaChatConfig() - # When only summary is present without effort, no "think" param is set - result = config.map_openai_params( - {"reasoning_effort": {"summary": "auto"}}, {}, "qwen3" - ) + result = config.map_openai_params({"reasoning_effort": {"summary": "auto"}}, {}, "qwen3", False) assert "think" not in result def test_map_openai_params_reasoning_effort_gpt_oss(self): config = OllamaChatConfig() - result = config.map_openai_params( - {"reasoning_effort": {"effort": "medium"}}, {}, "gpt-oss-123" - ) + result = config.map_openai_params({"reasoning_effort": {"effort": "medium"}}, {}, "gpt-oss-123", False) assert result == {"think": "medium"} - diff --git a/tests/test_litellm/llms/ollama/test_ollama_completion_transformation.py b/tests/test_litellm/llms/ollama/test_ollama_completion_transformation.py index dd59cdcac1c..2cad0ead065 100644 --- a/tests/test_litellm/llms/ollama/test_ollama_completion_transformation.py +++ b/tests/test_litellm/llms/ollama/test_ollama_completion_transformation.py @@ -507,3 +507,27 @@ class TestOllamaTextCompletionResponseIterator: assert result["usage"]["prompt_tokens"] == 10 assert result["usage"]["completion_tokens"] == 5 assert result["usage"]["total_tokens"] == 15 + + +class TestOllamaCompletionReasoningEffortMapping: + @pytest.mark.parametrize( + ("reasoning_effort", "model", "expected"), + [ + ("medium", "qwen3", {"think": True}), + ("none", "qwen3", {"think": False}), + ({"effort": "medium", "summary": "auto"}, "qwen3", {"think": True}), + ({"effort": "medium"}, "gpt-oss-123", {"think": "medium"}), + ({"summary": "auto"}, "qwen3", {}), + ], + ) + def test_map_openai_params_reasoning_effort(self, reasoning_effort, model, expected): + config = OllamaConfig() + + result = config.map_openai_params( + {"reasoning_effort": reasoning_effort}, + {}, + model, + False, + ) + + assert result == expected