mirror of
https://github.com/home-assistant/core.git
synced 2026-09-27 01:46:11 -04:00
Set tool metadata in todo (#182711)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
c08496e105
commit
ad076d23a0
@@ -14,6 +14,7 @@ from homeassistant.helpers.llm import (
|
||||
IntentTool,
|
||||
LLMContext,
|
||||
Tool,
|
||||
ToolAnnotations,
|
||||
ToolInput,
|
||||
ToolResult,
|
||||
)
|
||||
@@ -26,13 +27,27 @@ from .intent import (
|
||||
)
|
||||
|
||||
# Intents owned by this integration that are exposed as LLM tools.
|
||||
LLM_INTENTS = (INTENT_LIST_ADD_ITEM, INTENT_LIST_COMPLETE_ITEM, INTENT_LIST_REMOVE_ITEM)
|
||||
LLM_INTENTS = {
|
||||
INTENT_LIST_ADD_ITEM: "Add to-do list item",
|
||||
INTENT_LIST_COMPLETE_ITEM: "Complete to-do list item",
|
||||
INTENT_LIST_REMOVE_ITEM: "Remove to-do list item",
|
||||
}
|
||||
|
||||
# Adding an item appends to the list and takes nothing away. Completing and
|
||||
# removing both look for an item that is still there, so a repeated call
|
||||
# raises instead of having no further effect.
|
||||
INTENT_ANNOTATIONS = {
|
||||
INTENT_LIST_ADD_ITEM: ToolAnnotations(destructive=False, open_world=False),
|
||||
INTENT_LIST_COMPLETE_ITEM: ToolAnnotations(open_world=False),
|
||||
INTENT_LIST_REMOVE_ITEM: ToolAnnotations(open_world=False),
|
||||
}
|
||||
|
||||
|
||||
class TodoGetItemsTool(Tool):
|
||||
"""LLM Tool allowing querying a to-do list."""
|
||||
|
||||
name = "todo__get_items"
|
||||
title = "Get to-do list items"
|
||||
description = (
|
||||
"Query a to-do list to find out what items are on it. "
|
||||
"Use this to answer questions like "
|
||||
@@ -40,6 +55,10 @@ class TodoGetItemsTool(Tool):
|
||||
"'Read my grocery list'. "
|
||||
"Filters items by status (needs_action, completed, all)."
|
||||
)
|
||||
annotations = ToolAnnotations(
|
||||
read_only=True, destructive=False, idempotent=True, open_world=False
|
||||
)
|
||||
integration = DOMAIN
|
||||
|
||||
def __init__(self, todo_lists: list[str]) -> None:
|
||||
"""Init the get items tool."""
|
||||
@@ -115,7 +134,13 @@ def async_get_tools(
|
||||
|
||||
tools: list[Tool] = [TodoGetItemsTool(names)]
|
||||
tools.extend(
|
||||
IntentTool(f"{DOMAIN}__{handler.intent_type}", handler)
|
||||
IntentTool(
|
||||
f"{DOMAIN}__{handler.intent_type}",
|
||||
handler,
|
||||
title=LLM_INTENTS[handler.intent_type],
|
||||
integration=DOMAIN,
|
||||
annotations=INTENT_ANNOTATIONS[handler.intent_type],
|
||||
)
|
||||
for handler in intent.async_get(hass)
|
||||
if handler.intent_type in LLM_INTENTS
|
||||
)
|
||||
|
||||
@@ -57,6 +57,11 @@ async def test_todo_get_items_tool(hass: HomeAssistant) -> None:
|
||||
tool = next((tool for tool in result.tools if tool.name == "todo__get_items"), None)
|
||||
assert tool is not None
|
||||
assert tool.parameters.schema["todo_list"].container == ["Mock Todo List Name"]
|
||||
assert tool.title == "Get to-do list items"
|
||||
assert tool.integration == todo.DOMAIN
|
||||
assert tool.annotations == llm.ToolAnnotations(
|
||||
read_only=True, destructive=False, idempotent=True, open_world=False
|
||||
)
|
||||
|
||||
calls = async_mock_service(
|
||||
hass,
|
||||
@@ -124,7 +129,31 @@ async def test_todo_get_items_status_filter(
|
||||
async def test_todo_list_intents_exposed(hass: HomeAssistant) -> None:
|
||||
"""Test the todo list intents are exposed as tools when a list is exposed."""
|
||||
result = await llm_component.async_get_tools(hass, _llm_context(), "assist")
|
||||
names = {tool.name for tool in result.tools}
|
||||
assert "todo__HassListAddItem" in names
|
||||
assert "todo__HassListCompleteItem" in names
|
||||
assert "todo__HassListRemoveItem" in names
|
||||
tools = {tool.name: tool for tool in result.tools}
|
||||
assert "todo__HassListAddItem" in tools
|
||||
assert "todo__HassListCompleteItem" in tools
|
||||
assert "todo__HassListRemoveItem" in tools
|
||||
|
||||
# Completing or removing an item raises on a repeat, so neither is
|
||||
# idempotent. Adding an item takes nothing away.
|
||||
assert {
|
||||
name: (tool.title, tool.integration, tool.annotations)
|
||||
for name, tool in tools.items()
|
||||
if name.startswith("todo__HassList")
|
||||
} == {
|
||||
"todo__HassListAddItem": (
|
||||
"Add to-do list item",
|
||||
todo.DOMAIN,
|
||||
llm.ToolAnnotations(destructive=False, open_world=False),
|
||||
),
|
||||
"todo__HassListCompleteItem": (
|
||||
"Complete to-do list item",
|
||||
todo.DOMAIN,
|
||||
llm.ToolAnnotations(open_world=False),
|
||||
),
|
||||
"todo__HassListRemoveItem": (
|
||||
"Remove to-do list item",
|
||||
todo.DOMAIN,
|
||||
llm.ToolAnnotations(open_world=False),
|
||||
),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user