langchain-google-genai 2.1.2__py3-none-any.whl → 2.1.3__py3-none-any.whl

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.
@@ -36,7 +36,7 @@ Supported examples:
36
36
  "the GOOGLE_API_KEY envvar"
37
37
  temperature: float = 0.7
38
38
  """Run inference with this temperature. Must by in the closed interval
39
- [0.0, 1.0]."""
39
+ [0.0, 2.0]."""
40
40
  top_p: Optional[float] = None
41
41
  """Decode using nucleus sampling: consider the smallest set of tokens whose
42
42
  probability sum is at least top_p. Must be in the closed interval [0.0, 1.0]."""
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import base64
4
5
  import json
5
6
  import logging
7
+ import mimetypes
6
8
  import uuid
7
9
  import warnings
10
+ from difflib import get_close_matches
8
11
  from operator import itemgetter
9
12
  from typing import (
10
13
  Any,
@@ -22,6 +25,7 @@ from typing import (
22
25
  cast,
23
26
  )
24
27
 
28
+ import filetype # type: ignore[import]
25
29
  import google.api_core
26
30
 
27
31
  # TODO: remove ignore once the google package is published with types
@@ -61,6 +65,7 @@ from langchain_core.messages import (
61
65
  HumanMessage,
62
66
  SystemMessage,
63
67
  ToolMessage,
68
+ is_data_content_block,
64
69
  )
65
70
  from langchain_core.messages.ai import UsageMetadata
66
71
  from langchain_core.messages.tool import invalid_tool_call, tool_call, tool_call_chunk
@@ -112,9 +117,6 @@ from langchain_google_genai._image_utils import (
112
117
 
113
118
  from . import _genai_extension as genaix
114
119
 
115
- WARNED_STRUCTURED_OUTPUT_JSON_MODE = False
116
-
117
-
118
120
  logger = logging.getLogger(__name__)
119
121
 
120
122
 
@@ -239,7 +241,7 @@ async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
239
241
  return await _achat_with_retry(**kwargs)
240
242
 
241
243
 
242
- def _is_openai_parts_format(part: dict) -> bool:
244
+ def _is_lc_content_block(part: dict) -> bool:
243
245
  return "type" in part
244
246
 
245
247
 
@@ -254,10 +256,29 @@ def _convert_to_parts(
254
256
  if isinstance(part, str):
255
257
  parts.append(Part(text=part))
256
258
  elif isinstance(part, Mapping):
257
- # OpenAI Format
258
- if _is_openai_parts_format(part):
259
+ if _is_lc_content_block(part):
259
260
  if part["type"] == "text":
260
261
  parts.append(Part(text=part["text"]))
262
+ elif is_data_content_block(part):
263
+ if part["source_type"] == "url":
264
+ bytes_ = image_loader._bytes_from_url(part["url"])
265
+ elif part["source_type"] == "base64":
266
+ bytes_ = base64.b64decode(part["data"])
267
+ else:
268
+ raise ValueError("source_type must be url or base64.")
269
+ inline_data: dict = {"data": bytes_}
270
+ if "mime_type" in part:
271
+ inline_data["mime_type"] = part["mime_type"]
272
+ else:
273
+ source = cast(str, part.get("url") or part.get("data"))
274
+ mime_type, _ = mimetypes.guess_type(source)
275
+ if not mime_type:
276
+ kind = filetype.guess(bytes_)
277
+ if kind:
278
+ mime_type = kind.mime
279
+ if mime_type:
280
+ inline_data["mime_type"] = mime_type
281
+ parts.append(Part(inline_data=inline_data))
261
282
  elif part["type"] == "image_url":
262
283
  img_url = part["image_url"]
263
284
  if isinstance(img_url, dict):
@@ -709,7 +730,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
709
730
  .. code-block:: python
710
731
 
711
732
  AIMessageChunk(content='J', response_metadata={'finish_reason': 'STOP', 'safety_ratings': []}, id='run-e905f4f4-58cb-4a10-a960-448a2bb649e3', usage_metadata={'input_tokens': 18, 'output_tokens': 1, 'total_tokens': 19})
712
- AIMessageChunk(content="'adore programmer. \n", response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-e905f4f4-58cb-4a10-a960-448a2bb649e3', usage_metadata={'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23})
733
+ AIMessageChunk(content="'adore programmer. \\n", response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-e905f4f4-58cb-4a10-a960-448a2bb649e3', usage_metadata={'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23})
713
734
 
714
735
  .. code-block:: python
715
736
 
@@ -739,6 +760,109 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
739
760
  # batch:
740
761
  # await llm.abatch([messages])
741
762
 
763
+ Context Caching:
764
+ Context caching allows you to store and reuse content (e.g., PDFs, images) for faster processing.
765
+ The `cached_content` parameter accepts a cache name created via the Google Generative AI API.
766
+ Below are two examples: caching a single file directly and caching multiple files using `Part`.
767
+
768
+ Single File Example:
769
+ This caches a single file and queries it.
770
+
771
+ .. code-block:: python
772
+
773
+ from google import genai
774
+ from google.genai import types
775
+ import time
776
+ from langchain_google_genai import ChatGoogleGenerativeAI
777
+ from langchain_core.messages import HumanMessage
778
+
779
+ client = genai.Client()
780
+
781
+ # Upload file
782
+ file = client.files.upload(file="./example_file")
783
+ while file.state.name == 'PROCESSING':
784
+ time.sleep(2)
785
+ file = client.files.get(name=file.name)
786
+
787
+ # Create cache
788
+ model = 'models/gemini-1.5-flash-001'
789
+ cache = client.caches.create(
790
+ model=model,
791
+ config=types.CreateCachedContentConfig(
792
+ display_name='Cached Content',
793
+ system_instruction=(
794
+ 'You are an expert content analyzer, and your job is to answer '
795
+ 'the user\'s query based on the file you have access to.'
796
+ ),
797
+ contents=[file],
798
+ ttl="300s",
799
+ )
800
+ )
801
+
802
+ # Query with LangChain
803
+ llm = ChatGoogleGenerativeAI(
804
+ model=model,
805
+ cached_content=cache.name,
806
+ )
807
+ message = HumanMessage(content="Summarize the main points of the content.")
808
+ llm.invoke([message])
809
+
810
+ Multiple Files Example:
811
+ This caches two files using `Part` and queries them together.
812
+
813
+ .. code-block:: python
814
+
815
+ from google import genai
816
+ from google.genai.types import CreateCachedContentConfig, Content, Part
817
+ import time
818
+ from langchain_google_genai import ChatGoogleGenerativeAI
819
+ from langchain_core.messages import HumanMessage
820
+
821
+ client = genai.Client()
822
+
823
+ # Upload files
824
+ file_1 = client.files.upload(file="./file1")
825
+ while file_1.state.name == 'PROCESSING':
826
+ time.sleep(2)
827
+ file_1 = client.files.get(name=file_1.name)
828
+
829
+ file_2 = client.files.upload(file="./file2")
830
+ while file_2.state.name == 'PROCESSING':
831
+ time.sleep(2)
832
+ file_2 = client.files.get(name=file_2.name)
833
+
834
+ # Create cache with multiple files
835
+ contents = [
836
+ Content(
837
+ role="user",
838
+ parts=[
839
+ Part.from_uri(file_uri=file_1.uri, mime_type=file_1.mime_type),
840
+ Part.from_uri(file_uri=file_2.uri, mime_type=file_2.mime_type),
841
+ ],
842
+ )
843
+ ]
844
+ model = "gemini-1.5-flash-001"
845
+ cache = client.caches.create(
846
+ model=model,
847
+ config=CreateCachedContentConfig(
848
+ display_name='Cached Contents',
849
+ system_instruction=(
850
+ 'You are an expert content analyzer, and your job is to answer '
851
+ 'the user\'s query based on the files you have access to.'
852
+ ),
853
+ contents=contents,
854
+ ttl="300s",
855
+ )
856
+ )
857
+
858
+ # Query with LangChain
859
+ llm = ChatGoogleGenerativeAI(
860
+ model=model,
861
+ cached_content=cache.name,
862
+ )
863
+ message = HumanMessage(content="Provide a summary of the key information across both files.")
864
+ llm.invoke([message])
865
+
742
866
  Tool calling:
743
867
  .. code-block:: python
744
868
 
@@ -842,7 +966,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
842
966
 
843
967
  .. code-block:: python
844
968
 
845
- 'The weather in this image appears to be sunny and pleasant. The sky is a bright blue with scattered white clouds, suggesting fair weather. The lush green grass and trees indicate a warm and possibly slightly breezy day. There are no signs of rain or storms. \n'
969
+ 'The weather in this image appears to be sunny and pleasant. The sky is a bright blue with scattered white clouds, suggesting fair weather. The lush green grass and trees indicate a warm and possibly slightly breezy day. There are no signs of rain or storms.'
846
970
 
847
971
  Token usage:
848
972
  .. code-block:: python
@@ -891,6 +1015,28 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
891
1015
  ``cachedContents/{cachedContent}``.
892
1016
  """
