Merge pull request #13375 from colesmcintosh/fix/ollama-gpt-oss-thinking-field

This commit is contained in:
Krish Dholakia
2025-08-13 07:58:57 -07:00
committed by GitHub
2 changed files with 90 additions and 3 deletions
@@ -24,6 +24,8 @@ from litellm.types.utils import (
ModelResponse,
ModelResponseStream,
ProviderField,
StreamingChoices,
Delta,
)
from ..common_utils import OllamaError, _convert_image
@@ -423,7 +425,7 @@ class OllamaTextCompletionResponseIterator(BaseModelResponseIterator):
) -> Union[GenericStreamingChunk, ModelResponseStream]:
return self.chunk_parser(json.loads(str_line))
def chunk_parser(self, chunk: dict) -> GenericStreamingChunk:
def chunk_parser(self, chunk: dict) -> Union[GenericStreamingChunk, ModelResponseStream]:
try:
if "error" in chunk:
raise Exception(f"Ollama Error - {chunk}")
@@ -459,6 +461,17 @@ class OllamaTextCompletionResponseIterator(BaseModelResponseIterator):
finish_reason="stop",
usage=None,
)
elif "thinking" in chunk and not chunk["response"]:
# Return reasoning content as ModelResponseStream so UIs can render it
thinking_content = chunk.get("thinking") or ""
return ModelResponseStream(
choices=[
StreamingChoices(
index=0,
delta=Delta(reasoning_content=thinking_content),
)
]
)
else:
raise Exception(f"Unable to parse ollama chunk - {chunk}")
except Exception as e:
@@ -10,8 +10,11 @@ sys.path.insert(
0, os.path.abspath("../../../../..")
) # Adds the parent directory to the system path
from litellm.llms.ollama.completion.transformation import OllamaConfig
from litellm.types.utils import Message, ModelResponse
from litellm.llms.ollama.completion.transformation import (
OllamaConfig,
OllamaTextCompletionResponseIterator,
)
from litellm.types.utils import Message, ModelResponse, ModelResponseStream
class TestOllamaConfig:
@@ -155,3 +158,74 @@ class TestOllamaConfig:
assert result.choices[0]["message"].content == expected_content
assert result.choices[0]["finish_reason"] == "stop"
# No usage assertions here as we don't need to test them in every case
class TestOllamaTextCompletionResponseIterator:
def test_chunk_parser_with_thinking_field(self):
"""Test that chunks with 'thinking' field and empty 'response' are handled correctly."""
iterator = OllamaTextCompletionResponseIterator(
streaming_response=iter([]), sync_stream=True, json_mode=False
)
# Test chunk with thinking field - this is the problematic case from the issue
chunk_with_thinking = {
"model": "gpt-oss:20b",
"created_at": "2025-08-06T14:34:31.5276077Z",
"response": "",
"thinking": "User",
"done": False,
}
result = iterator.chunk_parser(chunk_with_thinking)
# Should return a ModelResponseStream with reasoning content
assert isinstance(result, ModelResponseStream)
assert result.choices and result.choices[0].delta is not None
assert getattr(result.choices[0].delta, "reasoning_content") == "User"
def test_chunk_parser_normal_response(self):
"""Test that normal response chunks still work."""
iterator = OllamaTextCompletionResponseIterator(
streaming_response=iter([]), sync_stream=True, json_mode=False
)
# Test normal chunk with response
normal_chunk = {
"model": "llama2",
"created_at": "2025-08-06T14:34:31.5276077Z",
"response": "Hello world",
"done": False,
}
result = iterator.chunk_parser(normal_chunk)
assert result["text"] == "Hello world"
assert result["is_finished"] is False
assert result["finish_reason"] == "stop"
assert result["usage"] is None
def test_chunk_parser_done_chunk(self):
"""Test that done chunks work correctly."""
iterator = OllamaTextCompletionResponseIterator(
streaming_response=iter([]), sync_stream=True, json_mode=False
)
# Test done chunk
done_chunk = {
"model": "llama2",
"created_at": "2025-08-06T14:34:31.5276077Z",
"response": "",
"done": True,
"prompt_eval_count": 10,
"eval_count": 5,
}
result = iterator.chunk_parser(done_chunk)
assert result["text"] == ""
assert result["is_finished"] is True
assert result["finish_reason"] == "stop"
assert result["usage"] is not None
assert result["usage"]["prompt_tokens"] == 10
assert result["usage"]["completion_tokens"] == 5
assert result["usage"]["total_tokens"] == 15