mirror of
https://github.com/home-assistant/core.git
synced 2026-09-21 17:23:23 +01:00
Co-authored-by: New Future <6290356+NewFuture@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Erwin Douna <e.douna@gmail.com> Co-authored-by: Michael Hansen <mike@rhasspy.org> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
109 lines
3.6 KiB
Python
109 lines
3.6 KiB
Python
"""Tests for the climate LLM tools platform."""
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components import llm as llm_component
|
|
from homeassistant.components.climate import (
|
|
ATTR_TEMPERATURE,
|
|
DOMAIN,
|
|
SERVICE_SET_TEMPERATURE,
|
|
ClimateEntityFeature,
|
|
llm as climate_llm,
|
|
)
|
|
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
|
|
from homeassistant.core import Context, HomeAssistant
|
|
from homeassistant.helpers import llm
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from tests.common import async_mock_service
|
|
|
|
ENTITY_ID = "climate.test"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def setup_integrations(hass: HomeAssistant) -> None:
|
|
"""Set up the integrations and expose a climate entity."""
|
|
assert await async_setup_component(hass, "homeassistant", {})
|
|
assert await async_setup_component(hass, "intent", {})
|
|
assert await async_setup_component(hass, "climate", {})
|
|
assert await async_setup_component(hass, "llm", {})
|
|
hass.states.async_set(
|
|
ENTITY_ID,
|
|
"on",
|
|
{
|
|
"friendly_name": "Test climate",
|
|
"supported_features": ClimateEntityFeature.TARGET_TEMPERATURE,
|
|
},
|
|
)
|
|
async_expose_entity(hass, "conversation", ENTITY_ID, True)
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
def _llm_context() -> llm.LLMContext:
|
|
"""Return an LLM context for the conversation assistant."""
|
|
return llm.LLMContext(
|
|
platform="test_platform",
|
|
context=Context(),
|
|
language="*",
|
|
assistant="conversation",
|
|
device_id=None,
|
|
)
|
|
|
|
|
|
async def _tool_names(hass: HomeAssistant) -> set[str]:
|
|
"""Return the names of the tools offered by the climate platform."""
|
|
result = await llm_component.async_get_tools(hass, _llm_context(), "assist")
|
|
return {tool.name for tool in result.tools}
|
|
|
|
|
|
async def test_intent_tool_exposed(hass: HomeAssistant) -> None:
|
|
"""Test the intent tool is offered for an exposed climate entity."""
|
|
assert "climate__HassClimateSetTemperature" in await _tool_names(hass)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"target_args",
|
|
[
|
|
pytest.param(
|
|
{"area": "", "floor": "", "name": "Test climate"},
|
|
id="named-target",
|
|
),
|
|
pytest.param(
|
|
{"area": "", "floor": " ", "name": None},
|
|
id="implicit-single-target",
|
|
),
|
|
],
|
|
)
|
|
async def test_set_temperature_omits_empty_optional_targets(
|
|
hass: HomeAssistant, target_args: dict[str, str | None]
|
|
) -> None:
|
|
"""Test empty optional targets do not invalidate a climate LLM tool call."""
|
|
api = await llm.async_get_api(hass, "assist", _llm_context())
|
|
calls = async_mock_service(hass, DOMAIN, SERVICE_SET_TEMPERATURE)
|
|
|
|
response = await api.async_call_tool(
|
|
llm.ToolInput(
|
|
"climate__HassClimateSetTemperature",
|
|
{**target_args, "temperature": 25},
|
|
)
|
|
)
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data == {
|
|
"entity_id": ENTITY_ID,
|
|
ATTR_TEMPERATURE: 25,
|
|
}
|
|
assert response["response_type"] == "action_done"
|
|
|
|
|
|
async def test_intent_tool_not_exposed(hass: HomeAssistant) -> None:
|
|
"""Test the intent tool is hidden when no climate entity is exposed."""
|
|
async_expose_entity(hass, "conversation", ENTITY_ID, False)
|
|
assert "climate__HassClimateSetTemperature" not in await _tool_names(hass)
|
|
assert climate_llm.async_get_tools(hass, _llm_context(), "assist") is None
|
|
|
|
|
|
async def test_no_tools_for_other_api(hass: HomeAssistant) -> None:
|
|
"""Test the platform returns None for an unsupported API."""
|
|
assert climate_llm.async_get_tools(hass, _llm_context(), "other") is None
|