893
1017
 
1018
+ def __init__(self, **kwargs: Any) -> None:
1019
+ """Needed for arg validation."""
1020
+ # Get all valid field names, including aliases
1021
+ valid_fields = set()
1022
+ for field_name, field_info in self.model_fields.items():
1023
+ valid_fields.add(field_name)
1024
+ if hasattr(field_info, "alias") and field_info.alias is not None:
1025
+ valid_fields.add(field_info.alias)
1026
+
1027
+ # Check for unrecognized arguments
1028
+ for arg in kwargs:
1029
+ if arg not in valid_fields:
1030
+ suggestions = get_close_matches(arg, valid_fields, n=1)
1031
+ suggestion = (
1032
+ f" Did you mean: '{suggestions[0]}'?" if suggestions else ""
1033
+ )
1034
+ logger.warning(
1035
+ f"Unexpected argument '{arg}' "
1036
+ f"provided to ChatGoogleGenerativeAI.{suggestion}"
1037
+ )
1038
+ super().__init__(**kwargs)
1039
+
894
1040
  model_config = ConfigDict(
895
1041
  populate_by_name=True,
896
1042
  )
@@ -927,7 +1073,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
927
1073
  if self.top_k is not None and self.top_k <= 0:
928
1074
  raise ValueError("top_k must be positive")
