2026-07-03 20:06:10 +02:00
|
|
|
"""Tests for the intent_script LLM tools platform."""
|
|
|
|
|
|
2026-07-04 02:39:51 +02:00
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
2026-07-03 20:06:10 +02:00
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from homeassistant.components import llm as llm_component
|
|
|
|
|
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
|
2026-07-04 02:39:51 +02:00
|
|
|
from homeassistant.components.intent_script import (
|
|
|
|
|
ScriptIntentHandler,
|
|
|
|
|
llm as intent_script_llm,
|
|
|
|
|
)
|
2026-07-03 20:06:10 +02:00
|
|
|
from homeassistant.core import Context, HomeAssistant
|
2026-07-04 02:39:51 +02:00
|
|
|
from homeassistant.helpers import intent, llm
|
2026-07-03 20:06:10 +02:00
|
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
|
|
|
|
|
|
LIGHT_ENTITY_ID = "light.kitchen"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
|
async def setup_integrations(hass: HomeAssistant) -> None:
|
|
|
|
|
"""Set up the integrations and configure intent scripts."""
|
|
|
|
|
assert await async_setup_component(hass, "homeassistant", {})
|
|
|
|
|
assert await async_setup_component(hass, "intent", {})
|
|
|
|
|
assert await async_setup_component(
|
|
|
|
|
hass,
|
|
|
|
|
"intent_script",
|
|
|
|
|
{
|
|
|
|
|
"intent_script": {
|
|
|
|
|
"Tell a joke": {
|
|
|
|
|
"description": "Tell a joke",
|
|
|
|
|
"speech": {"text": "Why did the chicken cross the road?"},
|
|
|
|
|
},
|
|
|
|
|
"LightAction": {
|
|
|
|
|
"description": "Do a light thing",
|
|
|
|
|
"platforms": ["light"],
|
|
|
|
|
"speech": {"text": "Done"},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
assert await async_setup_component(hass, "llm", {})
|
|
|
|
|
hass.states.async_set(LIGHT_ENTITY_ID, "on", {"friendly_name": "Kitchen Light"})
|
|
|
|
|
async_expose_entity(hass, "conversation", LIGHT_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 intent_script platform."""
|
2026-07-04 02:39:51 +02:00
|
|
|
result = await llm_component.async_get_tools(hass, _llm_context(), "assist")
|
2026-07-03 20:06:10 +02:00
|
|
|
return {tool.name for tool in result.tools}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_intent_scripts_exposed(hass: HomeAssistant) -> None:
|
|
|
|
|
"""Test intent scripts are exposed as LLM tools with slugified names."""
|
|
|
|
|
names = await _tool_names(hass)
|
|
|
|
|
# The user-provided "Tell a joke" name is slugified into a valid tool name.
|
|
|
|
|
assert "Tell_a_joke" in names
|
|
|
|
|
assert "LightAction" in names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_intent_script_platform_filtered(hass: HomeAssistant) -> None:
|
|
|
|
|
"""Test a platform-restricted intent script requires an exposed entity."""
|
|
|
|
|
async_expose_entity(hass, "conversation", LIGHT_ENTITY_ID, False)
|
|
|
|
|
names = await _tool_names(hass)
|
|
|
|
|
assert "LightAction" not in names
|
|
|
|
|
# Unrestricted intent scripts stay exposed.
|
|
|
|
|
assert "Tell_a_joke" in names
|
2026-07-04 02:39:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_no_tools_for_other_api(hass: HomeAssistant) -> None:
|
|
|
|
|
"""Test the platform returns None for an unsupported API."""
|
|
|
|
|
assert intent_script_llm.async_get_tools(hass, _llm_context(), "other") is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_no_tools_when_no_scripts_match(hass: HomeAssistant) -> None:
|
|
|
|
|
"""Test None is returned when no intent scripts match the exposed domains."""
|
|
|
|
|
async_expose_entity(hass, "conversation", LIGHT_ENTITY_ID, False)
|
|
|
|
|
restricted_handlers = [
|
|
|
|
|
handler
|
|
|
|
|
for handler in intent.async_get(hass)
|
|
|
|
|
if isinstance(handler, ScriptIntentHandler) and handler.platforms
|
|
|
|
|
]
|
|
|
|
|
with patch(
|
|
|
|
|
"homeassistant.components.intent_script.llm.intent.async_get",
|
|
|
|
|
return_value=restricted_handlers,
|
|
|
|
|
):
|
|
|
|
|
assert intent_script_llm.async_get_tools(hass, _llm_context(), "assist") is None
|