From 8ef1e25f8c56c7f7f3a1ed7e13d5cae8b40cfb67 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 7 Feb 2026 23:15:13 +0300 Subject: [PATCH] Fix JSON serialization of time objects in Ollama tool results (#162502) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/ollama/entity.py | 3 +- tests/components/ollama/test_conversation.py | 101 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/ollama/entity.py b/homeassistant/components/ollama/entity.py index 77c0acdca3a..946f0fea917 100644 --- a/homeassistant/components/ollama/entity.py +++ b/homeassistant/components/ollama/entity.py @@ -16,6 +16,7 @@ from homeassistant.config_entries import ConfigSubentry from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, llm from homeassistant.helpers.entity import Entity +from homeassistant.helpers.json import json_dumps from . import OllamaConfigEntry from .const import ( @@ -93,7 +94,7 @@ def _convert_content( if isinstance(chat_content, conversation.ToolResultContent): return ollama.Message( role=MessageRole.TOOL.value, - content=json.dumps(chat_content.tool_result), + content=json_dumps(chat_content.tool_result), ) if isinstance(chat_content, conversation.AssistantContent): return ollama.Message( diff --git a/tests/components/ollama/test_conversation.py b/tests/components/ollama/test_conversation.py index 74e47124606..9e91da87fda 100644 --- a/tests/components/ollama/test_conversation.py +++ b/tests/components/ollama/test_conversation.py @@ -1,6 +1,7 @@ """Tests for the Ollama integration.""" from collections.abc import AsyncGenerator +import datetime from typing import Any from unittest.mock import AsyncMock, Mock, patch @@ -23,6 +24,10 @@ from homeassistant.helpers import ( ) from tests.common import MockConfigEntry +from tests.components.conversation import ( + MockChatLog, + mock_chat_log, # noqa: F401 +) @pytest.fixture(autouse=True) @@ -458,6 +463,102 @@ async def test_function_exception( ) +async def test_history_conversion( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_init_component, + mock_chat_log: MockChatLog, # noqa: F811 +) -> None: + """Test that the pre-existing chat_log history is handled properly.""" + + agent_id = "conversation.ollama_conversation" + + # Add some pre-existing content from conversation.default_agent + mock_chat_log.async_add_user_content( + conversation.UserContent(content="What time is it?") + ) + mock_chat_log.async_add_assistant_content_without_tools( + conversation.AssistantContent( + agent_id=agent_id, + tool_calls=[ + llm.ToolInput( + tool_name="HassGetCurrentTime", + tool_args={}, + id="01KGW7TFC1VVVK7ANHVMDA4DJ6", + external=True, + ) + ], + ) + ) + mock_chat_log.async_add_assistant_content_without_tools( + conversation.ToolResultContent( + agent_id=agent_id, + tool_call_id="01KGW7TFC1VVVK7ANHVMDA4DJ6", + tool_name="HassGetCurrentTime", + tool_result={ + "speech": {"plain": {"speech": "4:24 PM", "extra_data": None}}, + "response_type": "action_done", + "speech_slots": {"time": datetime.time(16, 24, 17, 813343)}, + "data": {"targets": [], "success": [], "failed": []}, + }, + ) + ) + mock_chat_log.async_add_assistant_content_without_tools( + conversation.AssistantContent( + agent_id=agent_id, + content="4:24 PM", + ) + ) + + entry = MockConfigEntry() + entry.add_to_hass(hass) + + with patch( + "ollama.AsyncClient.chat", + return_value=stream_generator( + {"message": {"role": "assistant", "content": "test response"}} + ), + ) as mock_chat: + result = await conversation.async_converse( + hass, + "test message", + mock_chat_log.conversation_id, + Context(), + agent_id=agent_id, + ) + + assert mock_chat.call_count == 1 + args = mock_chat.call_args.kwargs + prompt = args["messages"][0]["content"] + + assert args["model"] == "test_model:latest" + assert args["messages"] == [ + Message(role="system", content=prompt), + Message(role="user", content="What time is it?"), + Message( + role="assistant", + tool_calls=[ + Message.ToolCall( + function=Message.ToolCall.Function( + name="HassGetCurrentTime", arguments={} + ) + ) + ], + ), + Message( + role="tool", + content='{"speech":{"plain":{"speech":"4:24 PM","extra_data":null}},"response_type":"action_done","speech_slots":{"time":"16:24:17.813343"},"data":{"targets":[],"success":[],"failed":[]}}', + ), + Message(role="assistant", content="4:24 PM"), + Message(role="user", content="test message"), + ] + + assert result.response.response_type == intent.IntentResponseType.ACTION_DONE, ( + result + ) + assert result.response.speech["plain"]["speech"] == "test response" + + async def test_unknown_hass_api( hass: HomeAssistant, mock_config_entry: MockConfigEntry,