diff --git a/ollama/_client.py b/ollama/_client.py index 8dfce824..898854fd 100644 --- a/ollama/_client.py +++ b/ollama/_client.py @@ -1320,8 +1320,9 @@ def _copy_images(images: Optional[Sequence[Union[Image, Any]]]) -> Iterator[Imag def _copy_messages(messages: Optional[Sequence[Union[Mapping[str, Any], Message]]]) -> Iterator[Message]: for message in messages or []: + # Keep empty strings (e.g. tool results with content='') — only drop None. yield Message.model_validate( - {k: list(_copy_images(v)) if k == 'images' else v for k, v in dict(message).items() if v}, + {k: list(_copy_images(v)) if k == 'images' else v for k, v in dict(message).items() if v is not None}, ) diff --git a/tests/test_client.py b/tests/test_client.py index 7b7ab38e..89170a60 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1379,6 +1379,22 @@ async def test_arbitrary_roles_accepted_in_message_request_async(monkeypatch: py await client.chat(model='llama3.1', messages=[{'role': 'somerandomrole', 'content': "I'm ok with you adding any role message now!"}, {'role': 'user', 'content': 'Hello world!'}]) +def test_copy_messages_preserves_empty_string_content(): + from ollama._client import _copy_messages + + msgs = list( + _copy_messages( + [ + {'role': 'assistant', 'content': ''}, + {'role': 'tool', 'content': '', 'tool_name': 'web_search'}, + ] + ) + ) + assert msgs[0].content == '' + assert msgs[1].content == '' + assert msgs[1].tool_name == 'web_search' + + def test_client_web_search_requires_bearer_auth_header(monkeypatch: pytest.MonkeyPatch): monkeypatch.delenv('OLLAMA_API_KEY', raising=False)