Update changelog for version 0.1.12, improve response parsing for nested OpenAI-compatible payloads, and enhance assistant message extraction in conversation agent.
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
||||||
|
|
||||||
|
## [0.1.12] - 2026-02-20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Improved response parsing for nested/modern OpenAI-compatible payloads (including `output` / nested `content` shapes), which could previously result in missing UI replies.
|
||||||
|
- Applied the same recursive extraction strategy to both service-based chat responses and Assist conversation fallback parsing.
|
||||||
|
|
||||||
## [0.1.11] - 2026-02-20
|
## [0.1.11] - 2026-02-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -380,8 +380,11 @@ def _get_first_entry_data(hass: HomeAssistant) -> dict[str, Any] | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _coerce_text(value: Any) -> str | None:
|
def _extract_text_recursive(value: Any, depth: int = 0) -> str | None:
|
||||||
"""Convert known message content shapes to plain text."""
|
"""Recursively extract assistant text from nested response payloads."""
|
||||||
|
if depth > 8:
|
||||||
|
return None
|
||||||
|
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
text = value.strip()
|
text = value.strip()
|
||||||
return text or None
|
return text or None
|
||||||
@@ -389,38 +392,41 @@ def _coerce_text(value: Any) -> str | None:
|
|||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
for item in value:
|
for item in value:
|
||||||
if isinstance(item, str):
|
extracted = _extract_text_recursive(item, depth + 1)
|
||||||
if item.strip():
|
if extracted:
|
||||||
parts.append(item.strip())
|
parts.append(extracted)
|
||||||
continue
|
|
||||||
if isinstance(item, dict):
|
|
||||||
text_part = item.get("text") or item.get("content")
|
|
||||||
if isinstance(text_part, str) and text_part.strip():
|
|
||||||
parts.append(text_part.strip())
|
|
||||||
if parts:
|
if parts:
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(value, dict):
|
||||||
|
priority_keys = (
|
||||||
|
"output_text",
|
||||||
|
"text",
|
||||||
|
"content",
|
||||||
|
"message",
|
||||||
|
"response",
|
||||||
|
"answer",
|
||||||
|
"choices",
|
||||||
|
"output",
|
||||||
|
"delta",
|
||||||
|
)
|
||||||
|
|
||||||
|
for key in priority_keys:
|
||||||
|
if key not in value:
|
||||||
|
continue
|
||||||
|
extracted = _extract_text_recursive(value.get(key), depth + 1)
|
||||||
|
if extracted:
|
||||||
|
return extracted
|
||||||
|
|
||||||
|
for nested_value in value.values():
|
||||||
|
extracted = _extract_text_recursive(nested_value, depth + 1)
|
||||||
|
if extracted:
|
||||||
|
return extracted
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _extract_assistant_message(response: dict[str, Any]) -> str | None:
|
def _extract_assistant_message(response: dict[str, Any]) -> str | None:
|
||||||
"""Extract assistant text from multiple OpenAI-compatible response shapes."""
|
"""Extract assistant text from modern/legacy OpenAI-compatible responses."""
|
||||||
choices = response.get("choices")
|
return _extract_text_recursive(response)
|
||||||
if isinstance(choices, list) and choices:
|
|
||||||
first = choices[0]
|
|
||||||
if isinstance(first, dict):
|
|
||||||
message = first.get("message")
|
|
||||||
if isinstance(message, dict):
|
|
||||||
extracted = _coerce_text(message.get("content"))
|
|
||||||
if extracted:
|
|
||||||
return extracted
|
|
||||||
extracted = _coerce_text(first.get("text"))
|
|
||||||
if extracted:
|
|
||||||
return extracted
|
|
||||||
|
|
||||||
for key in ("output_text", "response", "message", "content", "answer"):
|
|
||||||
extracted = _coerce_text(response.get(key))
|
|
||||||
if extracted:
|
|
||||||
return extracted
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -174,10 +174,54 @@ class OpenClawConversationAgent(conversation.AbstractConversationAgent):
|
|||||||
message=message,
|
message=message,
|
||||||
session_id=conversation_id,
|
session_id=conversation_id,
|
||||||
)
|
)
|
||||||
choices = response.get("choices", [])
|
extracted = self._extract_text_recursive(response)
|
||||||
if choices:
|
return extracted or ""
|
||||||
return choices[0].get("message", {}).get("content", "")
|
|
||||||
return ""
|
def _extract_text_recursive(self, value: Any, depth: int = 0) -> str | None:
|
||||||
|
"""Recursively extract assistant text from nested response payloads."""
|
||||||
|
if depth > 8:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(value, str):
|
||||||
|
text = value.strip()
|
||||||
|
return text or None
|
||||||
|
|
||||||
|
if isinstance(value, list):
|
||||||
|
parts: list[str] = []
|
||||||
|
for item in value:
|
||||||
|
extracted = self._extract_text_recursive(item, depth + 1)
|
||||||
|
if extracted:
|
||||||
|
parts.append(extracted)
|
||||||
|
if parts:
|
||||||
|
return "\n".join(parts)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(value, dict):
|
||||||
|
priority_keys = (
|
||||||
|
"output_text",
|
||||||
|
"text",
|
||||||
|
"content",
|
||||||
|
"message",
|
||||||
|
"response",
|
||||||
|
"answer",
|
||||||
|
"choices",
|
||||||
|
"output",
|
||||||
|
"delta",
|
||||||
|
)
|
||||||
|
|
||||||
|
for key in priority_keys:
|
||||||
|
if key not in value:
|
||||||
|
continue
|
||||||
|
extracted = self._extract_text_recursive(value.get(key), depth + 1)
|
||||||
|
if extracted:
|
||||||
|
return extracted
|
||||||
|
|
||||||
|
for nested_value in value.values():
|
||||||
|
extracted = self._extract_text_recursive(nested_value, depth + 1)
|
||||||
|
if extracted:
|
||||||
|
return extracted
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _error_result(
|
def _error_result(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "0.1.11",
|
"version": "0.1.12",
|
||||||
"dependencies": ["conversation"],
|
"dependencies": ["conversation"],
|
||||||
"after_dependencies": ["hassio", "lovelace"]
|
"after_dependencies": ["hassio", "lovelace"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user