From 9d8dc9ec0648749bbd13e423a1a2847ca236c5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20=C4=8Cerm=C3=A1k?= Date: Fri, 9 Jan 2026 12:06:36 +0100 Subject: [PATCH] Fix JSON serialization of time objects in anthropic tool results (#160459) Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> --- homeassistant/components/anthropic/entity.py | 3 +- .../snapshots/test_conversation.ambr | 60 ++++++++++++++++++- .../components/anthropic/test_conversation.py | 31 +++++++++- 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/anthropic/entity.py b/homeassistant/components/anthropic/entity.py index 2c71c2527ed5..0d07ae714328 100644 --- a/homeassistant/components/anthropic/entity.py +++ b/homeassistant/components/anthropic/entity.py @@ -69,6 +69,7 @@ from homeassistant.core import HomeAssistant 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 homeassistant.util import slugify from . import AnthropicConfigEntry @@ -193,7 +194,7 @@ def _convert_content( tool_result_block = ToolResultBlockParam( type="tool_result", tool_use_id=content.tool_call_id, - content=json.dumps(content.tool_result), + content=json_dumps(content.tool_result), ) external_tool = False if not messages or messages[-1]["role"] != ( diff --git a/tests/components/anthropic/snapshots/test_conversation.ambr b/tests/components/anthropic/snapshots/test_conversation.ambr index df5e2b3f5acc..acb4ced2c36e 100644 --- a/tests/components/anthropic/snapshots/test_conversation.ambr +++ b/tests/components/anthropic/snapshots/test_conversation.ambr @@ -309,12 +309,12 @@ 'type': 'text', }), dict({ - 'content': '{"success": true, "response": "Lights are off."}', + 'content': '{"success":true,"response":"Lights are off."}', 'tool_use_id': 'mock-tool-call-id', 'type': 'tool_result', }), dict({ - 'content': '{"success": false, "response": "Not enough milk."}', + 'content': '{"success":false,"response":"Not enough milk."}', 'tool_use_id': 'mock-tool-call-id-2', 'type': 'tool_result', }), @@ -462,6 +462,62 @@ }), ]) # --- +# name: test_history_conversion[content6] + list([ + dict({ + 'content': 'What time is it?', + 'role': 'user', + }), + dict({ + 'content': list([ + dict({ + 'text': 'Let me check the time for you.', + 'type': 'text', + }), + dict({ + 'id': 'mock-tool-call-id', + 'input': dict({ + }), + 'name': 'GetCurrentTime', + 'type': 'tool_use', + }), + ]), + 'role': 'assistant', + }), + dict({ + 'content': list([ + dict({ + 'content': '{"speech_slots":{"time":"14:30:00"},"message":"Current time retrieved"}', + 'tool_use_id': 'mock-tool-call-id', + 'type': 'tool_result', + }), + ]), + 'role': 'user', + }), + dict({ + 'content': list([ + dict({ + 'text': 'It is currently 2:30 PM.', + 'type': 'text', + }), + ]), + 'role': 'assistant', + }), + dict({ + 'content': 'Are you sure?', + 'role': 'user', + }), + dict({ + 'content': list([ + dict({ + 'text': 'Yes, I am sure!', + 'type': 'text', + }), + ]), + 'role': 'assistant', + }), + ]) +# --- # name: test_redacted_thinking list([ dict({ diff --git a/tests/components/anthropic/test_conversation.py b/tests/components/anthropic/test_conversation.py index cf5a3d17c828..ac54272fe0a2 100644 --- a/tests/components/anthropic/test_conversation.py +++ b/tests/components/anthropic/test_conversation.py @@ -1,5 +1,6 @@ """Tests for the Anthropic integration.""" +import datetime from typing import Any from unittest.mock import AsyncMock, Mock, patch @@ -317,7 +318,7 @@ async def test_function_exception( "role": "user", "content": [ { - "content": '{"error": "HomeAssistantError", "error_text": "Test tool exception"}', + "content": '{"error":"HomeAssistantError","error_text":"Test tool exception"}', "tool_use_id": "toolu_0123456789AbCdEfGhIjKlM", "type": "tool_result", } @@ -893,6 +894,34 @@ async def test_web_search( ), ), ], + [ + conversation.chat_log.SystemContent("You are a helpful assistant."), + conversation.chat_log.UserContent("What time is it?"), + conversation.chat_log.AssistantContent( + agent_id="conversation.claude_conversation", + content="Let me check the time for you.", + tool_calls=[ + llm.ToolInput( + id="mock-tool-call-id", + tool_name="GetCurrentTime", + tool_args={}, + ), + ], + ), + conversation.chat_log.ToolResultContent( + agent_id="conversation.claude_conversation", + tool_call_id="mock-tool-call-id", + tool_name="GetCurrentTime", + tool_result={ + "speech_slots": {"time": datetime.time(14, 30, 0)}, + "message": "Current time retrieved", + }, + ), + conversation.chat_log.AssistantContent( + agent_id="conversation.claude_conversation", + content="It is currently 2:30 PM.", + ), + ], ], ) async def test_history_conversion(