Files
ha-openai-compatible/tests/test_entity.py
T
pc-heiniandClaude Opus 5.5 84f4bcda55
validate / hassfest (push) Failing after 11s
validate / hacs (push) Failing after 23s
test / pytest (push) Successful in 46s
fix: make the home location lookup work against Claude
- Send the location question as a user message. A request with only a
  system message has no user content, which endpoints in front of Anthropic
  models reject ("text content blocks must be non-empty").
- Leave unknown fields out of the web search user_location. After a failed
  lookup there is no city or region, and the empty strings made every
  request fail ("user_location.city: String should have at least 1
  character").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:52:14 +02:00

138 lines
4.4 KiB
Python

"""Tests for the fork's chat log conversion fixes in entity.py."""
from unittest.mock import MagicMock
from homeassistant.components import conversation
from homeassistant.core import Context
from homeassistant.helpers import llm
from custom_components.openai_compatible.const import (
CONF_WEB_SEARCH,
CONF_WEB_SEARCH_COUNTRY,
CONF_WEB_SEARCH_TIMEZONE,
CONF_WEB_SEARCH_USER_LOCATION,
)
from custom_components.openai_compatible.entity import (
_convert_content_to_param,
_transform_stream,
)
from . import create_function_tool_call_item, create_message_item
async def _stream(events):
for event in events:
yield event
async def test_tool_call_without_arguments() -> None:
"""Test an empty argument string means a call without parameters.
Some OpenAI-compatible endpoints send "" instead of "{}" for tools that
take no parameters, e.g. the timer status intent.
"""
events = create_function_tool_call_item(
id="fc_1",
arguments=[],
call_id="call_1",
name="HassTimerStatus",
output_index=0,
)
deltas = [delta async for delta in _transform_stream(MagicMock(), _stream(events))]
tool_calls = [call for delta in deltas for call in delta.get("tool_calls", [])]
assert len(tool_calls) == 1
assert tool_calls[0].tool_name == "HassTimerStatus"
assert tool_calls[0].tool_args == {}
def test_web_search_not_replayed() -> None:
"""Test earlier web searches are left out of the history.
The chat log keeps only a search's status, not its results. Replaying the
bare search makes the model disown the answer it based on that search.
"""
content = [
conversation.UserContent(content="How did the match go?"),
conversation.AssistantContent(
agent_id="conversation.test",
tool_calls=[
llm.ToolInput(
id="ws_1",
tool_name="web_search_call",
tool_args={"action": {"type": "search", "query": "match"}},
external=True,
)
],
),
conversation.ToolResultContent(
agent_id="conversation.test",
tool_call_id="ws_1",
tool_name="web_search_call",
result=llm.ToolResult(data={"status": "completed"}),
),
conversation.AssistantContent(
agent_id="conversation.test",
content="It ended 2 to 2.",
tool_calls=[
llm.ToolInput(id="call_1", tool_name="GetLiveContext", tool_args={})
],
),
conversation.ToolResultContent(
agent_id="conversation.test",
tool_call_id="call_1",
tool_name="GetLiveContext",
result=llm.ToolResult(data={"success": True}),
),
]
messages = _convert_content_to_param(content)
assert [message["type"] for message in messages] == [
"message",
"message",
"function_call",
"function_call_output",
]
assert messages[1]["content"] == "It ended 2 to 2."
assert messages[2]["call_id"] == "call_1"
async def test_web_search_location_leaves_out_empty_fields(
hass, mock_config_entry, mock_init_component, mock_create_stream
) -> None:
"""Test unknown location fields are left out of the web search tool.
A failed location lookup stores no city or region; endpoints in front of
Anthropic models reject empty strings there.
"""
subentry = next(iter(mock_config_entry.subentries.values()))
hass.config_entries.async_update_subentry(
mock_config_entry,
subentry,
data={
**subentry.data,
CONF_WEB_SEARCH: True,
CONF_WEB_SEARCH_USER_LOCATION: True,
CONF_WEB_SEARCH_COUNTRY: "DE",
CONF_WEB_SEARCH_TIMEZONE: "Europe/Berlin",
},
)
await hass.config_entries.async_reload(mock_config_entry.entry_id)
mock_create_stream.return_value = [
create_message_item(id="msg_A", text="Hi", output_index=0)
]
await conversation.async_converse(
hass, "hello", None, Context(), agent_id="conversation.openai_conversation"
)
tools = mock_create_stream.mock_calls[0][2]["tools"]
web_search = next(tool for tool in tools if tool["type"] == "web_search")
assert web_search["user_location"] == {
"type": "approximate",
"country": "DE",
"timezone": "Europe/Berlin",
}