Skip to content

Commit 21755c5

Browse files
committed
Sync with core code
1 parent e27bac7 commit 21755c5

File tree

9 files changed

+173
-63
lines changed

9 files changed

+173
-63
lines changed

custom_components/powerllm/api.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from homeassistant.components.intent import async_device_supports_timers
1212
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
1313
from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN
14+
from homeassistant.components.todo import DOMAIN as TODO_DOMAIN
1415
from homeassistant.components.weather import INTENT_GET_WEATHER
1516
from homeassistant.config_entries import ConfigEntry
1617
from homeassistant.const import CONF_DEFAULT, CONF_NAME
@@ -34,9 +35,11 @@
3435
)
3536
from .llm_tools import (
3637
PowerCalendarGetEventsTool,
38+
PowerGetLiveContextTool,
3739
PowerIntentTool,
3840
PowerLLMTool,
3941
PowerScriptTool,
42+
PowerTodoGetItemsTool,
4043
)
4144
from .tools.duckduckgo import DDGNewsTool, DDGTextSearchTool
4245
from .tools.memory import MemoryTool
@@ -78,7 +81,7 @@ async def async_get_api_instance(
7881
"""Return the instance of the API."""
7982
if llm_context.assistant:
8083
exposed_entities: dict | None = llm._get_exposed_entities(
81-
self.hass, llm_context.assistant
84+
self.hass, llm_context.assistant, include_state=False
8285
)
8386
else:
8487
exposed_entities = None
@@ -103,10 +106,7 @@ def _async_get_api_prompt(
103106
) -> str:
104107
"""Return the prompt for the API."""
105108
if not exposed_entities or not exposed_entities["entities"]:
106-
return (
107-
"Only if the user wants to control a device, tell them to expose "
108-
"entities to their voice assistant in Home Assistant."
109-
)
109+
return llm.NO_ENTITIES_PROMPT
110110
return "\n".join(
111111
[
112112
*self._async_get_preable(llm_context),
@@ -160,6 +160,9 @@ def _async_get_preable(self, llm_context: llm.LLMContext) -> list[str]:
160160
):
161161
prompt.append("This device is not able to start timers.")
162162

163+
if self.config_entry.options[CONF_PROMPT_ENTITIES]:
164+
prompt.append(llm.DYNAMIC_CONTEXT_PROMPT)
165+
163166
return prompt
164167

165168
@callback
@@ -183,7 +186,8 @@ def _async_get_exposed_entities_prompt(
183186
)
184187

185188
prompt.append(
186-
"An overview of the areas and the devices in this smart home:"
189+
"Static Context: An overview of the areas and the devices in this "
190+
"smart home:"
187191
)
188192
prompt.append(yaml.dump(exposed_entities["entities"]))
189193

@@ -259,11 +263,22 @@ def _async_get_tools(
259263
names.extend(info["names"].split(", "))
260264
tools.append(PowerCalendarGetEventsTool(names))
261265

266+
if exposed_domains is not None and TODO_DOMAIN in exposed_domains:
267+
names = []
268+
for info in exposed_entities["entities"].values():
269+
if info["domain"] != TODO_DOMAIN:
270+
continue
271+
names.extend(info["names"].split(", "))
272+
tools.append(PowerTodoGetItemsTool(names))
273+
262274
tools.extend(
263275
PowerScriptTool(self.hass, script_entity_id)
264276
for script_entity_id in exposed_entities[SCRIPT_DOMAIN]
265277
)
266278

279+
if exposed_domains:
280+
tools.append(PowerGetLiveContextTool())
281+
267282
tools.append(
268283
DynamicScriptTool(self.config_entry.options[CONF_SCRIPT_EXPOSED_ONLY])
269284
)

custom_components/powerllm/config_flow.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ async def get_options_schema(self) -> vol.Schema:
146146
tmp_context = llm.LLMContext(
147147
platform=DOMAIN,
148148
context=Context(user_id="Temp"),
149-
user_prompt=None,
150149
language=None,
151150
assistant=None,
152151
device_id=None,

custom_components/powerllm/http.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ async def get(self, request: web.Request, api: str) -> web.Response:
4747
llm_context = llm.LLMContext(
4848
platform=DOMAIN,
4949
context=self.context(request),
50-
user_prompt=None,
5150
language=hass.config.language,
5251
assistant=CONVERSATION_DOMAIN,
5352
device_id=None,
@@ -69,7 +68,6 @@ async def get(self, request: web.Request, api: str) -> web.Response:
6968
@RequestDataValidator(
7069
vol.Schema(
7170
{
72-
vol.Optional("user_input"): cv.string,
7371
vol.Optional("language"): cv.string,
7472
vol.Optional("device_id"): cv.string,
7573
}
@@ -85,7 +83,6 @@ async def post(
8583
llm_context = llm.LLMContext(
8684
platform=DOMAIN,
8785
context=self.context(request),
88-
user_prompt=data.get("user_input"),
8986
language=data.get("language", hass.config.language),
9087
assistant=CONVERSATION_DOMAIN,
9188
device_id=data.get("device_id"),
@@ -115,7 +112,6 @@ class LLMToolView(HomeAssistantView):
115112
vol.Schema(
116113
{
117114
vol.Optional("tool_args", default={}): {cv.string: object},
118-
vol.Optional("user_input"): cv.string,
119115
vol.Optional("language"): cv.string,
120116
vol.Optional("device_id"): cv.string,
121117
}
@@ -135,7 +131,6 @@ async def post(
135131
llm_context = llm.LLMContext(
136132
platform=DOMAIN,
137133
context=self.context(request),
138-
user_prompt=data.get("user_input"),
139134
language=data.get("language", hass.config.language),
140135
assistant=CONVERSATION_DOMAIN,
141136
device_id=data.get("device_id"),

custom_components/powerllm/llm_tools.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -246,22 +246,39 @@ async def async_call(
246246
platform=llm_context.platform,
247247
intent_type=self.intent_handler.intent_type,
248248
slots=slots,
249-
text_input=llm_context.user_prompt,
249+
text_input=None,
250250
context=llm_context.context,
251251
language=llm_context.language,
252252
assistant=llm_context.assistant,
253253
device_id=llm_context.device_id,
254254
)
255-
response = intent_response.as_dict()
256-
if self._response_entities and intent_response.matched_states:
257-
response["data"]["matched_states"] = [
255+
return PowerIntentResponseDict(intent_response, self._response_entities, hass)
256+
257+
258+
class PowerIntentResponseDict(dict):
259+
"""Dictionary to represent an intent response resulting from a tool call."""
260+
261+
def __init__(
262+
self,
263+
intent_response: Any,
264+
response_entities: bool = False,
265+
hass: HomeAssistant | None = None,
266+
) -> None:
267+
"""Initialize the dictionary."""
268+
if not isinstance(intent_response, intent.IntentResponse):
269+
super().__init__(intent_response)
270+
return
271+
272+
result = intent_response.as_dict()
273+
if response_entities and intent_response.matched_states and hass is not None:
274+
result["data"]["matched_states"] = [
258275
_format_state(hass, state) for state in intent_response.matched_states
259276
]
260-
if self._response_entities and intent_response.unmatched_states:
261-
response["data"]["unmatched_states"] = [
277+
if response_entities and intent_response.unmatched_states and hass is not None:
278+
result["data"]["unmatched_states"] = [
262279
_format_state(hass, state) for state in intent_response.unmatched_states
263280
]
264-
del response["language"]
281+
del result["language"]
265282

266283
def remove_empty(value: JsonValueType):
267284
if isinstance(value, list):
@@ -274,16 +291,26 @@ def remove_empty(value: JsonValueType):
274291
if not value[key] and value[key] is not False:
275292
del value[key]
276293

277-
remove_empty(response)
278-
return response
294+
remove_empty(result)
295+
296+
super().__init__(result)
297+
self.original = intent_response
279298

280299

281300
class PowerScriptTool(PowerLLMTool, llm.ScriptTool):
282301
"""Power LLM Tool representing a Script."""
283302

284303

285304
class PowerCalendarGetEventsTool(PowerLLMTool, llm.CalendarGetEventsTool):
286-
"""LLM Tool allowing querying a calendar."""
305+
"""Power LLM Tool allowing querying a calendar."""
306+
307+
308+
class PowerTodoGetItemsTool(PowerLLMTool, llm.TodoGetItemsTool):
309+
"""Power LLM Tool allowing querying a to-do list."""
310+
311+
312+
class PowerGetLiveContextTool(PowerLLMTool, llm.GetLiveContextTool):
313+
"""Power LLM Tool for getting the current state of exposed entities."""
287314

288315

289316
class PowerFunctionTool(PowerLLMTool):

hacs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"name": "Power LLM",
3-
"homeassistant": "2025.3.0"
3+
"homeassistant": "2025.7.0"
44
}

tests/__snapshots__/test_http.ambr

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
'prompt': 'Only if the user wants to control a device, tell them to expose entities to their voice assistant in Home Assistant.',
55
'tools': list([
66
dict({
7-
'description': 'Turns on/opens a device or entity',
7+
'description': "Turns on/opens/presses a device or entity. For locks, this performs a 'lock' action. Use for requests like 'turn on', 'activate', 'enable', or 'lock'.",
88
'name': 'HassTurnOn',
99
'parameters': dict({
1010
'properties': dict({
@@ -22,13 +22,16 @@
2222
'garage',
2323
'gas',
2424
'gate',
25+
'identify',
2526
'outlet',
2627
'receiver',
28+
'restart',
2729
'shade',
2830
'shutter',
2931
'speaker',
3032
'switch',
3133
'tv',
34+
'update',
3235
'water',
3336
'window',
3437
]),
@@ -55,7 +58,7 @@
5558
}),
5659
}),
5760
dict({
58-
'description': 'Turns off/closes a device or entity',
61+
'description': "Turns off/closes a device or entity. For locks, this performs an 'unlock' action. Use for requests like 'turn off', 'deactivate', 'disable', or 'unlock'.",
5962
'name': 'HassTurnOff',
6063
'parameters': dict({
6164
'properties': dict({
@@ -73,13 +76,16 @@
7376
'garage',
7477
'gas',
7578
'gate',
79+
'identify',
7680
'outlet',
7781
'receiver',
82+
'restart',
7883
'shade',
7984
'shutter',
8085
'speaker',
8186
'switch',
8287
'tv',
88+
'update',
8389
'water',
8490
'window',
8591
]),

tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ def llm_context() -> llm.LLMContext:
6969
return llm.LLMContext(
7070
platform="test_platform",
7171
context=Context(user_id="12345"),
72-
user_prompt=None,
7372
language=None,
7473
assistant=None,
7574
device_id=None,

0 commit comments

Comments
 (0)