From 2b1a3b76eab705235de32a458b01e7c0d77204db Mon Sep 17 00:00:00 2001 From: clean99 <53188310+clean99@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:09:09 +0800 Subject: [PATCH 1/2] fix(ollama): normalize string stop sequences to arrays --- litellm/llms/ollama/chat/transformation.py | 2 +- litellm/llms/ollama/completion/transformation.py | 2 +- .../llms/ollama/test_ollama_chat_transformation.py | 11 +++++++++++ .../ollama/test_ollama_completion_transformation.py | 11 +++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py index f9e7071db41..a6b03507954 100644 --- a/litellm/llms/ollama/chat/transformation.py +++ b/litellm/llms/ollama/chat/transformation.py @@ -165,7 +165,7 @@ class OllamaChatConfig(BaseConfig): if param == "frequency_penalty": optional_params["repeat_penalty"] = value if param == "stop": - optional_params["stop"] = value + optional_params["stop"] = [value] if isinstance(value, str) else value if param == "response_format" and isinstance(value, dict) and value.get("type") == "json_object": optional_params["format"] = "json" if param == "response_format" and isinstance(value, dict) and value.get("type") == "json_schema": diff --git a/litellm/llms/ollama/completion/transformation.py b/litellm/llms/ollama/completion/transformation.py index 239b4e32645..46f9db88e06 100644 --- a/litellm/llms/ollama/completion/transformation.py +++ b/litellm/llms/ollama/completion/transformation.py @@ -247,7 +247,7 @@ class OllamaConfig(BaseConfig): elif param == "frequency_penalty": optional_params["frequency_penalty"] = value elif param == "stop": - optional_params["stop"] = value + optional_params["stop"] = [value] if isinstance(value, str) else value elif param == "reasoning_effort" and value is not None: if model.startswith("gpt-oss"): optional_params["think"] = value diff --git a/tests/unit/llms/ollama/test_ollama_chat_transformation.py b/tests/unit/llms/ollama/test_ollama_chat_transformation.py index 1a0bbe171a9..e2026bbd9a8 100644 --- a/tests/unit/llms/ollama/test_ollama_chat_transformation.py +++ b/tests/unit/llms/ollama/test_ollama_chat_transformation.py @@ -27,6 +27,17 @@ from litellm.types.utils import Choices, EmbeddingResponse, Message, ModelRespon from unittest import mock +@pytest.mark.parametrize( + "stop, expected", + [("END", ["END"]), ("", [""]), (["END", "STOP"], ["END", "STOP"]), ([], [])], +) +def test_stop_is_sent_as_an_array(stop: str | list[str], expected: list[str]) -> None: + optional_params: Final = get_optional_params( + model="test-model", custom_llm_provider="ollama_chat", stop=stop + ) + assert optional_params["stop"] == expected + + class TestEvent(BaseModel): name: str value: int diff --git a/tests/unit/llms/ollama/test_ollama_completion_transformation.py b/tests/unit/llms/ollama/test_ollama_completion_transformation.py index 248e745ebff..b0b6a5d3359 100644 --- a/tests/unit/llms/ollama/test_ollama_completion_transformation.py +++ b/tests/unit/llms/ollama/test_ollama_completion_transformation.py @@ -19,6 +19,17 @@ from litellm.llms.ollama.completion.transformation import ( from litellm.types.utils import Message, ModelResponse, ModelResponseStream +@pytest.mark.parametrize( + "stop, expected", + [("END", ["END"]), ("", [""]), (["END", "STOP"], ["END", "STOP"]), ([], [])], +) +def test_stop_is_sent_as_an_array(stop: str | list[str], expected: list[str]) -> None: + optional_params: Final = litellm.utils.get_optional_params( + model="test-model", custom_llm_provider="ollama", stop=stop + ) + assert optional_params["stop"] == expected + + class TestOllamaConfig: def test_transform_response_standard(self): # Initialize config From 86c9269b4285a6b2923c57fb5f34dc65e92308a5 Mon Sep 17 00:00:00 2001 From: clean99 <53188310+clean99@users.noreply.github.com> Date: Mon, 26 Oct 2026 07:03:35 +0800 Subject: [PATCH 2/2] fix(ollama): omit empty string stop sequences --- litellm/llms/ollama/chat/transformation.py | 2 +- litellm/llms/ollama/completion/transformation.py | 2 +- .../ollama/test_ollama_chat_transformation.py | 15 ++++++++++++--- .../test_ollama_completion_transformation.py | 15 ++++++++++++--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py index a6b03507954..c36ce925eab 100644 --- a/litellm/llms/ollama/chat/transformation.py +++ b/litellm/llms/ollama/chat/transformation.py @@ -164,7 +164,7 @@ class OllamaChatConfig(BaseConfig): optional_params["top_p"] = value if param == "frequency_penalty": optional_params["repeat_penalty"] = value - if param == "stop": + if param == "stop" and value != "": optional_params["stop"] = [value] if isinstance(value, str) else value if param == "response_format" and isinstance(value, dict) and value.get("type") == "json_object": optional_params["format"] = "json" diff --git a/litellm/llms/ollama/completion/transformation.py b/litellm/llms/ollama/completion/transformation.py index 46f9db88e06..76af839bb00 100644 --- a/litellm/llms/ollama/completion/transformation.py +++ b/litellm/llms/ollama/completion/transformation.py @@ -246,7 +246,7 @@ class OllamaConfig(BaseConfig): optional_params["top_p"] = value elif param == "frequency_penalty": optional_params["frequency_penalty"] = value - elif param == "stop": + elif param == "stop" and value != "": optional_params["stop"] = [value] if isinstance(value, str) else value elif param == "reasoning_effort" and value is not None: if model.startswith("gpt-oss"): diff --git a/tests/unit/llms/ollama/test_ollama_chat_transformation.py b/tests/unit/llms/ollama/test_ollama_chat_transformation.py index e2026bbd9a8..0c5e5f8fc3b 100644 --- a/tests/unit/llms/ollama/test_ollama_chat_transformation.py +++ b/tests/unit/llms/ollama/test_ollama_chat_transformation.py @@ -29,13 +29,22 @@ from unittest import mock @pytest.mark.parametrize( "stop, expected", - [("END", ["END"]), ("", [""]), (["END", "STOP"], ["END", "STOP"]), ([], [])], + [ + ("END", ["END"]), + ("", None), + (["END", "STOP"], ["END", "STOP"]), + ([""], [""]), + ([], []), + ], ) -def test_stop_is_sent_as_an_array(stop: str | list[str], expected: list[str]) -> None: +def test_stop_is_sent_as_an_array(stop: str | list[str], expected: list[str] | None) -> None: optional_params: Final = get_optional_params( model="test-model", custom_llm_provider="ollama_chat", stop=stop ) - assert optional_params["stop"] == expected + if expected is None: + assert "stop" not in optional_params + else: + assert optional_params["stop"] == expected class TestEvent(BaseModel): diff --git a/tests/unit/llms/ollama/test_ollama_completion_transformation.py b/tests/unit/llms/ollama/test_ollama_completion_transformation.py index b0b6a5d3359..400921dfda3 100644 --- a/tests/unit/llms/ollama/test_ollama_completion_transformation.py +++ b/tests/unit/llms/ollama/test_ollama_completion_transformation.py @@ -21,13 +21,22 @@ from litellm.types.utils import Message, ModelResponse, ModelResponseStream @pytest.mark.parametrize( "stop, expected", - [("END", ["END"]), ("", [""]), (["END", "STOP"], ["END", "STOP"]), ([], [])], + [ + ("END", ["END"]), + ("", None), + (["END", "STOP"], ["END", "STOP"]), + ([""], [""]), + ([], []), + ], ) -def test_stop_is_sent_as_an_array(stop: str | list[str], expected: list[str]) -> None: +def test_stop_is_sent_as_an_array(stop: str | list[str], expected: list[str] | None) -> None: optional_params: Final = litellm.utils.get_optional_params( model="test-model", custom_llm_provider="ollama", stop=stop ) - assert optional_params["stop"] == expected + if expected is None: + assert "stop" not in optional_params + else: + assert optional_params["stop"] == expected class TestOllamaConfig: