diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py
index d4ce4052a7..c70fb97af7 100644
--- a/litellm/llms/ollama/chat/transformation.py
+++ b/litellm/llms/ollama/chat/transformation.py
@@ -137,6 +137,7 @@ class OllamaChatConfig(BaseConfig):
"tool_choice",
"functions",
"response_format",
+ "reasoning_effort",
]
def map_openai_params(
@@ -175,6 +176,8 @@ class OllamaChatConfig(BaseConfig):
if value.get("json_schema") and value["json_schema"].get("schema"):
optional_params["format"] = value["json_schema"]["schema"]
### FUNCTION CALLING LOGIC ###
+ if param == "reasoning_effort" and value is not None:
+ optional_params["think"] = True
if param == "tools":
## CHECK IF MODEL SUPPORTS TOOL CALLING ##
try:
@@ -212,9 +215,9 @@ class OllamaChatConfig(BaseConfig):
litellm.add_function_to_prompt = (
True # so that main.py adds the function call to the prompt
)
- optional_params[
- "functions_unsupported_model"
- ] = non_default_params.get("functions")
+ optional_params["functions_unsupported_model"] = (
+ non_default_params.get("functions")
+ )
non_default_params.pop("tool_choice", None) # causes ollama requests to hang
non_default_params.pop("functions", None) # causes ollama requests to hang
return optional_params
@@ -346,11 +349,31 @@ class OllamaChatConfig(BaseConfig):
## RESPONSE OBJECT
model_response.choices[0].finish_reason = "stop"
+ response_json_message = response_json.get("message")
+ if response_json_message is not None:
+ if "thinking" in response_json_message:
+ # remap 'thinking' to 'reasoning_content'
+ response_json_message["reasoning_content"] = response_json_message[
+ "thinking"
+ ]
+ del response_json_message["thinking"]
+ elif response_json_message.get("content") is not None:
+ # parse reasoning content from content
+ from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
+ _parse_content_for_reasoning,
+ )
+
+ reasoning_content, content = _parse_content_for_reasoning(
+ response_json_message["content"]
+ )
+ response_json_message["reasoning_content"] = reasoning_content
+ response_json_message["content"] = content
+
if (
request_data.get("format", "") == "json"
and litellm_params.get("function_name") is not None
):
- function_call = json.loads(response_json["message"]["content"])
+ function_call = json.loads(response_json_message["content"])
message = litellm.Message(
content=None,
tool_calls=[
@@ -367,11 +390,13 @@ class OllamaChatConfig(BaseConfig):
"type": "function",
}
],
+ reasoning_content=response_json_message.get("reasoning_content"),
)
model_response.choices[0].message = message # type: ignore
model_response.choices[0].finish_reason = "tool_calls"
else:
- _message = litellm.Message(**response_json["message"])
+
+ _message = litellm.Message(**response_json_message)
model_response.choices[0].message = _message # type: ignore
model_response.created = int(time.time())
model_response.model = "ollama_chat/" + model
@@ -412,6 +437,9 @@ class OllamaChatConfig(BaseConfig):
class OllamaChatCompletionResponseIterator(BaseModelResponseIterator):
+ started_reasoning_content: bool = False
+ finished_reasoning_content: bool = False
+
def _is_function_call_complete(self, function_args: Union[str, dict]) -> bool:
if isinstance(function_args, dict):
return True
@@ -465,8 +493,38 @@ class OllamaChatCompletionResponseIterator(BaseModelResponseIterator):
if is_function_call_complete:
tool_call["id"] = str(uuid.uuid4())
+ # PROCESS REASONING CONTENT
+ reasoning_content: Optional[str] = None
+ content: Optional[str] = None
+ if chunk["message"].get("thinking") is not None:
+ if self.started_reasoning_content is False:
+ reasoning_content = chunk["message"].get("thinking")
+ self.started_reasoning_content = True
+ elif self.finished_reasoning_content is False:
+ reasoning_content = chunk["message"].get("thinking")
+ self.finished_reasoning_content = True
+ elif chunk["message"].get("content") is not None:
+ message_content = chunk["message"].get("content")
+ if "" in message_content:
+ message_content = message_content.replace("", "")
+
+ self.started_reasoning_content = True
+
+ if "" in message_content and self.started_reasoning_content:
+ message_content = message_content.replace("", "")
+ self.finished_reasoning_content = True
+
+ if (
+ self.started_reasoning_content
+ and not self.finished_reasoning_content
+ ):
+ reasoning_content = message_content
+ else:
+ content = message_content
+
delta = Delta(
- content=chunk["message"].get("content", ""),
+ content=content,
+ reasoning_content=reasoning_content,
tool_calls=tool_calls,
)
diff --git a/litellm/llms/ollama/completion/transformation.py b/litellm/llms/ollama/completion/transformation.py
index 4f7be507cc..5689864017 100644
--- a/litellm/llms/ollama/completion/transformation.py
+++ b/litellm/llms/ollama/completion/transformation.py
@@ -19,13 +19,13 @@ from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMExcepti
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.openai import AllMessageValues, ChatCompletionUsageBlock
from litellm.types.utils import (
+ Delta,
GenericStreamingChunk,
ModelInfoBase,
ModelResponse,
ModelResponseStream,
ProviderField,
StreamingChoices,
- Delta,
)
from ..common_utils import OllamaError, _convert_image
@@ -92,9 +92,9 @@ class OllamaConfig(BaseConfig):
repeat_penalty: Optional[float] = None
temperature: Optional[float] = None
seed: Optional[int] = None
- stop: Optional[
- list
- ] = None # stop is a list based on this - https://github.com/ollama/ollama/pull/442
+ stop: Optional[list] = (
+ None # stop is a list based on this - https://github.com/ollama/ollama/pull/442
+ )
tfs_z: Optional[float] = None
num_predict: Optional[int] = None
top_k: Optional[int] = None
@@ -154,6 +154,7 @@ class OllamaConfig(BaseConfig):
"stop",
"response_format",
"max_completion_tokens",
+ "reasoning_effort",
]
def map_openai_params(
@@ -166,19 +167,21 @@ class OllamaConfig(BaseConfig):
for param, value in non_default_params.items():
if param == "max_tokens" or param == "max_completion_tokens":
optional_params["num_predict"] = value
- if param == "stream":
+ elif param == "stream":
optional_params["stream"] = value
- if param == "temperature":
+ elif param == "temperature":
optional_params["temperature"] = value
- if param == "seed":
+ elif param == "seed":
optional_params["seed"] = value
- if param == "top_p":
+ elif param == "top_p":
optional_params["top_p"] = value
- if param == "frequency_penalty":
+ elif param == "frequency_penalty":
optional_params["frequency_penalty"] = value
- if param == "stop":
+ elif param == "stop":
optional_params["stop"] = value
- if param == "response_format" and isinstance(value, dict):
+ elif param == "reasoning_effort" and value is not None:
+ optional_params["think"] = True
+ elif param == "response_format" and isinstance(value, dict):
if value["type"] == "json_object":
optional_params["format"] = "json"
elif value["type"] == "json_schema":
@@ -258,12 +261,17 @@ class OllamaConfig(BaseConfig):
api_key: Optional[str] = None,
json_mode: Optional[bool] = None,
) -> ModelResponse:
+ from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
+ _parse_content_for_reasoning,
+ )
+
response_json = raw_response.json()
## RESPONSE OBJECT
model_response.choices[0].finish_reason = "stop"
if request_data.get("format", "") == "json":
# Check if response field exists and is not empty before parsing JSON
response_text = response_json.get("response", "")
+
if not response_text or not response_text.strip():
# Handle empty response gracefully - set empty content
message = litellm.Message(content="")
@@ -288,7 +296,9 @@ class OllamaConfig(BaseConfig):
"id": f"call_{str(uuid.uuid4())}",
"function": {
"name": function_call["name"],
- "arguments": json.dumps(function_call["arguments"]),
+ "arguments": json.dumps(
+ function_call["arguments"]
+ ),
},
"type": "function",
}
@@ -305,11 +315,26 @@ class OllamaConfig(BaseConfig):
model_response.choices[0].finish_reason = "stop"
except json.JSONDecodeError:
# If JSON parsing fails, treat as regular text response
- message = litellm.Message(content=response_text)
+ ## output parse reasoning content from response_text
+ reasoning_content: Optional[str] = None
+ content: Optional[str] = None
+ if response_text is not None:
+ reasoning_content, content = _parse_content_for_reasoning(
+ response_text
+ )
+ message = litellm.Message(
+ content=content, reasoning_content=reasoning_content
+ )
model_response.choices[0].message = message # type: ignore
model_response.choices[0].finish_reason = "stop"
else:
- model_response.choices[0].message.content = response_json["response"] # type: ignore
+ response_text = response_json.get("response", "")
+ content = None
+ reasoning_content = None
+ if response_text is not None:
+ reasoning_content, content = _parse_content_for_reasoning(response_text)
+ model_response.choices[0].message.content = content # type: ignore
+ model_response.choices[0].message.reasoning_content = reasoning_content # type: ignore
model_response.created = int(time.time())
model_response.model = "ollama/" + model
_prompt = request_data.get("prompt", "")
@@ -434,12 +459,21 @@ class OllamaConfig(BaseConfig):
class OllamaTextCompletionResponseIterator(BaseModelResponseIterator):
+ def __init__(
+ self, streaming_response, sync_stream: bool, json_mode: Optional[bool] = False
+ ):
+ super().__init__(streaming_response, sync_stream, json_mode)
+ self.started_reasoning_content: bool = False
+ self.finished_reasoning_content: bool = False
+
def _handle_string_chunk(
self, str_line: str
) -> Union[GenericStreamingChunk, ModelResponseStream]:
return self.chunk_parser(json.loads(str_line))
- def chunk_parser(self, chunk: dict) -> Union[GenericStreamingChunk, ModelResponseStream]:
+ def chunk_parser(
+ self, chunk: dict
+ ) -> Union[GenericStreamingChunk, ModelResponseStream]:
try:
if "error" in chunk:
raise Exception(f"Ollama Error - {chunk}")
@@ -469,12 +503,42 @@ class OllamaTextCompletionResponseIterator(BaseModelResponseIterator):
)
elif chunk["response"]:
text = chunk["response"]
- return GenericStreamingChunk(
- text=text,
- is_finished=is_finished,
- finish_reason="stop",
+ reasoning_content: Optional[str] = None
+ content: Optional[str] = None
+ if text is not None:
+ if "" in text:
+ text = text.replace("", "")
+ self.started_reasoning_content = True
+ elif "" in text:
+ text = text.replace("", "")
+ self.finished_reasoning_content = True
+
+ if (
+ self.started_reasoning_content
+ and not self.finished_reasoning_content
+ ):
+ reasoning_content = text
+ else:
+ content = text
+
+ return ModelResponseStream(
+ choices=[
+ StreamingChoices(
+ index=0,
+ delta=Delta(
+ reasoning_content=reasoning_content, content=content
+ ),
+ )
+ ],
+ finish_reason=finish_reason,
usage=None,
)
+ # return GenericStreamingChunk(
+ # text=text,
+ # is_finished=is_finished,
+ # 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 ""
diff --git a/litellm/proxy/_experimental/out/model_hub_table.html b/litellm/proxy/_experimental/out/model_hub_table/index.html
similarity index 100%
rename from litellm/proxy/_experimental/out/model_hub_table.html
rename to litellm/proxy/_experimental/out/model_hub_table/index.html
diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html
deleted file mode 100644
index 5c5f1cfe90..0000000000
--- a/litellm/proxy/_experimental/out/onboarding.html
+++ /dev/null
@@ -1 +0,0 @@
-
LiteLLM Dashboard
\ No newline at end of file
diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml
index f4dc1fca71..b38272560d 100644
--- a/litellm/proxy/_new_secret_config.yaml
+++ b/litellm/proxy/_new_secret_config.yaml
@@ -1,22 +1,27 @@
model_list:
- - model_name: fake-openai-endpoint
- litellm_params:
- model: openai/fake
- api_key: fake-key
- api_base: https://exampleopenaiendpoint-production.up.railway.app/
- - model_name: gpt-5-mini
- litellm_params:
- model: azure/gpt-5-mini
- api_base: os.environ/AZURE_GPT_5_MINI_API_BASE # runs os.getenv("AZURE_API_BASE")
- api_key: os.environ/AZURE_GPT_5_MINI_API_KEY # runs os.getenv("AZURE_API_KEY")
- stream_timeout: 60
- merge_reasoning_content_in_choices: true
- model_info:
- mode: chat
+ - model_name: fake-openai-endpoint
+ litellm_params:
+ model: openai/fake
+ api_key: fake-key
+ api_base: https://exampleopenaiendpoint-production.up.railway.app/
+ - model_name: gpt-5-mini
+ litellm_params:
+ model: azure/gpt-5-mini
+ api_base: os.environ/AZURE_GPT_5_MINI_API_BASE # runs os.getenv("AZURE_API_BASE")
+ api_key: os.environ/AZURE_GPT_5_MINI_API_KEY # runs os.getenv("AZURE_API_KEY")
+ stream_timeout: 60
+ merge_reasoning_content_in_choices: true
+ model_info:
+ mode: chat
+ - model_name: ollama-deepseek-r1
+ litellm_params:
+ model: ollama/deepseek-r1:1.5b
+ model_info:
+ mode: chat
router_settings:
model_group_alias: {"my-fake-gpt-4": "fake-openai-endpoint"}
-
+
litellm_settings:
callbacks: ["otel"]
- success_callback: ["braintrust"]
\ No newline at end of file
+ success_callback: ["braintrust"]
diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py
index 9b6cad3800..6e7c415077 100644
--- a/litellm/types/llms/openai.py
+++ b/litellm/types/llms/openai.py
@@ -1029,29 +1029,29 @@ class ResponseAPIUsage(BaseLiteLLMOpenAIResponseObject):
class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject):
id: str
created_at: int
- error: Optional[dict]
- incomplete_details: Optional[IncompleteDetails]
- instructions: Optional[str]
- metadata: Optional[Dict]
- model: Optional[str]
- object: Optional[str]
+ error: Optional[dict] = None
+ incomplete_details: Optional[IncompleteDetails] = None
+ instructions: Optional[str] = None
+ metadata: Optional[Dict] = None
+ model: Optional[str] = None
+ object: Optional[str] = None
output: Union[
List[Union[ResponseOutputItem, Dict]],
List[Union[GenericResponseOutputItem, OutputFunctionToolCall]],
]
parallel_tool_calls: bool
- temperature: Optional[float]
+ temperature: Optional[float] = None
tool_choice: ToolChoice
tools: Union[List[Tool], List[ResponseFunctionToolCall], List[Dict[str, Any]]]
top_p: Optional[float]
- max_output_tokens: Optional[int]
- previous_response_id: Optional[str]
- reasoning: Optional[Reasoning]
- status: Optional[str]
- text: Optional[Union["ResponseText", Dict[str, Any]]]
- truncation: Optional[Literal["auto", "disabled"]]
- usage: Optional[ResponseAPIUsage]
- user: Optional[str]
+ max_output_tokens: Optional[int] = None
+ previous_response_id: Optional[str] = None
+ reasoning: Optional[Reasoning] = None
+ status: Optional[str] = None
+ text: Optional[Union["ResponseText", Dict[str, Any]]] = None
+ truncation: Optional[Literal["auto", "disabled"]] = None
+ usage: Optional[ResponseAPIUsage] = None
+ user: Optional[str] = None
store: Optional[bool] = None
# Define private attributes using PrivateAttr
_hidden_params: dict = PrivateAttr(default_factory=dict)
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 985d51f99d..452f5a9402 100644
--- a/tests/test_litellm/llms/ollama/test_ollama_completion_transformation.py
+++ b/tests/test_litellm/llms/ollama/test_ollama_completion_transformation.py
@@ -159,6 +159,261 @@ class TestOllamaConfig:
assert result.choices[0]["finish_reason"] == "stop"
# No usage assertions here as we don't need to test them in every case
+ def test_transform_response_with_thinking_tags(self):
+ """Test that responses with ... tags parse reasoning content correctly."""
+ # Initialize config
+ config = OllamaConfig()
+
+ # Create mock response with thinking tags
+ raw_response = MagicMock()
+ raw_response.json.return_value = {
+ "response": "I need to think about this problem step by stepHere is my answer",
+ "prompt_eval_count": 15,
+ "eval_count": 8,
+ }
+
+ # Create properly structured model response object
+ model_response = ModelResponse(
+ id="test_id",
+ choices=[{"message": Message(content="")}],
+ )
+
+ # Create mock encoding
+ mock_encoding = MagicMock()
+ mock_encoding.encode.return_value = [1, 2, 3]
+
+ # Transform response
+ result = config.transform_response(
+ model="llama2",
+ raw_response=raw_response,
+ model_response=model_response,
+ logging_obj=MagicMock(),
+ request_data={},
+ messages=[],
+ optional_params={},
+ litellm_params={},
+ encoding=mock_encoding,
+ )
+
+ # Verify reasoning content is extracted
+ assert (
+ result.choices[0]["message"].reasoning_content
+ == "I need to think about this problem step by step"
+ )
+ assert result.choices[0]["message"].content == "Here is my answer"
+ assert result.choices[0]["finish_reason"] == "stop"
+
+ def test_transform_response_with_thinking_tags_alternative(self):
+ """Test that responses with ... tags parse reasoning content correctly."""
+ # Initialize config
+ config = OllamaConfig()
+
+ # Create mock response with thinking tags (alternative format)
+ raw_response = MagicMock()
+ raw_response.json.return_value = {
+ "response": "Let me analyze this carefullyThe solution is X",
+ }
+
+ # Create properly structured model response object
+ model_response = ModelResponse(
+ id="test_id",
+ choices=[{"message": Message(content="")}],
+ )
+
+ # Create mock encoding
+ mock_encoding = MagicMock()
+ mock_encoding.encode.return_value = [1, 2, 3]
+
+ # Transform response
+ result = config.transform_response(
+ model="llama2",
+ raw_response=raw_response,
+ model_response=model_response,
+ logging_obj=MagicMock(),
+ request_data={},
+ messages=[],
+ optional_params={},
+ litellm_params={},
+ encoding=mock_encoding,
+ )
+
+ # Verify reasoning content is extracted
+ assert (
+ result.choices[0]["message"].reasoning_content
+ == "Let me analyze this carefully"
+ )
+ assert result.choices[0]["message"].content == "The solution is X"
+ assert result.choices[0]["finish_reason"] == "stop"
+
+ def test_transform_response_with_multiline_thinking_tags(self):
+ """Test that responses with multiline thinking content work correctly."""
+ # Initialize config
+ config = OllamaConfig()
+
+ # Create mock response with multiline thinking content
+ raw_response = MagicMock()
+ raw_response.json.return_value = {
+ "response": "\nThis is a complex problem.\nI need to break it down:\n1. First step\n2. Second step\nBased on my analysis, the answer is Y",
+ }
+
+ # Create properly structured model response object
+ model_response = ModelResponse(
+ id="test_id",
+ choices=[{"message": Message(content="")}],
+ )
+
+ # Create mock encoding
+ mock_encoding = MagicMock()
+ mock_encoding.encode.return_value = [1, 2, 3]
+
+ # Transform response
+ result = config.transform_response(
+ model="llama2",
+ raw_response=raw_response,
+ model_response=model_response,
+ logging_obj=MagicMock(),
+ request_data={},
+ messages=[],
+ optional_params={},
+ litellm_params={},
+ encoding=mock_encoding,
+ )
+
+ # Verify multiline reasoning content is extracted
+ expected_reasoning = "\nThis is a complex problem.\nI need to break it down:\n1. First step\n2. Second step\n"
+ assert result.choices[0]["message"].reasoning_content == expected_reasoning
+ assert (
+ result.choices[0]["message"].content
+ == "Based on my analysis, the answer is Y"
+ )
+ assert result.choices[0]["finish_reason"] == "stop"
+
+ def test_transform_response_thinking_only(self):
+ """Test response with only thinking content and no additional content."""
+ # Initialize config
+ config = OllamaConfig()
+
+ # Create mock response with only thinking content
+ raw_response = MagicMock()
+ raw_response.json.return_value = {
+ "response": "Just internal thoughts, no response",
+ }
+
+ # Create properly structured model response object
+ model_response = ModelResponse(
+ id="test_id",
+ choices=[{"message": Message(content="")}],
+ )
+
+ # Create mock encoding
+ mock_encoding = MagicMock()
+ mock_encoding.encode.return_value = [1, 2, 3]
+
+ # Transform response
+ result = config.transform_response(
+ model="llama2",
+ raw_response=raw_response,
+ model_response=model_response,
+ logging_obj=MagicMock(),
+ request_data={},
+ messages=[],
+ optional_params={},
+ litellm_params={},
+ encoding=mock_encoding,
+ )
+
+ # Verify reasoning content is extracted and content is empty
+ assert (
+ result.choices[0]["message"].reasoning_content
+ == "Just internal thoughts, no response"
+ )
+ assert result.choices[0]["message"].content == ""
+ assert result.choices[0]["finish_reason"] == "stop"
+
+ def test_transform_response_json_mode_with_thinking_tags(self):
+ """Test JSON mode with thinking tags - should handle as text when JSON parsing fails."""
+ # Initialize config
+ config = OllamaConfig()
+
+ # Create mock response with thinking tags in JSON mode
+ raw_response = MagicMock()
+ raw_response.json.return_value = {
+ "response": "Planning my JSON responseThis is not valid JSON",
+ }
+
+ # Create properly structured model response object
+ model_response = ModelResponse(
+ id="test_id",
+ choices=[{"message": Message(content="")}],
+ )
+
+ # Create mock encoding
+ mock_encoding = MagicMock()
+ mock_encoding.encode.return_value = [1, 2, 3]
+
+ # Transform response
+ result = config.transform_response(
+ model="llama2",
+ raw_response=raw_response,
+ model_response=model_response,
+ logging_obj=MagicMock(),
+ request_data={"format": "json"},
+ messages=[],
+ optional_params={},
+ litellm_params={},
+ encoding=mock_encoding,
+ )
+
+ # Verify reasoning content is extracted even in JSON mode when JSON parsing fails
+ assert (
+ result.choices[0]["message"].reasoning_content
+ == "Planning my JSON response"
+ )
+ assert result.choices[0]["message"].content == "This is not valid JSON"
+ assert result.choices[0]["finish_reason"] == "stop"
+
+ def test_transform_response_no_thinking_tags(self):
+ """Test that responses without thinking tags work normally."""
+ # Initialize config
+ config = OllamaConfig()
+
+ # Create mock response without thinking tags
+ raw_response = MagicMock()
+ raw_response.json.return_value = {
+ "response": "Regular response without any thinking tags",
+ }
+
+ # Create properly structured model response object
+ model_response = ModelResponse(
+ id="test_id",
+ choices=[{"message": Message(content="")}],
+ )
+
+ # Create mock encoding
+ mock_encoding = MagicMock()
+ mock_encoding.encode.return_value = [1, 2, 3]
+
+ # Transform response
+ result = config.transform_response(
+ model="llama2",
+ raw_response=raw_response,
+ model_response=model_response,
+ logging_obj=MagicMock(),
+ request_data={},
+ messages=[],
+ optional_params={},
+ litellm_params={},
+ encoding=mock_encoding,
+ )
+
+ # Verify no reasoning content is extracted
+ assert result.choices[0]["message"].reasoning_content is None
+ assert (
+ result.choices[0]["message"].content
+ == "Regular response without any thinking tags"
+ )
+ assert result.choices[0]["finish_reason"] == "stop"
+
class TestOllamaTextCompletionResponseIterator:
def test_chunk_parser_with_thinking_field(self):
@@ -199,10 +454,11 @@ class TestOllamaTextCompletionResponseIterator:
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
+ # Updated to handle ModelResponseStream return type
+ assert isinstance(result, ModelResponseStream)
+ assert result.choices and result.choices[0].delta is not None
+ assert result.choices[0].delta.content == "Hello world"
+ assert getattr(result.choices[0].delta, "reasoning_content", None) is None
def test_chunk_parser_done_chunk(self):
"""Test that done chunks work correctly."""