2024-05-15 23:16:47 +00:00
|
|
|
"""Tests for the llm helpers."""
|
|
|
|
|
2024-05-25 02:23:05 +00:00
|
|
|
from unittest.mock import Mock, patch
|
2024-05-15 23:16:47 +00:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
import voluptuous as vol
|
|
|
|
|
|
|
|
from homeassistant.core import Context, HomeAssistant, State
|
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2024-05-25 02:23:05 +00:00
|
|
|
from homeassistant.helpers import (
|
|
|
|
area_registry as ar,
|
|
|
|
config_validation as cv,
|
|
|
|
device_registry as dr,
|
|
|
|
floor_registry as fr,
|
|
|
|
intent,
|
|
|
|
llm,
|
|
|
|
)
|
|
|
|
|
|
|
|
from tests.common import MockConfigEntry
|
2024-05-15 23:16:47 +00:00
|
|
|
|
|
|
|
|
2024-05-19 01:14:05 +00:00
|
|
|
async def test_get_api_no_existing(hass: HomeAssistant) -> None:
|
|
|
|
"""Test getting an llm api where no config exists."""
|
|
|
|
with pytest.raises(HomeAssistantError):
|
|
|
|
llm.async_get_api(hass, "non-existing")
|
|
|
|
|
|
|
|
|
|
|
|
async def test_register_api(hass: HomeAssistant) -> None:
|
|
|
|
"""Test registering an llm api."""
|
2024-05-20 02:11:25 +00:00
|
|
|
|
|
|
|
class MyAPI(llm.API):
|
2024-05-24 20:04:48 +00:00
|
|
|
async def async_get_api_prompt(self, tool_input: llm.ToolInput) -> str:
|
|
|
|
"""Return a prompt for the tool."""
|
|
|
|
return ""
|
|
|
|
|
2024-05-20 02:11:25 +00:00
|
|
|
def async_get_tools(self) -> list[llm.Tool]:
|
|
|
|
"""Return a list of tools."""
|
|
|
|
return []
|
|
|
|
|
2024-05-24 20:04:48 +00:00
|
|
|
api = MyAPI(hass=hass, id="test", name="Test")
|
2024-05-19 01:14:05 +00:00
|
|
|
llm.async_register_api(hass, api)
|
|
|
|
|
|
|
|
assert llm.async_get_api(hass, "test") is api
|
|
|
|
assert api in llm.async_get_apis(hass)
|
|
|
|
|
|
|
|
with pytest.raises(HomeAssistantError):
|
|
|
|
llm.async_register_api(hass, api)
|
|
|
|
|
|
|
|
|
2024-05-15 23:16:47 +00:00
|
|
|
async def test_call_tool_no_existing(hass: HomeAssistant) -> None:
|
|
|
|
"""Test calling an llm tool where no config exists."""
|
|
|
|
with pytest.raises(HomeAssistantError):
|
2024-05-19 01:14:05 +00:00
|
|
|
await llm.async_get_api(hass, "intent").async_call_tool(
|
2024-05-15 23:16:47 +00:00
|
|
|
llm.ToolInput(
|
|
|
|
"test_tool",
|
|
|
|
{},
|
|
|
|
"test_platform",
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
2024-05-22 01:24:46 +00:00
|
|
|
None,
|
2024-05-15 23:16:47 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-05-19 01:14:05 +00:00
|
|
|
async def test_assist_api(hass: HomeAssistant) -> None:
|
|
|
|
"""Test Assist API."""
|
2024-05-15 23:16:47 +00:00
|
|
|
schema = {
|
|
|
|
vol.Optional("area"): cv.string,
|
|
|
|
vol.Optional("floor"): cv.string,
|
|
|
|
}
|
|
|
|
|
|
|
|
class MyIntentHandler(intent.IntentHandler):
|
|
|
|
intent_type = "test_intent"
|
|
|
|
slot_schema = schema
|
|
|
|
|
|
|
|
intent_handler = MyIntentHandler()
|
|
|
|
|
|
|
|
intent.async_register(hass, intent_handler)
|
|
|
|
|
2024-05-19 01:14:05 +00:00
|
|
|
assert len(llm.async_get_apis(hass)) == 1
|
|
|
|
api = llm.async_get_api(hass, "assist")
|
|
|
|
tools = api.async_get_tools()
|
|
|
|
assert len(tools) == 1
|
|
|
|
tool = tools[0]
|
2024-05-15 23:16:47 +00:00
|
|
|
assert tool.name == "test_intent"
|
|
|
|
assert tool.description == "Execute Home Assistant test_intent intent"
|
|
|
|
assert tool.parameters == vol.Schema(intent_handler.slot_schema)
|
|
|
|
assert str(tool) == "<IntentTool - test_intent>"
|
|
|
|
|
|
|
|
test_context = Context()
|
|
|
|
intent_response = intent.IntentResponse("*")
|
|
|
|
intent_response.matched_states = [State("light.matched", "on")]
|
|
|
|
intent_response.unmatched_states = [State("light.unmatched", "on")]
|
|
|
|
tool_input = llm.ToolInput(
|
|
|
|
tool_name="test_intent",
|
|
|
|
tool_args={"area": "kitchen", "floor": "ground_floor"},
|
|
|
|
platform="test_platform",
|
|
|
|
context=test_context,
|
|
|
|
user_prompt="test_text",
|
|
|
|
language="*",
|
|
|
|
assistant="test_assistant",
|
2024-05-22 01:24:46 +00:00
|
|
|
device_id="test_device",
|
2024-05-15 23:16:47 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.helpers.intent.async_handle", return_value=intent_response
|
|
|
|
) as mock_intent_handle:
|
2024-05-19 01:14:05 +00:00
|
|
|
response = await api.async_call_tool(tool_input)
|
2024-05-15 23:16:47 +00:00
|
|
|
|
|
|
|
mock_intent_handle.assert_awaited_once_with(
|
|
|
|
hass,
|
|
|
|
"test_platform",
|
|
|
|
"test_intent",
|
|
|
|
{
|
|
|
|
"area": {"value": "kitchen"},
|
|
|
|
"floor": {"value": "ground_floor"},
|
|
|
|
},
|
|
|
|
"test_text",
|
|
|
|
test_context,
|
|
|
|
"*",
|
|
|
|
"test_assistant",
|
2024-05-22 01:24:46 +00:00
|
|
|
"test_device",
|
2024-05-15 23:16:47 +00:00
|
|
|
)
|
|
|
|
assert response == {
|
|
|
|
"card": {},
|
|
|
|
"data": {
|
|
|
|
"failed": [],
|
|
|
|
"success": [],
|
|
|
|
"targets": [],
|
|
|
|
},
|
|
|
|
"language": "*",
|
|
|
|
"response_type": "action_done",
|
|
|
|
"speech": {},
|
|
|
|
}
|
2024-05-21 16:54:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_assist_api_description(hass: HomeAssistant) -> None:
|
|
|
|
"""Test intent description with Assist API."""
|
|
|
|
|
|
|
|
class MyIntentHandler(intent.IntentHandler):
|
|
|
|
intent_type = "test_intent"
|
|
|
|
description = "my intent handler"
|
|
|
|
|
|
|
|
intent.async_register(hass, MyIntentHandler())
|
|
|
|
|
|
|
|
assert len(llm.async_get_apis(hass)) == 1
|
|
|
|
api = llm.async_get_api(hass, "assist")
|
|
|
|
tools = api.async_get_tools()
|
|
|
|
assert len(tools) == 1
|
|
|
|
tool = tools[0]
|
|
|
|
assert tool.name == "test_intent"
|
|
|
|
assert tool.description == "my intent handler"
|
2024-05-25 02:23:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_assist_api_prompt(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
device_registry: dr.DeviceRegistry,
|
|
|
|
area_registry: ar.AreaRegistry,
|
|
|
|
floor_registry: fr.FloorRegistry,
|
|
|
|
) -> None:
|
|
|
|
"""Test prompt for the assist API."""
|
|
|
|
context = Context()
|
|
|
|
tool_input = llm.ToolInput(
|
|
|
|
tool_name=None,
|
|
|
|
tool_args=None,
|
|
|
|
platform="test_platform",
|
|
|
|
context=context,
|
|
|
|
user_prompt="test_text",
|
|
|
|
language="*",
|
|
|
|
assistant="test_assistant",
|
|
|
|
device_id="test_device",
|
|
|
|
)
|
|
|
|
api = llm.async_get_api(hass, "assist")
|
|
|
|
prompt = await api.async_get_api_prompt(tool_input)
|
2024-05-25 18:24:51 +00:00
|
|
|
assert prompt == (
|
|
|
|
"Call the intent tools to control Home Assistant."
|
|
|
|
" Just pass the name to the intent."
|
2024-05-25 02:23:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(title=None)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
tool_input.device_id = device_registry.async_get_or_create(
|
|
|
|
config_entry_id=entry.entry_id,
|
|
|
|
connections={("test", "1234")},
|
|
|
|
name="Test Device",
|
|
|
|
manufacturer="Test Manufacturer",
|
|
|
|
model="Test Model",
|
|
|
|
suggested_area="Test Area",
|
|
|
|
).id
|
|
|
|
prompt = await api.async_get_api_prompt(tool_input)
|
2024-05-25 18:24:51 +00:00
|
|
|
assert prompt == (
|
|
|
|
"Call the intent tools to control Home Assistant."
|
|
|
|
" Just pass the name to the intent. You are in Test Area."
|
2024-05-25 02:23:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
floor = floor_registry.async_create("second floor")
|
|
|
|
area = area_registry.async_get_area_by_name("Test Area")
|
|
|
|
area_registry.async_update(area.id, floor_id=floor.floor_id)
|
|
|
|
prompt = await api.async_get_api_prompt(tool_input)
|
2024-05-25 18:24:51 +00:00
|
|
|
assert prompt == (
|
|
|
|
"Call the intent tools to control Home Assistant."
|
|
|
|
" Just pass the name to the intent. You are in Test Area (second floor)."
|
2024-05-25 02:23:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
context.user_id = "12345"
|
|
|
|
mock_user = Mock()
|
|
|
|
mock_user.id = "12345"
|
|
|
|
mock_user.name = "Test User"
|
|
|
|
with patch("homeassistant.auth.AuthManager.async_get_user", return_value=mock_user):
|
|
|
|
prompt = await api.async_get_api_prompt(tool_input)
|
2024-05-25 18:24:51 +00:00
|
|
|
assert prompt == (
|
|
|
|
"Call the intent tools to control Home Assistant."
|
|
|
|
" Just pass the name to the intent. You are in Test Area (second floor)."
|
|
|
|
" The user name is Test User."
|
2024-05-25 02:23:05 +00:00
|
|
|
)
|