929
1075
 
930
- if not self.model.startswith("models/"):
1076
+ if not any(
1077
+ self.model.startswith(prefix) for prefix in ("models/", "tunedModels/")
1078
+ ):
931
1079
  self.model = f"models/{self.model}"
932
1080
 
933
1081
  additional_headers = self.additional_headers or {}
@@ -964,12 +1112,17 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
964
1112
  # this check ensures that async client is only initialized
965
1113
  # within an asyncio event loop to avoid the error
966
1114
  if not self.async_client_running and _is_event_loop_running():
1115
+ # async clients don't support "rest" transport
1116
+ # https://github.com/googleapis/gapic-generator-python/issues/1962
1117
+ transport = self.transport
1118
+ if transport == "rest":
1119
+ transport = "grpc_asyncio"
967
1120
  self.async_client_running = genaix.build_generative_async_service(
968
1121
  credentials=self.credentials,
969
1122
  api_key=google_api_key,
970
1123
  client_info=get_client_info("ChatGoogleGenerativeAI"),
971
1124
  client_options=self.client_options,
972
- transport=self.transport,
1125
+ transport=transport,
973
1126
  )
974
1127
  return self.async_client_running
975
1128
 
@@ -1406,14 +1559,6 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1406
1559
  tools=[schema], first_tool_only=True
1407
1560
  )
1408
1561
  else:
1409
- global WARNED_STRUCTURED_OUTPUT_JSON_MODE
1410
- warnings.warn(
1411
- "ChatGoogleGenerativeAI.with_structured_output with dict schema has "
1412
- "changed recently to align with behavior of other LangChain chat "
1413
- "models. More context: "
1414
- "https://github.com/langchain-ai/langchain-google/pull/772"
1415
- )
1416
- WARNED_STRUCTURED_OUTPUT_JSON_MODE = True
1417
1562
  parser = JsonOutputKeyToolsParser(key_name=tool_name, first_tool_only=True)
