diff --git a/homeassistant/components/ai_task/__init__.py b/homeassistant/components/ai_task/__init__.py index 2021a92d6e2..06d10e4a35e 100644 --- a/homeassistant/components/ai_task/__init__.py +++ b/homeassistant/components/ai_task/__init__.py @@ -2,8 +2,17 @@ import logging +import voluptuous as vol + from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import ( + HassJobType, + HomeAssistant, + ServiceCall, + ServiceResponse, + SupportsResponse, + callback, +) from homeassistant.helpers import config_validation as cv, storage from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType @@ -36,6 +45,20 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[DATA_PREFERENCES] = AITaskPreferences(hass) await hass.data[DATA_PREFERENCES].async_load() async_setup_conversation_http(hass) + hass.services.async_register( + DOMAIN, + "generate_text", + async_service_generate_text, + schema=vol.Schema( + { + vol.Required("task_name"): cv.string, + vol.Optional("entity_id"): cv.entity_id, + vol.Required("instructions"): cv.string, + } + ), + supports_response=SupportsResponse.ONLY, + job_type=HassJobType.Coroutinefunction, + ) return True @@ -49,6 +72,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return await hass.data[DATA_COMPONENT].async_unload_entry(entry) +async def async_service_generate_text(call: ServiceCall) -> ServiceResponse: + """Run the run task service.""" + result = await async_generate_text(hass=call.hass, **call.data) + return result.as_dict() # type: ignore[return-value] + + class AITaskPreferences: """AI Task preferences.""" diff --git a/homeassistant/components/ai_task/icons.json b/homeassistant/components/ai_task/icons.json new file mode 100644 index 00000000000..cb09e5c8f5d --- /dev/null +++ b/homeassistant/components/ai_task/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "generate_text": { + "service": "mdi:file-star-four-points-outline" + } + } +} diff --git a/homeassistant/components/ai_task/services.yaml b/homeassistant/components/ai_task/services.yaml new file mode 100644 index 00000000000..fb193dd2bbb --- /dev/null +++ b/homeassistant/components/ai_task/services.yaml @@ -0,0 +1,17 @@ +generate_text: + fields: + task_name: + example: "home summary" + required: true + selector: + text: + instructions: + example: "Funny notification that garage door left open" + required: true + selector: + text: + entity_id: + required: false + selector: + entity: + domain: llm_task diff --git a/homeassistant/components/ai_task/strings.json b/homeassistant/components/ai_task/strings.json new file mode 100644 index 00000000000..1cdbf20ba4f --- /dev/null +++ b/homeassistant/components/ai_task/strings.json @@ -0,0 +1,22 @@ +{ + "services": { + "generate_text": { + "name": "Generate text", + "description": "Use AI to run a task that generates text.", + "fields": { + "task_name": { + "name": "Task Name", + "description": "Name of the task." + }, + "instructions": { + "name": "Instructions", + "description": "Instructions on what needs to be done." + }, + "entity_id": { + "name": "Entity ID", + "description": "Entity ID to run the task on. If not provided, the preferred entity will be used." + } + } + } + } +} diff --git a/tests/components/ai_task/test_init.py b/tests/components/ai_task/test_init.py index 2498ee2680a..0c059229115 100644 --- a/tests/components/ai_task/test_init.py +++ b/tests/components/ai_task/test_init.py @@ -1,11 +1,14 @@ """Test initialization of the AI Task component.""" from freezegun.api import FrozenDateTimeFactory +import pytest from homeassistant.components.ai_task import AITaskPreferences from homeassistant.components.ai_task.const import DATA_PREFERENCES from homeassistant.core import HomeAssistant +from .conftest import TEST_ENTITY_ID + from tests.common import flush_store @@ -51,3 +54,42 @@ async def test_preferences_storage_load( await another_new_preferences_instance.async_load() assert another_new_preferences_instance.gen_text_entity_id == gen_text_id_2 + + +@pytest.mark.parametrize( + ("set_preferences", "msg_extra"), + [ + ( + {"gen_text_entity_id": TEST_ENTITY_ID}, + {}, + ), + ( + {}, + {"entity_id": TEST_ENTITY_ID}, + ), + ], +) +async def test_generate_text_service( + hass: HomeAssistant, + init_components: None, + freezer: FrozenDateTimeFactory, + set_preferences: dict[str, str | None], + msg_extra: dict[str, str], +) -> None: + """Test the generate text service.""" + preferences = hass.data[DATA_PREFERENCES] + preferences.async_set_preferences(**set_preferences) + + result = await hass.services.async_call( + "ai_task", + "generate_text", + { + "task_name": "Test Name", + "instructions": "Test prompt", + } + | msg_extra, + blocking=True, + return_response=True, + ) + + assert result["result"] == "Mock result"