Skip to content

Commit 6890cf4

Browse files
committed
fix(open_responses): align integration with Responses API
1 parent b836bef commit 6890cf4

11 files changed

Lines changed: 352 additions & 1350 deletions

File tree

homeassistant/components/open_responses/__init__.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from homeassistant.config_entries import ConfigEntry
66
from homeassistant.const import CONF_API_KEY, Platform
77
from homeassistant.core import HomeAssistant
8-
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
98
from homeassistant.helpers import config_validation as cv
109
from homeassistant.helpers.httpx_client import get_async_client
1110
from homeassistant.helpers.typing import ConfigType
@@ -15,15 +14,17 @@
1514
PLATFORMS = (Platform.AI_TASK, Platform.CONVERSATION)
1615
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
1716

18-
type OpenAIConfigEntry = ConfigEntry[openai.AsyncClient]
17+
type OpenResponsesConfigEntry = ConfigEntry[openai.AsyncClient]
1918

2019

2120
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
2221
"""Set up Open Responses."""
2322
return True
2423

2524

26-
async def async_setup_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bool:
25+
async def async_setup_entry(
26+
hass: HomeAssistant, entry: OpenResponsesConfigEntry
27+
) -> bool:
2728
"""Set up Open Responses from a config entry."""
2829
client = openai.AsyncOpenAI(
2930
api_key=entry.data[CONF_API_KEY],
@@ -34,13 +35,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bo
3435
# Cache current platform data which gets added to each request (caching done by library)
3536
_ = await hass.async_add_executor_job(client.platform_headers)
3637

37-
try:
38-
await client.models.list(timeout=10.0)
39-
except openai.AuthenticationError as err:
40-
raise ConfigEntryAuthFailed(err) from err
41-
except openai.OpenAIError as err:
42-
raise ConfigEntryNotReady(err) from err
43-
4438
entry.runtime_data = client
4539

4640
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -50,11 +44,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bo
5044
return True
5145

5246

53-
async def async_unload_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bool:
47+
async def async_unload_entry(
48+
hass: HomeAssistant, entry: OpenResponsesConfigEntry
49+
) -> bool:
5450
"""Unload Open Responses."""
5551
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
5652

5753

58-
async def async_update_options(hass: HomeAssistant, entry: OpenAIConfigEntry) -> None:
54+
async def async_update_options(
55+
hass: HomeAssistant, entry: OpenResponsesConfigEntry
56+
) -> None:
5957
"""Update options."""
6058
await hass.config_entries.async_reload(entry.entry_id)
Lines changed: 9 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,28 @@
11
"""AI Task integration for Open Responses."""
22

3-
import base64
43
from json import JSONDecodeError
54
import logging
65
from typing import TYPE_CHECKING
76

8-
from openai.types.responses.response_output_item import ImageGenerationCall
9-
107
from homeassistant.components import ai_task, conversation
118
from homeassistant.core import HomeAssistant
129
from homeassistant.exceptions import HomeAssistantError
1310
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
1411
from homeassistant.util.json import json_loads
1512

16-
from .const import (
17-
CONF_CHAT_MODEL,
18-
CONF_IMAGE_MODEL,
19-
RECOMMENDED_CHAT_MODEL,
20-
RECOMMENDED_IMAGE_MODEL,
21-
UNSUPPORTED_IMAGE_MODELS,
22-
)
23-
from .entity import OpenAIBaseLLMEntity
13+
from .entity import OpenResponsesEntity
2414

2515
if TYPE_CHECKING:
2616
from homeassistant.config_entries import ConfigSubentry
2717

28-
from . import OpenAIConfigEntry
18+
from . import OpenResponsesConfigEntry
2919

3020
_LOGGER = logging.getLogger(__name__)
3121

3222

3323
async def async_setup_entry(
3424
hass: HomeAssistant,
35-
config_entry: OpenAIConfigEntry,
25+
config_entry: OpenResponsesConfigEntry,
3626
async_add_entities: AddConfigEntryEntitiesCallback,
3727
) -> None:
3828
"""Set up AI Task entities."""
@@ -41,27 +31,26 @@ async def async_setup_entry(
4131
continue
4232

4333
async_add_entities(
44-
[OpenAITaskEntity(config_entry, subentry)],
34+
[OpenResponsesTaskEntity(config_entry, subentry)],
4535
config_subentry_id=subentry.subentry_id,
4636
)
4737

4838

49-
class OpenAITaskEntity(
39+
class OpenResponsesTaskEntity(
5040
ai_task.AITaskEntity,
51-
OpenAIBaseLLMEntity,
41+
OpenResponsesEntity,
5242
):
5343
"""Open Responses AI Task entity."""
5444

55-
def __init__(self, entry: OpenAIConfigEntry, subentry: ConfigSubentry) -> None:
45+
def __init__(
46+
self, entry: OpenResponsesConfigEntry, subentry: ConfigSubentry
47+
) -> None:
5648
"""Initialize the entity."""
5749
super().__init__(entry, subentry)
5850
self._attr_supported_features = (
5951
ai_task.AITaskEntityFeature.GENERATE_DATA
6052
| ai_task.AITaskEntityFeature.SUPPORT_ATTACHMENTS
6153
)
62-
model = self.subentry.data.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
63-
if not model.startswith(tuple(UNSUPPORTED_IMAGE_MODELS)):
64-
self._attr_supported_features |= ai_task.AITaskEntityFeature.GENERATE_IMAGE
6554

6655
async def _async_generate_data(
6756
self,
@@ -101,56 +90,3 @@ async def _async_generate_data(
10190
conversation_id=chat_log.conversation_id,
10291
data=data,
10392
)
104-
105-
async def _async_generate_image(
106-
self,
107-
task: ai_task.GenImageTask,
108-
chat_log: conversation.ChatLog,
109-
) -> ai_task.GenImageTaskResult:
110-
"""Handle a generate image task."""
111-
await self._async_handle_chat_log(chat_log, task.name, force_image=True)
112-
113-
if not isinstance(chat_log.content[-1], conversation.AssistantContent):
114-
raise HomeAssistantError(
115-
"Last content in chat log is not an AssistantContent"
116-
)
117-
118-
image_call: ImageGenerationCall | None = None
119-
for content in reversed(chat_log.content):
120-
if not isinstance(content, conversation.AssistantContent):
121-
break
122-
if isinstance(content.native, ImageGenerationCall):
123-
if image_call is None or image_call.result is None:
124-
image_call = content.native
125-
else: # Remove image data from chat log to save memory
126-
content.native.result = None
127-
128-
if image_call is None or image_call.result is None:
129-
raise HomeAssistantError("No image returned")
130-
131-
image_data = base64.b64decode(image_call.result)
132-
image_call.result = None
133-
134-
if hasattr(image_call, "output_format") and (
135-
output_format := image_call.output_format
136-
):
137-
mime_type = f"image/{output_format}"
138-
else:
139-
mime_type = "image/png"
140-
141-
if hasattr(image_call, "size") and (size := image_call.size):
142-
width, height = tuple(size.split("x"))
143-
else:
144-
width, height = None, None
145-
146-
return ai_task.GenImageTaskResult(
147-
image_data=image_data,
148-
conversation_id=chat_log.conversation_id,
149-
mime_type=mime_type,
150-
width=int(width) if width else None,
151-
height=int(height) if height else None,
152-
model=self.subentry.data.get(CONF_IMAGE_MODEL, RECOMMENDED_IMAGE_MODEL),
153-
revised_prompt=image_call.revised_prompt
154-
if hasattr(image_call, "revised_prompt")
155-
else None,
156-
)

0 commit comments

Comments
 (0)