1418
1563
  tool_choice = tool_name if self._supports_tool_choice else None
1419
1564
  try:
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
6
6
  from google.ai.generativelanguage_v1beta.types import (
7
7
  BatchEmbedContentsRequest,
8
8
  EmbedContentRequest,
9
+ EmbedContentResponse,
9
10
  )
10
11
  from langchain_core.embeddings import Embeddings
11
12
  from langchain_core.utils import secret_from_env
@@ -239,7 +240,8 @@ class GoogleGenerativeAIEmbeddings(BaseModel, Embeddings):
239
240
  title: Optional[str] = None,
240
241
  output_dimensionality: Optional[int] = None,
241
242
  ) -> List[float]:
242
- """Embed a text.
243
+ """Embed a text, using the non-batch endpoint:
244
+ https://ai.google.dev/api/rest/v1/models/embedContent#EmbedContentRequest
243
245
 
244
246
  Args:
245
247
  text: The text to embed.
@@ -247,15 +249,19 @@ class GoogleGenerativeAIEmbeddings(BaseModel, Embeddings):
247
249
  title: An optional title for the text.
248
250
  Only applicable when TaskType is RETRIEVAL_DOCUMENT.
249
251
  output_dimensionality: Optional reduced dimension for the output embedding.
250
- https://ai.google.dev/api/rest/v1/models/batchEmbedContents#EmbedContentRequest
251
252
 
252
253
  Returns:
253
254
  Embedding for the text.
254
255
  """
255
256
  task_type = self.task_type or "RETRIEVAL_QUERY"
256
- return self.embed_documents(
257
- [text],
258
- task_type=task_type,
259
- titles=[title] if title else None,
260
- output_dimensionality=output_dimensionality,
261
- )[0]
257
+ try:
258
+ request: EmbedContentRequest = self._prepare_request(
259
+ text=text,
260
+ task_type=task_type,
261
+ title=title,
262
+ output_dimensionality=output_dimensionality,
263
+ )
264
+ result: EmbedContentResponse = self.client.embed_content(request)
265
+ except Exception as e:
266
+ raise GoogleGenerativeAIError(f"Error embedding content: {e}") from e
267
+ return list(result.embedding.values)
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
4
+ from difflib import get_close_matches
3
5
  from typing import Any, Iterator, List, Optional
4
6
 
5
7
  from langchain_core.callbacks import (
@@ -17,6 +19,8 @@ from langchain_google_genai._common import (
17
19
  )
18
20
  from langchain_google_genai.chat_models import ChatGoogleGenerativeAI
19
21
 
22
+ logger = logging.getLogger(__name__)
23
+
20
24
 
21
25
  class GoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseLLM):
22
26
  """Google GenerativeAI models.
@@ -33,6 +37,28 @@ class GoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseLLM):
33
37
  populate_by_name=True,
34
38
  )
35
39
 
40
+ def __init__(self, **kwargs: Any) -> None:
41
+ """Needed for arg validation."""
42
+ # Get all valid field names, including aliases
43
+ valid_fields = set()
44
+ for field_name, field_info in self.model_fields.items():
45
+ valid_fields.add(field_name)
46
+ if hasattr(field_info, "alias") and field_info.alias is not None:
47
+ valid_fields.add(field_info.alias)
48
+
49
+ # Check for unrecognized arguments
50
+ for arg in kwargs:
51
+ if arg not in valid_fields:
52
+ suggestions = get_close_matches(arg, valid_fields, n=1)
53
+ suggestion = (
54
+ f" Did you mean: '{suggestions[0]}'?" if suggestions else ""
55
+ )
56
+ logger.warning(
57
+ f"Unexpected argument '{arg}' "
58
+ f"provided to GoogleGenerativeAI.{suggestion}"
59
+ )
60
+ super().__init__(**kwargs)
61
+
36
62
  @model_validator(mode="after")
37
63
  def validate_environment(self) -> Self:
