langchain-google-genai 1.0.2__tar.gz → 1.0.4__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.
Potentially problematic release.
This version of langchain-google-genai might be problematic. Click here for more details.
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/PKG-INFO +3 -3
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/langchain_google_genai/__init__.py +1 -1
- langchain_google_genai-1.0.4/langchain_google_genai/_common.py +52 -0
- langchain_google_genai-1.0.4/langchain_google_genai/_enums.py +6 -0
- langchain_google_genai-1.0.4/langchain_google_genai/_function_utils.py +237 -0
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/langchain_google_genai/_genai_extension.py +64 -7
- langchain_google_genai-1.0.4/langchain_google_genai/_image_utils.py +187 -0
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/langchain_google_genai/chat_models.py +360 -120
- langchain_google_genai-1.0.4/langchain_google_genai/embeddings.py +179 -0
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/langchain_google_genai/llms.py +8 -1
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/pyproject.toml +7 -7
- langchain_google_genai-1.0.2/langchain_google_genai/_common.py +0 -4
- langchain_google_genai-1.0.2/langchain_google_genai/_enums.py +0 -6
- langchain_google_genai-1.0.2/langchain_google_genai/_function_utils.py +0 -116
- langchain_google_genai-1.0.2/langchain_google_genai/embeddings.py +0 -129
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/LICENSE +0 -0
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/README.md +0 -0
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/langchain_google_genai/genai_aqa.py +0 -0
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/langchain_google_genai/google_vector_store.py +0 -0
- {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.4}/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.4
|
|
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
|
|
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Provides-Extra: images
|
|
15
|
-
Requires-Dist: google-generativeai (>=0.5.
|
|
16
|
-
Requires-Dist: langchain-core (>=0.1.
|
|
15
|
+
Requires-Dist: google-generativeai (>=0.5.2,<0.6.0)
|
|
16
|
+
Requires-Dist: langchain-core (>=0.1.45,<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
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Callable,
|
|
6
|
+
Dict,
|
|
7
|
+
List,
|
|
8
|
+
Literal,
|
|
9
|
+
Optional,
|
|
10
|
+
Sequence,
|
|
11
|
+
Type,
|
|
12
|
+
TypedDict,
|
|
13
|
+
Union,
|
|
14
|
+
cast,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
import google.ai.generativelanguage as glm
|
|
18
|
+
from google.ai.generativelanguage import (
|
|
19
|
+
FunctionCallingConfig,
|
|
20
|
+
FunctionDeclaration,
|
|
21
|
+
)
|
|
22
|
+
from google.ai.generativelanguage import (
|
|
23
|
+
Tool as GoogleTool,
|
|
24
|
+
)
|
|
25
|
+
from langchain_core.pydantic_v1 import BaseModel
|
|
26
|
+
from langchain_core.tools import BaseTool
|
|
27
|
+
from langchain_core.tools import tool as callable_as_lc_tool
|
|
28
|
+
from langchain_core.utils.json_schema import dereference_refs
|
|
29
|
+
|
|
30
|
+
TYPE_ENUM = {
|
|
31
|
+
"string": glm.Type.STRING,
|
|
32
|
+
"number": glm.Type.NUMBER,
|
|
33
|
+
"integer": glm.Type.INTEGER,
|
|
34
|
+
"boolean": glm.Type.BOOLEAN,
|
|
35
|
+
"array": glm.Type.ARRAY,
|
|
36
|
+
"object": glm.Type.OBJECT,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
TYPE_ENUM_REVERSE = {v: k for k, v in TYPE_ENUM.items()}
|
|
40
|
+
|
|
41
|
+
_FunctionDeclarationLike = Union[
|
|
42
|
+
BaseTool, Type[BaseModel], dict, Callable, FunctionDeclaration
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _ToolDict(TypedDict):
|
|
47
|
+
function_declarations: Sequence[_FunctionDeclarationLike]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def convert_to_genai_function_declarations(
|
|
51
|
+
tool: Union[
|
|
52
|
+
GoogleTool,
|
|
53
|
+
_ToolDict,
|
|
54
|
+
_FunctionDeclarationLike,
|
|
55
|
+
Sequence[_FunctionDeclarationLike],
|
|
56
|
+
],
|
|
57
|
+
) -> GoogleTool:
|
|
58
|
+
if isinstance(tool, GoogleTool):
|
|
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(
|
|
68
|
+
function_declarations=[
|
|
69
|
+
_convert_to_genai_function(fc) for fc in tool["function_declarations"]
|
|
70
|
+
],
|
|
71
|
+
)
|
|
72
|
+
return GoogleTool(function_declarations=[_convert_to_genai_function(tool)]) # type: ignore[arg-type]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def tool_to_dict(tool: GoogleTool) -> _ToolDict:
|
|
76
|
+
function_declarations = []
|
|
77
|
+
for function_declaration_proto in tool.function_declarations:
|
|
78
|
+
properties: Dict[str, Any] = {}
|
|
79
|
+
for property in function_declaration_proto.parameters.properties:
|
|
80
|
+
property_type = function_declaration_proto.parameters.properties[
|
|
81
|
+
property
|
|
82
|
+
].type
|
|
83
|
+
property_dict = {"type": TYPE_ENUM_REVERSE[property_type]}
|
|
84
|
+
property_description = function_declaration_proto.parameters.properties[
|
|
85
|
+
property
|
|
86
|
+
].description
|
|
87
|
+
if property_description:
|
|
88
|
+
property_dict["description"] = property_description
|
|
89
|
+
properties[property] = property_dict
|
|
90
|
+
function_declaration = {
|
|
91
|
+
"name": function_declaration_proto.name,
|
|
92
|
+
"description": function_declaration_proto.description,
|
|
93
|
+
"parameters": {"type": "object", "properties": properties},
|
|
94
|
+
}
|
|
95
|
+
if function_declaration_proto.parameters.required:
|
|
96
|
+
function_declaration["parameters"][ # type: ignore[index]
|
|
97
|
+
"required"
|
|
98
|
+
] = function_declaration_proto.parameters.required
|
|
99
|
+
function_declarations.append(function_declaration)
|
|
100
|
+
return {"function_declarations": function_declarations}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _convert_to_genai_function(fc: _FunctionDeclarationLike) -> FunctionDeclaration:
|
|
104
|
+
if isinstance(fc, BaseTool):
|
|
105
|
+
return _convert_tool_to_genai_function(fc)
|
|
106
|
+
elif isinstance(fc, type) and issubclass(fc, BaseModel):
|
|
107
|
+
return _convert_pydantic_to_genai_function(fc)
|
|
108
|
+
elif callable(fc):
|
|
109
|
+
return _convert_tool_to_genai_function(callable_as_lc_tool()(fc))
|
|
110
|
+
elif isinstance(fc, dict):
|
|
111
|
+
formatted_fc = {"name": fc["name"], "description": fc.get("description")}
|
|
112
|
+
if "parameters" in fc:
|
|
113
|
+
formatted_fc["parameters"] = {
|
|
114
|
+
"properties": {
|
|
115
|
+
k: {
|
|
116
|
+
"type_": TYPE_ENUM[v["type"]],
|
|
117
|
+
"description": v.get("description"),
|
|
118
|
+
}
|
|
119
|
+
for k, v in fc["parameters"]["properties"].items()
|
|
120
|
+
},
|
|
121
|
+
"required": fc.get("parameters", []).get("required", []),
|
|
122
|
+
"type_": TYPE_ENUM[fc["parameters"]["type"]],
|
|
123
|
+
}
|
|
124
|
+
return FunctionDeclaration(**formatted_fc)
|
|
125
|
+
else:
|
|
126
|
+
raise ValueError(f"Unsupported function call type {fc}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _convert_tool_to_genai_function(tool: BaseTool) -> FunctionDeclaration:
|
|
130
|
+
if tool.args_schema:
|
|
131
|
+
schema = dereference_refs(tool.args_schema.schema())
|
|
132
|
+
schema.pop("definitions", None)
|
|
133
|
+
return FunctionDeclaration(
|
|
134
|
+
name=tool.name or schema["title"],
|
|
135
|
+
description=tool.description or schema["description"],
|
|
136
|
+
parameters={
|
|
137
|
+
"properties": {
|
|
138
|
+
k: {
|
|
139
|
+
"type_": TYPE_ENUM[v["type"]],
|
|
140
|
+
"description": v.get("description"),
|
|
141
|
+
}
|
|
142
|
+
for k, v in schema["properties"].items()
|
|
143
|
+
},
|
|
144
|
+
"required": schema.get("required", []),
|
|
145
|
+
"type_": TYPE_ENUM[schema["type"]],
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
return FunctionDeclaration(
|
|
150
|
+
name=tool.name,
|
|
151
|
+
description=tool.description,
|
|
152
|
+
parameters={
|
|
153
|
+
"properties": {
|
|
154
|
+
"__arg1": {"type_": TYPE_ENUM["string"]},
|
|
155
|
+
},
|
|
156
|
+
"required": ["__arg1"],
|
|
157
|
+
"type_": TYPE_ENUM["object"],
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _convert_pydantic_to_genai_function(
|
|
163
|
+
pydantic_model: Type[BaseModel],
|
|
164
|
+
) -> FunctionDeclaration:
|
|
165
|
+
schema = dereference_refs(pydantic_model.schema())
|
|
166
|
+
schema.pop("definitions", None)
|
|
167
|
+
return FunctionDeclaration(
|
|
168
|
+
name=schema["title"],
|
|
169
|
+
description=schema.get("description", ""),
|
|
170
|
+
parameters={
|
|
171
|
+
"properties": {
|
|
172
|
+
k: {
|
|
173
|
+
"type_": TYPE_ENUM[v["type"]],
|
|
174
|
+
"description": v.get("description"),
|
|
175
|
+
}
|
|
176
|
+
for k, v in schema["properties"].items()
|
|
177
|
+
},
|
|
178
|
+
"required": schema["required"],
|
|
179
|
+
"type_": TYPE_ENUM[schema["type"]],
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
_ToolChoiceType = Union[
|
|
185
|
+
dict, List[str], str, Literal["auto", "none", "any"], Literal[True]
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class _FunctionCallingConfigDict(TypedDict):
|
|
190
|
+
mode: Union[FunctionCallingConfig.Mode, str]
|
|
191
|
+
allowed_function_names: Optional[List[str]]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class _ToolConfigDict(TypedDict):
|
|
195
|
+
function_calling_config: _FunctionCallingConfigDict
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _tool_choice_to_tool_config(
|
|
199
|
+
tool_choice: _ToolChoiceType,
|
|
200
|
+
all_names: List[str],
|
|
201
|
+
) -> _ToolConfigDict:
|
|
202
|
+
allowed_function_names: Optional[List[str]] = None
|
|
203
|
+
if tool_choice is True or tool_choice == "any":
|
|
204
|
+
mode = "any"
|
|
205
|
+
allowed_function_names = all_names
|
|
206
|
+
elif tool_choice == "auto":
|
|
207
|
+
mode = "auto"
|
|
208
|
+
elif tool_choice == "none":
|
|
209
|
+
mode = "none"
|
|
210
|
+
elif isinstance(tool_choice, str):
|
|
211
|
+
mode = "any"
|
|
212
|
+
allowed_function_names = [tool_choice]
|
|
213
|
+
elif isinstance(tool_choice, list):
|
|
214
|
+
mode = "any"
|
|
215
|
+
allowed_function_names = tool_choice
|
|
216
|
+
elif isinstance(tool_choice, dict):
|
|
217
|
+
if "mode" in tool_choice:
|
|
218
|
+
mode = tool_choice["mode"]
|
|
219
|
+
allowed_function_names = tool_choice.get("allowed_function_names")
|
|
220
|
+
elif "function_calling_config" in tool_choice:
|
|
221
|
+
mode = tool_choice["function_calling_config"]["mode"]
|
|
222
|
+
allowed_function_names = tool_choice["function_calling_config"].get(
|
|
223
|
+
"allowed_function_names"
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
raise ValueError(
|
|
227
|
+
f"Unrecognized tool choice format:\n\n{tool_choice=}\n\nShould match "
|
|
228
|
+
f"Google GenerativeAI ToolConfig or FunctionCallingConfig format."
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
raise ValueError(f"Unrecognized tool choice format:\n\n{tool_choice=}")
|
|
232
|
+
return _ToolConfigDict(
|
|
233
|
+
function_calling_config={
|
|
234
|
+
"mode": mode,
|
|
235
|
+
"allowed_function_names": allowed_function_names,
|
|
236
|
+
}
|
|
237
|
+
)
|
|
@@ -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}"
|