Fix JSON serialization of time objects in OpenAI tool results (#162490)

This commit is contained in:
Denis Shulyaka
2026-02-07 23:13:38 +03:00
committed by GitHub
parent a98010d0c1
commit ce3dd2b6db
3 changed files with 127 additions and 6 deletions

View File

@@ -64,6 +64,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, issue_registry as ir, llm
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.json import json_dumps
from homeassistant.util import slugify
from .const import (
@@ -183,7 +184,7 @@ def _convert_content_to_param(
FunctionCallOutput(
type="function_call_output",
call_id=content.tool_call_id,
output=json.dumps(content.tool_result),
output=json_dumps(content.tool_result),
)
)
continue
@@ -217,7 +218,7 @@ def _convert_content_to_param(
ResponseFunctionToolCallParam(
type="function_call",
name=tool_call.tool_name,
arguments=json.dumps(tool_call.tool_args),
arguments=json_dumps(tool_call.tool_args),
call_id=tool_call.id,
)
)

View File

@@ -7,14 +7,14 @@
'type': 'message',
}),
dict({
'arguments': '{"code": "import math\\nmath.sqrt(55555)", "container": "cntr_A"}',
'arguments': '{"code":"import math\\nmath.sqrt(55555)","container":"cntr_A"}',
'call_id': 'ci_A',
'name': 'code_interpreter',
'type': 'function_call',
}),
dict({
'call_id': 'ci_A',
'output': '{"output": [{"logs": "235.70108188126758\\n", "type": "logs"}]}',
'output': '{"output":[{"logs":"235.70108188126758\\n","type":"logs"}]}',
'type': 'function_call_output',
}),
dict({
@@ -36,6 +36,65 @@
# ---
# name: test_function_call
list([
dict({
'attachments': None,
'content': 'What time is it?',
'created': HAFakeDatetime(2025, 10, 31, 12, 0, tzinfo=datetime.timezone.utc),
'role': 'user',
}),
dict({
'agent_id': 'conversation.openai_conversation',
'content': None,
'created': HAFakeDatetime(2025, 10, 31, 12, 0, tzinfo=datetime.timezone.utc),
'native': None,
'role': 'assistant',
'thinking_content': None,
'tool_calls': list([
dict({
'external': True,
'id': 'mock-tool-call-id',
'tool_args': dict({
}),
'tool_name': 'HassGetCurrentTime',
}),
]),
}),
dict({
'agent_id': 'conversation.openai_conversation',
'created': HAFakeDatetime(2025, 10, 31, 12, 0, tzinfo=datetime.timezone.utc),
'role': 'tool_result',
'tool_call_id': 'mock-tool-call-id',
'tool_name': 'HassGetCurrentTime',
'tool_result': dict({
'data': dict({
'failed': list([
]),
'success': list([
]),
'targets': list([
]),
}),
'response_type': 'action_done',
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': '12:00 PM',
}),
}),
'speech_slots': dict({
'time': datetime.time(12, 0),
}),
}),
}),
dict({
'agent_id': 'conversation.openai_conversation',
'content': '12:00 PM',
'created': HAFakeDatetime(2025, 10, 31, 12, 0, tzinfo=datetime.timezone.utc),
'native': None,
'role': 'assistant',
'thinking_content': None,
'tool_calls': None,
}),
dict({
'attachments': None,
'content': 'Please call the test function',
@@ -125,6 +184,27 @@
# ---
# name: test_function_call.1
list([
dict({
'content': 'What time is it?',
'role': 'user',
'type': 'message',
}),
dict({
'arguments': '{}',
'call_id': 'mock-tool-call-id',
'name': 'HassGetCurrentTime',
'type': 'function_call',
}),
dict({
'call_id': 'mock-tool-call-id',
'output': '{"speech":{"plain":{"speech":"12:00 PM","extra_data":null}},"response_type":"action_done","speech_slots":{"time":"12:00:00"},"data":{"targets":[],"success":[],"failed":[]}}',
'type': 'function_call_output',
}),
dict({
'content': '12:00 PM',
'role': 'assistant',
'type': 'message',
}),
dict({
'content': 'Please call the test function',
'role': 'user',
@@ -146,7 +226,7 @@
'type': 'reasoning',
}),
dict({
'arguments': '{"param1": "call1"}',
'arguments': '{"param1":"call1"}',
'call_id': 'call_call_1',
'name': 'test_tool',
'type': 'function_call',
@@ -157,7 +237,7 @@
'type': 'function_call_output',
}),
dict({
'arguments': '{"param1": "call2"}',
'arguments': '{"param1":"call2"}',
'call_id': 'call_call_2',
'name': 'test_tool',
'type': 'function_call',

View File

@@ -1,5 +1,6 @@
"""Tests for the OpenAI integration."""
import datetime
from unittest.mock import AsyncMock, patch
from freezegun import freeze_time
@@ -30,6 +31,7 @@ from homeassistant.components.openai_conversation.const import (
from homeassistant.const import CONF_LLM_HASS_API
from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers import intent
from homeassistant.helpers.llm import ToolInput
from homeassistant.setup import async_setup_component
from . import (
@@ -251,6 +253,44 @@ async def test_function_call(
snapshot: SnapshotAssertion,
) -> None:
"""Test function call from the assistant."""
# 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="conversation.openai_conversation",
tool_calls=[
ToolInput(
tool_name="HassGetCurrentTime",
tool_args={},
id="mock-tool-call-id",
external=True,
)
],
)
)
mock_chat_log.async_add_assistant_content_without_tools(
conversation.ToolResultContent(
agent_id="conversation.openai_conversation",
tool_call_id="mock-tool-call-id",
tool_name="HassGetCurrentTime",
tool_result={
"speech": {"plain": {"speech": "12:00 PM", "extra_data": None}},
"response_type": "action_done",
"speech_slots": {"time": datetime.time(12, 0, 0, 0)},
"data": {"targets": [], "success": [], "failed": []},
},
)
)
mock_chat_log.async_add_assistant_content_without_tools(
conversation.AssistantContent(
agent_id="conversation.openai_conversation",
content="12:00 PM",
)
)
mock_create_stream.return_value = [
# Initial conversation
(