langchain-google-genai 1.0.3__tar.gz → 1.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/PKG-INFO +2 -2
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/__init__.py +1 -1
- langchain_google_genai-1.0.5/langchain_google_genai/_common.py +52 -0
- langchain_google_genai-1.0.5/langchain_google_genai/_enums.py +6 -0
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/_function_utils.py +50 -54
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/_genai_extension.py +64 -7
- langchain_google_genai-1.0.5/langchain_google_genai/_image_utils.py +187 -0
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/chat_models.py +211 -107
- langchain_google_genai-1.0.5/langchain_google_genai/embeddings.py +179 -0
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/llms.py +0 -1
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/pyproject.toml +5 -5
- langchain_google_genai-1.0.3/langchain_google_genai/_common.py +0 -4
- langchain_google_genai-1.0.3/langchain_google_genai/_enums.py +0 -6
- langchain_google_genai-1.0.3/langchain_google_genai/embeddings.py +0 -135
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/LICENSE +0 -0
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/README.md +0 -0
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/genai_aqa.py +0 -0
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/google_vector_store.py +0 -0
- {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: langchain-google-genai
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: An integration package connecting Google's genai package and LangChain
|
|
5
5
|
Home-page: https://github.com/langchain-ai/langchain-google
|
|
6
6
|
License: MIT
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Provides-Extra: images
|
|
15
15
|
Requires-Dist: google-generativeai (>=0.5.2,<0.6.0)
|
|
16
|
-
Requires-Dist: langchain-core (>=0.
|
|
16
|
+
Requires-Dist: langchain-core (>=0.2.0,<0.3)
|
|
17
17
|
Requires-Dist: pillow (>=10.1.0,<11.0.0) ; extra == "images"
|
|
18
18
|
Project-URL: Repository, https://github.com/langchain-ai/langchain-google
|
|
19
19
|
Project-URL: Source Code, https://github.com/langchain-ai/langchain-google/tree/main/libs/genai
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from importlib import metadata
|
|
2
|
+
from typing import Optional, Tuple, TypedDict
|
|
3
|
+
|
|
4
|
+
from google.api_core.gapic_v1.client_info import ClientInfo
|
|
5
|
+
|
|
6
|
+
from langchain_google_genai._enums import HarmBlockThreshold, HarmCategory
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GoogleGenerativeAIError(Exception):
|
|
10
|
+
"""
|
|
11
|
+
Custom exception class for errors associated with the `Google GenAI` API.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_user_agent(module: Optional[str] = None) -> Tuple[str, str]:
|
|
16
|
+
r"""Returns a custom user agent header.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
module (Optional[str]):
|
|
20
|
+
Optional. The module for a custom user agent header.
|
|
21
|
+
Returns:
|
|
22
|
+
Tuple[str, str]
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
langchain_version = metadata.version("langchain-google-genai")
|
|
26
|
+
except metadata.PackageNotFoundError:
|
|
27
|
+
langchain_version = "0.0.0"
|
|
28
|
+
client_library_version = (
|
|
29
|
+
f"{langchain_version}-{module}" if module else langchain_version
|
|
30
|
+
)
|
|
31
|
+
return client_library_version, f"langchain-google-genai/{client_library_version}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_client_info(module: Optional[str] = None) -> "ClientInfo":
|
|
35
|
+
r"""Returns a client info object with a custom user agent header.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
module (Optional[str]):
|
|
39
|
+
Optional. The module for a custom user agent header.
|
|
40
|
+
Returns:
|
|
41
|
+
google.api_core.gapic_v1.client_info.ClientInfo
|
|
42
|
+
"""
|
|
43
|
+
client_library_version, user_agent = get_user_agent(module)
|
|
44
|
+
return ClientInfo(
|
|
45
|
+
client_library_version=client_library_version,
|
|
46
|
+
user_agent=user_agent,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SafetySettingDict(TypedDict):
|
|
51
|
+
category: HarmCategory
|
|
52
|
+
threshold: HarmBlockThreshold
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
4
|
Any,
|
|
5
|
+
Callable,
|
|
5
6
|
Dict,
|
|
6
7
|
List,
|
|
7
8
|
Literal,
|
|
@@ -10,15 +11,16 @@ from typing import (
|
|
|
10
11
|
Type,
|
|
11
12
|
TypedDict,
|
|
12
13
|
Union,
|
|
14
|
+
cast,
|
|
13
15
|
)
|
|
14
16
|
|
|
15
17
|
import google.ai.generativelanguage as glm
|
|
16
|
-
from google.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
from google.ai.generativelanguage import (
|
|
19
|
+
FunctionCallingConfig,
|
|
20
|
+
FunctionDeclaration,
|
|
21
|
+
)
|
|
22
|
+
from google.ai.generativelanguage import (
|
|
23
|
+
Tool as GoogleTool,
|
|
22
24
|
)
|
|
23
25
|
from langchain_core.pydantic_v1 import BaseModel
|
|
24
26
|
from langchain_core.tools import BaseTool
|
|
@@ -36,51 +38,41 @@ TYPE_ENUM = {
|
|
|
36
38
|
|
|
37
39
|
TYPE_ENUM_REVERSE = {v: k for k, v in TYPE_ENUM.items()}
|
|
38
40
|
|
|
41
|
+
_FunctionDeclarationLike = Union[
|
|
42
|
+
BaseTool, Type[BaseModel], dict, Callable, FunctionDeclaration
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _ToolDict(TypedDict):
|
|
47
|
+
function_declarations: Sequence[_FunctionDeclarationLike]
|
|
48
|
+
|
|
39
49
|
|
|
40
50
|
def convert_to_genai_function_declarations(
|
|
41
51
|
tool: Union[
|
|
42
|
-
GoogleTool,
|
|
52
|
+
GoogleTool,
|
|
53
|
+
_ToolDict,
|
|
54
|
+
_FunctionDeclarationLike,
|
|
55
|
+
Sequence[_FunctionDeclarationLike],
|
|
43
56
|
],
|
|
44
|
-
) ->
|
|
45
|
-
"""Convert any tool-like object to a ToolType.
|
|
46
|
-
|
|
47
|
-
https://github.com/google-gemini/generative-ai-python/blob/668695ebe3e9de496a36eeb95cb2ed2faba9b939/google/generativeai/types/content_types.py#L574
|
|
48
|
-
"""
|
|
57
|
+
) -> GoogleTool:
|
|
49
58
|
if isinstance(tool, GoogleTool):
|
|
50
|
-
return tool
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
except AttributeError:
|
|
60
|
-
pass
|
|
61
|
-
if schema is None:
|
|
62
|
-
schema = first_function_declaration.get("parameters")
|
|
63
|
-
if schema is None or isinstance(schema, glm.Schema):
|
|
64
|
-
return tool
|
|
65
|
-
return glm.Tool(
|
|
59
|
+
return cast(GoogleTool, tool)
|
|
60
|
+
if isinstance(tool, type) and issubclass(tool, BaseModel):
|
|
61
|
+
return GoogleTool(function_declarations=[_convert_to_genai_function(tool)])
|
|
62
|
+
if callable(tool):
|
|
63
|
+
return _convert_tool_to_genai_function(callable_as_lc_tool()(tool))
|
|
64
|
+
if isinstance(tool, list):
|
|
65
|
+
return convert_to_genai_function_declarations({"function_declarations": tool})
|
|
66
|
+
if isinstance(tool, dict) and "function_declarations" in tool:
|
|
67
|
+
return GoogleTool(
|
|
66
68
|
function_declarations=[
|
|
67
69
|
_convert_to_genai_function(fc) for fc in tool["function_declarations"]
|
|
68
70
|
],
|
|
69
71
|
)
|
|
70
|
-
|
|
71
|
-
return glm.Tool(function_declarations=[_convert_to_genai_function(tool)])
|
|
72
|
-
elif callable(tool):
|
|
73
|
-
return _convert_tool_to_genai_function(callable_as_lc_tool()(tool))
|
|
74
|
-
elif isinstance(tool, list):
|
|
75
|
-
return glm.Tool(
|
|
76
|
-
function_declarations=[_convert_to_genai_function(fc) for fc in tool]
|
|
77
|
-
)
|
|
78
|
-
return glm.Tool(function_declarations=[_convert_to_genai_function(tool)])
|
|
72
|
+
return GoogleTool(function_declarations=[_convert_to_genai_function(tool)]) # type: ignore[arg-type]
|
|
79
73
|
|
|
80
74
|
|
|
81
|
-
def tool_to_dict(tool:
|
|
82
|
-
if isinstance(tool, GoogleTool):
|
|
83
|
-
tool = tool._proto
|
|
75
|
+
def tool_to_dict(tool: GoogleTool) -> _ToolDict:
|
|
84
76
|
function_declarations = []
|
|
85
77
|
for function_declaration_proto in tool.function_declarations:
|
|
86
78
|
properties: Dict[str, Any] = {}
|
|
@@ -108,7 +100,7 @@ def tool_to_dict(tool: Union[glm.Tool, GoogleTool]) -> ToolDict:
|
|
|
108
100
|
return {"function_declarations": function_declarations}
|
|
109
101
|
|
|
110
102
|
|
|
111
|
-
def _convert_to_genai_function(fc:
|
|
103
|
+
def _convert_to_genai_function(fc: _FunctionDeclarationLike) -> FunctionDeclaration:
|
|
112
104
|
if isinstance(fc, BaseTool):
|
|
113
105
|
return _convert_tool_to_genai_function(fc)
|
|
114
106
|
elif isinstance(fc, type) and issubclass(fc, BaseModel):
|
|
@@ -116,10 +108,9 @@ def _convert_to_genai_function(fc: FunctionDeclarationType) -> glm.FunctionDecla
|
|
|
116
108
|
elif callable(fc):
|
|
117
109
|
return _convert_tool_to_genai_function(callable_as_lc_tool()(fc))
|
|
118
110
|
elif isinstance(fc, dict):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
parameters={
|
|
111
|
+
formatted_fc = {"name": fc["name"], "description": fc.get("description")}
|
|
112
|
+
if "parameters" in fc:
|
|
113
|
+
formatted_fc["parameters"] = {
|
|
123
114
|
"properties": {
|
|
124
115
|
k: {
|
|
125
116
|
"type_": TYPE_ENUM[v["type"]],
|
|
@@ -127,19 +118,19 @@ def _convert_to_genai_function(fc: FunctionDeclarationType) -> glm.FunctionDecla
|
|
|
127
118
|
}
|
|
128
119
|
for k, v in fc["parameters"]["properties"].items()
|
|
129
120
|
},
|
|
130
|
-
"required": fc
|
|
121
|
+
"required": fc.get("parameters", []).get("required", []),
|
|
131
122
|
"type_": TYPE_ENUM[fc["parameters"]["type"]],
|
|
132
|
-
}
|
|
133
|
-
)
|
|
123
|
+
}
|
|
124
|
+
return FunctionDeclaration(**formatted_fc)
|
|
134
125
|
else:
|
|
135
126
|
raise ValueError(f"Unsupported function call type {fc}")
|
|
136
127
|
|
|
137
128
|
|
|
138
|
-
def _convert_tool_to_genai_function(tool: BaseTool) ->
|
|
129
|
+
def _convert_tool_to_genai_function(tool: BaseTool) -> FunctionDeclaration:
|
|
139
130
|
if tool.args_schema:
|
|
140
131
|
schema = dereference_refs(tool.args_schema.schema())
|
|
141
132
|
schema.pop("definitions", None)
|
|
142
|
-
return
|
|
133
|
+
return FunctionDeclaration(
|
|
143
134
|
name=tool.name or schema["title"],
|
|
144
135
|
description=tool.description or schema["description"],
|
|
145
136
|
parameters={
|
|
@@ -155,7 +146,7 @@ def _convert_tool_to_genai_function(tool: BaseTool) -> glm.FunctionDeclaration:
|
|
|
155
146
|
},
|
|
156
147
|
)
|
|
157
148
|
else:
|
|
158
|
-
return
|
|
149
|
+
return FunctionDeclaration(
|
|
159
150
|
name=tool.name,
|
|
160
151
|
description=tool.description,
|
|
161
152
|
parameters={
|
|
@@ -170,10 +161,10 @@ def _convert_tool_to_genai_function(tool: BaseTool) -> glm.FunctionDeclaration:
|
|
|
170
161
|
|
|
171
162
|
def _convert_pydantic_to_genai_function(
|
|
172
163
|
pydantic_model: Type[BaseModel],
|
|
173
|
-
) ->
|
|
164
|
+
) -> FunctionDeclaration:
|
|
174
165
|
schema = dereference_refs(pydantic_model.schema())
|
|
175
166
|
schema.pop("definitions", None)
|
|
176
|
-
return
|
|
167
|
+
return FunctionDeclaration(
|
|
177
168
|
name=schema["title"],
|
|
178
169
|
description=schema.get("description", ""),
|
|
179
170
|
parameters={
|
|
@@ -195,8 +186,13 @@ _ToolChoiceType = Union[
|
|
|
195
186
|
]
|
|
196
187
|
|
|
197
188
|
|
|
189
|
+
class _FunctionCallingConfigDict(TypedDict):
|
|
190
|
+
mode: Union[FunctionCallingConfig.Mode, str]
|
|
191
|
+
allowed_function_names: Optional[List[str]]
|
|
192
|
+
|
|
193
|
+
|
|
198
194
|
class _ToolConfigDict(TypedDict):
|
|
199
|
-
function_calling_config:
|
|
195
|
+
function_calling_config: _FunctionCallingConfigDict
|
|
200
196
|
|
|
201
197
|
|
|
202
198
|
def _tool_choice_to_tool_config(
|
|
@@ -12,6 +12,12 @@ from typing import Any, Dict, Iterator, List, MutableSequence, Optional
|
|
|
12
12
|
|
|
13
13
|
import google.ai.generativelanguage as genai
|
|
14
14
|
import langchain_core
|
|
15
|
+
from google.ai.generativelanguage_v1beta import (
|
|
16
|
+
GenerativeServiceAsyncClient as v1betaGenerativeServiceAsyncClient,
|
|
17
|
+
)
|
|
18
|
+
from google.ai.generativelanguage_v1beta import (
|
|
19
|
+
GenerativeServiceClient as v1betaGenerativeServiceClient,
|
|
20
|
+
)
|
|
15
21
|
from google.api_core import client_options as client_options_lib
|
|
16
22
|
from google.api_core import exceptions as gapi_exception
|
|
17
23
|
from google.api_core import gapic_v1
|
|
@@ -225,15 +231,66 @@ def build_semantic_retriever() -> genai.RetrieverServiceClient:
|
|
|
225
231
|
)
|
|
226
232
|
|
|
227
233
|
|
|
228
|
-
def
|
|
229
|
-
credentials =
|
|
230
|
-
|
|
234
|
+
def _prepare_config(
|
|
235
|
+
credentials: Optional[credentials.Credentials] = None,
|
|
236
|
+
api_key: Optional[str] = None,
|
|
237
|
+
client_options: Optional[Dict[str, Any]] = None,
|
|
238
|
+
client_info: Optional[gapic_v1.client_info.ClientInfo] = None,
|
|
239
|
+
transport: Optional[str] = None,
|
|
240
|
+
) -> Dict[str, Any]:
|
|
241
|
+
formatted_client_options = {"api_endpoint": _config.api_endpoint}
|
|
242
|
+
if client_options:
|
|
243
|
+
formatted_client_options.update(**client_options)
|
|
244
|
+
if not credentials and api_key:
|
|
245
|
+
formatted_client_options["api_key"] = api_key
|
|
246
|
+
elif not credentials and not api_key:
|
|
247
|
+
credentials = _get_credentials()
|
|
248
|
+
client_info = (
|
|
249
|
+
client_info
|
|
250
|
+
if client_info
|
|
251
|
+
else gapic_v1.client_info.ClientInfo(user_agent=_USER_AGENT)
|
|
252
|
+
)
|
|
253
|
+
config = {
|
|
254
|
+
"credentials": credentials,
|
|
255
|
+
"client_info": client_info,
|
|
256
|
+
"client_options": client_options_lib.ClientOptions(**formatted_client_options),
|
|
257
|
+
"transport": transport,
|
|
258
|
+
}
|
|
259
|
+
return {k: v for k, v in config.items() if v is not None}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def build_generative_service(
|
|
263
|
+
credentials: Optional[credentials.Credentials] = None,
|
|
264
|
+
api_key: Optional[str] = None,
|
|
265
|
+
client_options: Optional[Dict[str, Any]] = None,
|
|
266
|
+
client_info: Optional[gapic_v1.client_info.ClientInfo] = None,
|
|
267
|
+
transport: Optional[str] = None,
|
|
268
|
+
) -> v1betaGenerativeServiceClient:
|
|
269
|
+
config = _prepare_config(
|
|
231
270
|
credentials=credentials,
|
|
232
|
-
|
|
233
|
-
client_options=
|
|
234
|
-
|
|
235
|
-
|
|
271
|
+
api_key=api_key,
|
|
272
|
+
client_options=client_options,
|
|
273
|
+
transport=transport,
|
|
274
|
+
client_info=client_info,
|
|
275
|
+
)
|
|
276
|
+
return v1betaGenerativeServiceClient(**config)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def build_generative_async_service(
|
|
280
|
+
credentials: Optional[credentials.Credentials],
|
|
281
|
+
api_key: Optional[str] = None,
|
|
282
|
+
client_options: Optional[Dict[str, Any]] = None,
|
|
283
|
+
client_info: Optional[gapic_v1.client_info.ClientInfo] = None,
|
|
284
|
+
transport: Optional[str] = None,
|
|
285
|
+
) -> v1betaGenerativeServiceAsyncClient:
|
|
286
|
+
config = _prepare_config(
|
|
287
|
+
credentials=credentials,
|
|
288
|
+
api_key=api_key,
|
|
289
|
+
client_options=client_options,
|
|
290
|
+
transport=transport,
|
|
291
|
+
client_info=client_info,
|
|
236
292
|
)
|
|
293
|
+
return v1betaGenerativeServiceAsyncClient(**config)
|
|
237
294
|
|
|
238
295
|
|
|
239
296
|
def list_corpora(
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import mimetypes
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
from google.ai.generativelanguage_v1beta.types import Part
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Route(Enum):
|
|
16
|
+
"""Image Loading Route"""
|
|
17
|
+
|
|
18
|
+
BASE64 = 1
|
|
19
|
+
LOCAL_FILE = 2
|
|
20
|
+
URL = 3
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ImageBytesLoader:
|
|
24
|
+
"""Loads image bytes from multiple sources given a string.
|
|
25
|
+
|
|
26
|
+
Currently supported:
|
|
27
|
+
- B64 Encoded image string
|
|
28
|
+
- Local file path
|
|
29
|
+
- URL
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def load_bytes(self, image_string: str) -> bytes:
|
|
33
|
+
"""Routes to the correct loader based on the image_string.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
image_string: Can be either:
|
|
37
|
+
- B64 Encoded image string
|
|
38
|
+
- Local file path
|
|
39
|
+
- URL
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Image bytes.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
route = self._route(image_string)
|
|
46
|
+
|
|
47
|
+
if route == Route.BASE64:
|
|
48
|
+
return self._bytes_from_b64(image_string)
|
|
49
|
+
|
|
50
|
+
if route == Route.URL:
|
|
51
|
+
return self._bytes_from_url(image_string)
|
|
52
|
+
|
|
53
|
+
if route == Route.LOCAL_FILE:
|
|
54
|
+
return self._bytes_from_file(image_string)
|
|
55
|
+
|
|
56
|
+
raise ValueError(
|
|
57
|
+
"Image string must be one of: Google Cloud Storage URI, "
|
|
58
|
+
"b64 encoded image string (data:image/...), valid image url, "
|
|
59
|
+
f"or existing local image file. Instead got '{image_string}'."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def load_part(self, image_string: str) -> Part:
|
|
63
|
+
"""Gets Part for loading from Gemini.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
image_string: Can be either:
|
|
67
|
+
- B64 Encoded image string
|
|
68
|
+
- Local file path
|
|
69
|
+
- URL
|
|
70
|
+
"""
|
|
71
|
+
route = self._route(image_string)
|
|
72
|
+
|
|
73
|
+
if route == Route.BASE64:
|
|
74
|
+
bytes_ = self._bytes_from_b64(image_string)
|
|
75
|
+
|
|
76
|
+
if route == Route.URL:
|
|
77
|
+
bytes_ = self._bytes_from_url(image_string)
|
|
78
|
+
|
|
79
|
+
if route == Route.LOCAL_FILE:
|
|
80
|
+
bytes_ = self._bytes_from_file(image_string)
|
|
81
|
+
|
|
82
|
+
inline_data: Dict[str, Any] = {"data": bytes_}
|
|
83
|
+
mime_type, _ = mimetypes.guess_type(image_string)
|
|
84
|
+
if mime_type:
|
|
85
|
+
inline_data["mime_type"] = mime_type
|
|
86
|
+
|
|
87
|
+
return Part(inline_data=inline_data)
|
|
88
|
+
|
|
89
|
+
def _route(self, image_string: str) -> Route:
|
|
90
|
+
if image_string.startswith("data:image/"):
|
|
91
|
+
return Route.BASE64
|
|
92
|
+
|
|
93
|
+
if self._is_url(image_string):
|
|
94
|
+
return Route.URL
|
|
95
|
+
|
|
96
|
+
if os.path.exists(image_string):
|
|
97
|
+
return Route.LOCAL_FILE
|
|
98
|
+
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"Image string must be one of: "
|
|
101
|
+
"b64 encoded image string (data:image/...), valid image url, "
|
|
102
|
+
f"or existing local image file. Instead got '{image_string}'."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def _bytes_from_b64(self, base64_image: str) -> bytes:
|
|
106
|
+
"""Gets image bytes from a base64 encoded string.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
base64_image: Encoded image in b64 format.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Image bytes
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
pattern = r"data:image/\w{2,4};base64,(.*)"
|
|
116
|
+
match = re.search(pattern, base64_image)
|
|
117
|
+
|
|
118
|
+
if match is not None:
|
|
119
|
+
encoded_string = match.group(1)
|
|
120
|
+
return base64.b64decode(encoded_string)
|
|
121
|
+
|
|
122
|
+
raise ValueError(f"Error in b64 encoded image. Must follow pattern: {pattern}")
|
|
123
|
+
|
|
124
|
+
def _bytes_from_file(self, file_path: str) -> bytes:
|
|
125
|
+
"""Gets image bytes from a local file path.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
file_path: Existing file path.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Image bytes
|
|
132
|
+
"""
|
|
133
|
+
with open(file_path, "rb") as image_file:
|
|
134
|
+
image_bytes = image_file.read()
|
|
135
|
+
return image_bytes
|
|
136
|
+
|
|
137
|
+
def _bytes_from_url(self, url: str) -> bytes:
|
|
138
|
+
"""Gets image bytes from a public url.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
url: Valid url.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
HTTP Error if there is one.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Image bytes
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
response = requests.get(url)
|
|
151
|
+
|
|
152
|
+
if not response.ok:
|
|
153
|
+
response.raise_for_status()
|
|
154
|
+
|
|
155
|
+
return response.content
|
|
156
|
+
|
|
157
|
+
def _is_url(self, url_string: str) -> bool:
|
|
158
|
+
"""Checks if a url is valid.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
url_string: Url to check.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Whether the url is valid.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
result = urlparse(url_string)
|
|
168
|
+
return all([result.scheme, result.netloc])
|
|
169
|
+
except Exception:
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def image_bytes_to_b64_string(
|
|
174
|
+
image_bytes: bytes, encoding: str = "ascii", image_format: str = "png"
|
|
175
|
+
) -> str:
|
|
176
|
+
"""Encodes image bytes into a b64 encoded string.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
image_bytes: Bytes of the image.
|
|
180
|
+
encoding: Type of encoding in the string. 'ascii' by default.
|
|
181
|
+
image_format: Format of the image. 'png' by default.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
B64 image encoded string.
|
|
185
|
+
"""
|
|
186
|
+
encoded_bytes = base64.b64encode(image_bytes).decode(encoding)
|
|
187
|
+
return f"data:image/{image_format};base64,{encoded_bytes}"
|