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.
Files changed (19) hide show
  1. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/PKG-INFO +2 -2
  2. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/__init__.py +1 -1
  3. langchain_google_genai-1.0.5/langchain_google_genai/_common.py +52 -0
  4. langchain_google_genai-1.0.5/langchain_google_genai/_enums.py +6 -0
  5. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/_function_utils.py +50 -54
  6. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/_genai_extension.py +64 -7
  7. langchain_google_genai-1.0.5/langchain_google_genai/_image_utils.py +187 -0
  8. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/chat_models.py +211 -107
  9. langchain_google_genai-1.0.5/langchain_google_genai/embeddings.py +179 -0
  10. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/llms.py +0 -1
  11. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/pyproject.toml +5 -5
  12. langchain_google_genai-1.0.3/langchain_google_genai/_common.py +0 -4
  13. langchain_google_genai-1.0.3/langchain_google_genai/_enums.py +0 -6
  14. langchain_google_genai-1.0.3/langchain_google_genai/embeddings.py +0 -135
  15. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/LICENSE +0 -0
  16. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/README.md +0 -0
  17. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/genai_aqa.py +0 -0
  18. {langchain_google_genai-1.0.3 → langchain_google_genai-1.0.5}/langchain_google_genai/google_vector_store.py +0 -0
  19. {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
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.1.45,<0.2)
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
@@ -35,7 +35,7 @@ llm.invoke("Sing a ballad of LangChain.")
35
35
  ## Using LLMs
36
36
 
37
37
  The package also supports generating text with Google's models.
38
-
38
+
39
39
  ```python
40
40
  from langchain_google_genai import GoogleGenerativeAI
41
41
 
@@ -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,6 @@
1
+ import google.ai.generativelanguage_v1beta as genai
2
+
3
+ HarmBlockThreshold = genai.SafetySetting.HarmBlockThreshold
4
+ HarmCategory = genai.HarmCategory
5
+
6
+ __all__ = ["HarmBlockThreshold", "HarmCategory"]
@@ -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.generativeai.types import Tool as GoogleTool # type: ignore[import]
17
- from google.generativeai.types.content_types import ( # type: ignore[import]
18
- FunctionCallingConfigType,
19
- FunctionDeclarationType,
20
- ToolDict,
21
- ToolType,
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, ToolDict, FunctionDeclarationType, Sequence[FunctionDeclarationType]
52
+ GoogleTool,
53
+ _ToolDict,
54
+ _FunctionDeclarationLike,
55
+ Sequence[_FunctionDeclarationLike],
43
56
  ],
44
- ) -> ToolType:
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
- # check whether a dict is supported by glm, otherwise we parse it explicitly
52
- if isinstance(tool, dict):
53
- first_function_declaration = tool.get("function_declarations", [None])[0]
54
- if isinstance(first_function_declaration, glm.FunctionDeclaration):
55
- return tool
56
- schema = None
57
- try:
58
- schema = first_function_declaration.parameters
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
- elif isinstance(tool, type) and issubclass(tool, BaseModel):
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: Union[glm.Tool, GoogleTool]) -> ToolDict:
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: FunctionDeclarationType) -> glm.FunctionDeclaration:
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
- return glm.FunctionDeclaration(
120
- name=fc["name"],
121
- description=fc.get("description"),
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["parameters"].get("required", []),
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) -> glm.FunctionDeclaration:
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 glm.FunctionDeclaration(
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 glm.FunctionDeclaration(
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
- ) -> glm.FunctionDeclaration:
164
+ ) -> FunctionDeclaration:
174
165
  schema = dereference_refs(pydantic_model.schema())
175
166
  schema.pop("definitions", None)
176
- return glm.FunctionDeclaration(
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: FunctionCallingConfigType
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 build_generative_service() -> genai.GenerativeServiceClient:
229
- credentials = _get_credentials()
230
- return genai.GenerativeServiceClient(
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
- client_info=gapic_v1.client_info.ClientInfo(user_agent=_USER_AGENT),
233
- client_options=client_options_lib.ClientOptions(
234
- api_endpoint=_config.api_endpoint
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}"