1
+ import base64
1
2
import logging
3
+ import mimetypes
4
+ from pathlib import Path
5
+ from urllib .parse import urlparse
2
6
3
- import voluptuous as vol
4
7
from openai import AsyncOpenAI
5
8
from openai ._exceptions import OpenAIError
9
+ from openai .types .chat .chat_completion_content_part_image_param import (
10
+ ChatCompletionContentPartImageParam ,
11
+ )
12
+ import voluptuous as vol
6
13
7
14
from homeassistant .core import (
8
15
HomeAssistant ,
11
18
SupportsResponse ,
12
19
)
13
20
from homeassistant .exceptions import HomeAssistantError
21
+ from homeassistant .helpers import config_validation as cv , selector
14
22
from homeassistant .helpers .typing import ConfigType
15
- from homeassistant .helpers import selector , config_validation as cv
16
23
17
24
from .const import DOMAIN , SERVICE_QUERY_IMAGE
18
25
25
32
),
26
33
vol .Required ("model" , default = "gpt-4-vision-preview" ): cv .string ,
27
34
vol .Required ("prompt" ): cv .string ,
28
- vol .Required ("images" ): vol .All (cv .ensure_list , [{"url" : cv .url }]),
35
+ vol .Required ("images" ): vol .All (cv .ensure_list , [{"url" : cv .string }]),
29
36
vol .Optional ("max_tokens" , default = 300 ): cv .positive_int ,
30
37
}
31
38
)
@@ -41,7 +48,7 @@ async def query_image(call: ServiceCall) -> ServiceResponse:
41
48
try :
42
49
model = call .data ["model" ]
43
50
images = [
44
- {"type" : "image_url" , "image_url" : image }
51
+ {"type" : "image_url" , "image_url" : to_image_param ( hass , image ) }
45
52
for image in call .data ["images" ]
46
53
]
47
54
@@ -74,3 +81,32 @@ async def query_image(call: ServiceCall) -> ServiceResponse:
74
81
schema = QUERY_IMAGE_SCHEMA ,
75
82
supports_response = SupportsResponse .ONLY ,
76
83
)
84
+
85
+
86
+ def to_image_param (hass : HomeAssistant , image ) -> ChatCompletionContentPartImageParam :
87
+ """Convert url to base64 encoded image if local."""
88
+ url = image ["url" ]
89
+
90
+ if urlparse (url ).scheme in cv .EXTERNAL_URL_PROTOCOL_SCHEMA_LIST :
91
+ return image
92
+
93
+ if not hass .config .is_allowed_path (url ):
94
+ raise HomeAssistantError (
95
+ f"Cannot read `{ url } `, no access to path; "
96
+ "`allowlist_external_dirs` may need to be adjusted in "
97
+ "`configuration.yaml`"
98
+ )
99
+ if not Path (url ).exists ():
100
+ raise HomeAssistantError (f"`{ url } ` does not exist" )
101
+ mime_type , _ = mimetypes .guess_type (url )
102
+ if mime_type is None or not mime_type .startswith ("image" ):
103
+ raise HomeAssistantError (f"`{ url } ` is not an image" )
104
+
105
+ image ["url" ] = f"data:{ mime_type } ;base64,{ encode_image (url )} "
106
+ return image
107
+
108
+
109
+ def encode_image (image_path ):
110
+ """Convert to base64 encoded image."""
111
+ with open (image_path , "rb" ) as image_file :
112
+ return base64 .b64encode (image_file .read ()).decode ("utf-8" )
0 commit comments