38
64
  """Validates params and passes them to google-generativeai package."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-google-genai
3
- Version: 2.1.2
3
+ Version: 2.1.3
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
  Requires-Dist: filetype (>=1.2.0,<2.0.0)
15
15
  Requires-Dist: google-ai-generativelanguage (>=0.6.16,<0.7.0)
16
- Requires-Dist: langchain-core (>=0.3.49,<0.4.0)
16
+ Requires-Dist: langchain-core (>=0.3.52,<0.4.0)
17
17
  Requires-Dist: pydantic (>=2,<3)
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
@@ -1,16 +1,16 @@
1
1
  langchain_google_genai/__init__.py,sha256=IsTvA3UcECLDckt3zWxK6u-n3MEa5KeEQpqsS-Z8shM,2784
2
- langchain_google_genai/_common.py,sha256=RS_FN1k9BeeSRz8HuPyjh_lfDQPZKY6KdvK5EfVuxDw,5431
2
+ langchain_google_genai/_common.py,sha256=WDcPLFQhhrNHopaTngxV5uHEGahqISNPAkYDOrFaWOE,5431
3
3
  langchain_google_genai/_enums.py,sha256=Zj3BXXLlkm_UybegCi6fLsfFhriJCt_LAJvgatgPWQ0,252
4
4
  langchain_google_genai/_function_utils.py,sha256=lvHunWWsSU0RmAal4rVpgm2JucCnKUq21Uc0eqFY49c,19115
5
5
  langchain_google_genai/_genai_extension.py,sha256=81a4ly5ZHlqMf37uJfdB8K41qE6J5ujLnbUypIfFf2o,20775
6
6
  langchain_google_genai/_image_utils.py,sha256=tPrQyMvVmO8xkuow1SvA91omxUEv9ZUy1EMHNGjMAKY,5202
7
- langchain_google_genai/chat_models.py,sha256=PTWraSNiYBQJ-ev9R2OoWcu7cruBeS8JfRdUl2ydXZU,57944
8
- langchain_google_genai/embeddings.py,sha256=jQRWPXD9twXoVBlXJQG7Duz0fb8UC0kgRzzwAmW3Dic,10146
7
+ langchain_google_genai/chat_models.py,sha256=xjpI8LmEN6-45IUXHPzP0t_1vGAqQDmJEE_lpj9eHMI,63867
8
+ langchain_google_genai/embeddings.py,sha256=syN-GXcLAeuHEnF8Yqp2AQPD7rKEaR9l29jSLmt9dwM,10468
9
9
  langchain_google_genai/genai_aqa.py,sha256=qB6h3-BSXqe0YLR3eeVllYzmNKK6ofI6xJLdBahUVZo,4300
10
10
  langchain_google_genai/google_vector_store.py,sha256=4wvhIiOmc3Fo046FyafPmT9NBCLek-9bgluvuTfrbpQ,16148
11
- langchain_google_genai/llms.py,sha256=QNPitkORf86w8WQpTbjuPFCQFkB-qKRMW2phhRBwAEA,4318
11
+ langchain_google_genai/llms.py,sha256=ASjrEk2T_1hUXVNJlfPB8PKC4PbhPe00H3_UHunMc_Q,5334
12
12
  langchain_google_genai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- langchain_google_genai-2.1.2.dist-info/LICENSE,sha256=DppmdYJVSc1jd0aio6ptnMUn5tIHrdAhQ12SclEBfBg,1072
14
- langchain_google_genai-2.1.2.dist-info/METADATA,sha256=go98GAJH_sz8QSo18j1vrEw8guoNw_xOF_-iPCutwd4,4728
15
- langchain_google_genai-2.1.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
16
- langchain_google_genai-2.1.2.dist-info/RECORD,,
13
+ langchain_google_genai-2.1.3.dist-info/LICENSE,sha256=DppmdYJVSc1jd0aio6ptnMUn5tIHrdAhQ12SclEBfBg,1072
14
+ langchain_google_genai-2.1.3.dist-info/METADATA,sha256=k8VYBs6xHBSYiIcJJdfGoKYcPgltEEOiBXBvAQylz1k,4728
15
+ langchain_google_genai-2.1.3.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
16
+ langchain_google_genai-2.1.3.dist-info/RECORD,,