mirror of
https://github.com/home-assistant/core.git
synced 2025-07-11 15:27:08 +00:00
Add a GetHomeState tool to return the current state of the home (#140971)
* Add a GetHomeState tool to return the current state of the home * Fix check for exposing entities * Add "all" to get home state description
This commit is contained in:
parent
a388863e62
commit
a83bf4f514
@ -66,6 +66,11 @@ Answer questions about the world truthfully.
|
|||||||
Answer in plain text. Keep it simple and to the point.
|
Answer in plain text. Keep it simple and to the point.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
NO_ENTITIES_PROMPT = (
|
||||||
|
"Only if the user wants to control a device, tell them to expose entities "
|
||||||
|
"to their voice assistant in Home Assistant."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_render_no_api_prompt(hass: HomeAssistant) -> str:
|
def async_render_no_api_prompt(hass: HomeAssistant) -> str:
|
||||||
@ -329,10 +334,7 @@ class AssistAPI(API):
|
|||||||
self, llm_context: LLMContext, exposed_entities: dict | None
|
self, llm_context: LLMContext, exposed_entities: dict | None
|
||||||
) -> str:
|
) -> str:
|
||||||
if not exposed_entities or not exposed_entities["entities"]:
|
if not exposed_entities or not exposed_entities["entities"]:
|
||||||
return (
|
return NO_ENTITIES_PROMPT
|
||||||
"Only if the user wants to control a device, tell them to expose entities "
|
|
||||||
"to their voice assistant in Home Assistant."
|
|
||||||
)
|
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
[
|
[
|
||||||
*self._async_get_preable(llm_context),
|
*self._async_get_preable(llm_context),
|
||||||
@ -454,6 +456,9 @@ class AssistAPI(API):
|
|||||||
for script_entity_id in exposed_entities[SCRIPT_DOMAIN]
|
for script_entity_id in exposed_entities[SCRIPT_DOMAIN]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if exposed_domains:
|
||||||
|
tools.append(GetHomeStateTool())
|
||||||
|
|
||||||
return tools
|
return tools
|
||||||
|
|
||||||
|
|
||||||
@ -885,3 +890,39 @@ class CalendarGetEventsTool(Tool):
|
|||||||
]
|
]
|
||||||
|
|
||||||
return {"success": True, "result": events}
|
return {"success": True, "result": events}
|
||||||
|
|
||||||
|
|
||||||
|
class GetHomeStateTool(Tool):
|
||||||
|
"""Tool for getting the current state of exposed entities.
|
||||||
|
|
||||||
|
This returns state for all entities that have been exposed to
|
||||||
|
the assistant. This is different than the GetState intent, which
|
||||||
|
returns state for entities based on intent parameters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "get_home_state"
|
||||||
|
description = "Get the current state of all devices in the home. "
|
||||||
|
|
||||||
|
async def async_call(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
tool_input: ToolInput,
|
||||||
|
llm_context: LLMContext,
|
||||||
|
) -> JsonObjectType:
|
||||||
|
"""Get the current state of exposed entities."""
|
||||||
|
if llm_context.assistant is None:
|
||||||
|
# Note this doesn't happen in practice since this tool won't be
|
||||||
|
# exposed if no assistant is configured.
|
||||||
|
return {"success": False, "error": "No assistant configured"}
|
||||||
|
|
||||||
|
exposed_entities = _get_exposed_entities(hass, llm_context.assistant)
|
||||||
|
if not exposed_entities["entities"]:
|
||||||
|
return {"success": False, "error": NO_ENTITIES_PROMPT}
|
||||||
|
prompt = [
|
||||||
|
"An overview of the areas and the devices in this smart home:",
|
||||||
|
yaml_util.dump(list(exposed_entities["entities"].values())),
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"result": "\n".join(prompt),
|
||||||
|
}
|
||||||
|
@ -181,19 +181,19 @@ async def test_assist_api(
|
|||||||
|
|
||||||
assert len(llm.async_get_apis(hass)) == 1
|
assert len(llm.async_get_apis(hass)) == 1
|
||||||
api = await llm.async_get_api(hass, "assist", llm_context)
|
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||||
assert len(api.tools) == 0
|
assert [tool.name for tool in api.tools] == ["get_home_state"]
|
||||||
|
|
||||||
# Match all
|
# Match all
|
||||||
intent_handler.platforms = None
|
intent_handler.platforms = None
|
||||||
|
|
||||||
api = await llm.async_get_api(hass, "assist", llm_context)
|
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||||
assert len(api.tools) == 1
|
assert [tool.name for tool in api.tools] == ["test_intent", "get_home_state"]
|
||||||
|
|
||||||
# Match specific domain
|
# Match specific domain
|
||||||
intent_handler.platforms = {"light"}
|
intent_handler.platforms = {"light"}
|
||||||
|
|
||||||
api = await llm.async_get_api(hass, "assist", llm_context)
|
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||||
assert len(api.tools) == 1
|
assert len(api.tools) == 2
|
||||||
tool = api.tools[0]
|
tool = api.tools[0]
|
||||||
assert tool.name == "test_intent"
|
assert tool.name == "test_intent"
|
||||||
assert tool.description == "Execute Home Assistant test_intent intent"
|
assert tool.description == "Execute Home Assistant test_intent intent"
|
||||||
@ -643,6 +643,15 @@ async def test_assist_api_prompt(
|
|||||||
{exposed_entities_prompt}"""
|
{exposed_entities_prompt}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Verify that the get_home_state tool returns the same results as the exposed_entities_prompt
|
||||||
|
result = await api.async_call_tool(
|
||||||
|
llm.ToolInput(tool_name="get_home_state", tool_args={})
|
||||||
|
)
|
||||||
|
assert result == {
|
||||||
|
"success": True,
|
||||||
|
"result": exposed_entities_prompt,
|
||||||
|
}
|
||||||
|
|
||||||
# Fake that request is made from a specific device ID with an area
|
# Fake that request is made from a specific device ID with an area
|
||||||
llm_context.device_id = device.id
|
llm_context.device_id = device.id
|
||||||
area_prompt = (
|
area_prompt = (
|
||||||
@ -1267,3 +1276,19 @@ async def test_calendar_get_events_tool(hass: HomeAssistant) -> None:
|
|||||||
"start_date_time": now,
|
"start_date_time": now,
|
||||||
"end_date_time": dt_util.start_of_local_day() + timedelta(days=7),
|
"end_date_time": dt_util.start_of_local_day() + timedelta(days=7),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_tools_exposed(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that tools are not exposed when no entities are exposed."""
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
|
context = Context()
|
||||||
|
llm_context = llm.LLMContext(
|
||||||
|
platform="test_platform",
|
||||||
|
context=context,
|
||||||
|
user_prompt="test_text",
|
||||||
|
language="*",
|
||||||
|
assistant="conversation",
|
||||||
|
device_id=None,
|
||||||
|
)
|
||||||
|
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||||
|
assert api.tools == []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user