Update changelog for version 0.1.13, add native Assist entity exposure support, enhance context handling in chat service, and introduce helper for entity exposure context.
This commit is contained in:
@@ -44,6 +44,7 @@ from .const import (
|
||||
SERVICE_SEND_MESSAGE,
|
||||
)
|
||||
from .coordinator import OpenClawCoordinator
|
||||
from .exposure import build_exposed_entities_context
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -313,9 +314,11 @@ def _async_register_services(hass: HomeAssistant) -> None:
|
||||
coordinator: OpenClawCoordinator = entry_data["coordinator"]
|
||||
|
||||
try:
|
||||
system_prompt = build_exposed_entities_context(hass, assistant="assist")
|
||||
response = await client.async_send_message(
|
||||
message=message,
|
||||
session_id=session_id,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
|
||||
assistant_message = _extract_assistant_message(response)
|
||||
|
||||
@@ -165,6 +165,7 @@ class OpenClawApiClient:
|
||||
message: str,
|
||||
session_id: str | None = None,
|
||||
model: str | None = None,
|
||||
system_prompt: str | None = None,
|
||||
stream: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Send a chat message (non-streaming).
|
||||
@@ -181,8 +182,13 @@ class OpenClawApiClient:
|
||||
if stream:
|
||||
raise ValueError("Use async_stream_message() for streaming")
|
||||
|
||||
messages: list[dict[str, str]] = []
|
||||
if system_prompt:
|
||||
messages.append({"role": "system", "content": system_prompt})
|
||||
messages.append({"role": "user", "content": message})
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"messages": [{"role": "user", "content": message}],
|
||||
"messages": messages,
|
||||
"stream": False,
|
||||
}
|
||||
if model:
|
||||
@@ -220,6 +226,7 @@ class OpenClawApiClient:
|
||||
message: str,
|
||||
session_id: str | None = None,
|
||||
model: str | None = None,
|
||||
system_prompt: str | None = None,
|
||||
) -> AsyncIterator[str]:
|
||||
"""Send a chat message and stream the response via SSE.
|
||||
|
||||
@@ -233,8 +240,13 @@ class OpenClawApiClient:
|
||||
Yields:
|
||||
Content delta strings from the streaming response.
|
||||
"""
|
||||
messages: list[dict[str, str]] = []
|
||||
if system_prompt:
|
||||
messages.append({"role": "system", "content": system_prompt})
|
||||
messages.append({"role": "user", "content": message})
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"messages": [{"role": "user", "content": message}],
|
||||
"messages": messages,
|
||||
"stream": True,
|
||||
}
|
||||
if model:
|
||||
|
||||
@@ -26,6 +26,7 @@ from .const import (
|
||||
EVENT_MESSAGE_RECEIVED,
|
||||
)
|
||||
from .coordinator import OpenClawCoordinator
|
||||
from .exposure import build_exposed_entities_context
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -100,9 +101,23 @@ class OpenClawConversationAgent(conversation.AbstractConversationAgent):
|
||||
|
||||
message = user_input.text
|
||||
conversation_id = user_input.conversation_id or "default"
|
||||
assistant_id = getattr(user_input, "assistant", None) or "assist"
|
||||
exposed_context = build_exposed_entities_context(
|
||||
self.hass,
|
||||
assistant=assistant_id,
|
||||
)
|
||||
extra_system_prompt = getattr(user_input, "extra_system_prompt", None)
|
||||
system_prompt = "\n\n".join(
|
||||
part for part in (exposed_context, extra_system_prompt) if part
|
||||
) or None
|
||||
|
||||
try:
|
||||
full_response = await self._get_response(client, message, conversation_id)
|
||||
full_response = await self._get_response(
|
||||
client,
|
||||
message,
|
||||
conversation_id,
|
||||
system_prompt,
|
||||
)
|
||||
except OpenClawApiError as err:
|
||||
_LOGGER.error("OpenClaw conversation error: %s", err)
|
||||
|
||||
@@ -113,7 +128,10 @@ class OpenClawConversationAgent(conversation.AbstractConversationAgent):
|
||||
if refreshed:
|
||||
try:
|
||||
full_response = await self._get_response(
|
||||
client, message, conversation_id
|
||||
client,
|
||||
message,
|
||||
conversation_id,
|
||||
system_prompt,
|
||||
)
|
||||
except OpenClawApiError as retry_err:
|
||||
return self._error_result(
|
||||
@@ -156,6 +174,7 @@ class OpenClawConversationAgent(conversation.AbstractConversationAgent):
|
||||
client: OpenClawApiClient,
|
||||
message: str,
|
||||
conversation_id: str,
|
||||
system_prompt: str | None = None,
|
||||
) -> str:
|
||||
"""Get a response from OpenClaw, trying streaming first."""
|
||||
# Try streaming (lower TTFB for voice pipeline)
|
||||
@@ -163,6 +182,7 @@ class OpenClawConversationAgent(conversation.AbstractConversationAgent):
|
||||
async for chunk in client.async_stream_message(
|
||||
message=message,
|
||||
session_id=conversation_id,
|
||||
system_prompt=system_prompt,
|
||||
):
|
||||
full_response += chunk
|
||||
|
||||
@@ -173,6 +193,7 @@ class OpenClawConversationAgent(conversation.AbstractConversationAgent):
|
||||
response = await client.async_send_message(
|
||||
message=message,
|
||||
session_id=conversation_id,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
extracted = self._extract_text_recursive(response)
|
||||
return extracted or ""
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Helpers for Assist entity exposure context."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from homeassistant.components.homeassistant import async_should_expose
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
def build_exposed_entities_context(
|
||||
hass: HomeAssistant,
|
||||
assistant: str | None = "assist",
|
||||
max_entities: int = 250,
|
||||
) -> str | None:
|
||||
"""Build a compact prompt block of entities exposed to an assistant.
|
||||
|
||||
Uses Home Assistant's built-in expose rules (Settings -> Voice assistants -> Expose).
|
||||
"""
|
||||
assistant_id = assistant or "assist"
|
||||
|
||||
exposed_states = [
|
||||
state
|
||||
for state in hass.states.async_all()
|
||||
if async_should_expose(hass, assistant_id, state.entity_id)
|
||||
]
|
||||
|
||||
if not exposed_states:
|
||||
return None
|
||||
|
||||
exposed_states.sort(key=lambda state: state.entity_id)
|
||||
domain_counts = Counter(state.domain for state in exposed_states)
|
||||
|
||||
lines: list[str] = [
|
||||
"Home Assistant live context (entities exposed to this assistant):",
|
||||
f"- total_exposed_entities: {len(exposed_states)}",
|
||||
"- domain_counts:",
|
||||
]
|
||||
lines.extend(f" - {domain}: {count}" for domain, count in sorted(domain_counts.items()))
|
||||
lines.append("- entities:")
|
||||
|
||||
for state in exposed_states[:max_entities]:
|
||||
friendly_name = state.name or state.entity_id
|
||||
lines.append(
|
||||
f" - id: {state.entity_id}; name: {friendly_name}; state: {state.state}"
|
||||
)
|
||||
|
||||
if len(exposed_states) > max_entities:
|
||||
lines.append(
|
||||
f" - ... {len(exposed_states) - max_entities} additional exposed entities omitted"
|
||||
)
|
||||
|
||||
lines.append(
|
||||
"Use only these exposed entities when answering or controlling Home Assistant."
|
||||
)
|
||||
|
||||
return "\n".join(lines)
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
||||
"requirements": [],
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.13",
|
||||
"dependencies": ["conversation"],
|
||||
"after_dependencies": ["hassio", "lovelace"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user