langchain-google-genai 2.1.1__py3-none-any.whl → 2.1.2__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.

Potentially problematic release.


This version of langchain-google-genai might be problematic. Click here for more details.

@@ -61,18 +61,22 @@ _ALLOWED_SCHEMA_FIELDS_SET = set(_ALLOWED_SCHEMA_FIELDS)
61
61
  _FunctionDeclarationLike = Union[
62
62
  BaseTool, Type[BaseModel], gapic.FunctionDeclaration, Callable, Dict[str, Any]
63
63
  ]
64
+ _GoogleSearchRetrievalLike = Union[
65
+ gapic.GoogleSearchRetrieval,
66
+ Dict[str, Any],
67
+ ]
64
68
 
65
69
 
66
70
  class _ToolDict(TypedDict):
67
71
  function_declarations: Sequence[_FunctionDeclarationLike]
72
+ google_search_retrieval: Optional[_GoogleSearchRetrievalLike]
68
73
 
69
74
 
70
75
  # Info: This means one tool=Sequence of FunctionDeclaration
71
76
  # The dict should be gapic.Tool like. {"function_declarations": [ { "name": ...}.
72
77
  # OpenAI like dict is not be accepted. {{'type': 'function', 'function': {'name': ...}
73
- _ToolsType = Union[
74
- gapic.Tool, _ToolDict, _FunctionDeclarationLike, Sequence[_FunctionDeclarationLike]
75
- ]
78
+ _ToolType = Union[gapic.Tool, _ToolDict, _FunctionDeclarationLike]
79
+ _ToolsType = Sequence[_ToolType]
76
80
 
77
81
 
78
82
  def _format_json_schema_to_gapic(schema: Dict[str, Any]) -> Dict[str, Any]:
@@ -122,7 +126,7 @@ def _format_dict_to_function_declaration(
122
126
 
123
127
  # Info: gapic.Tool means function_declarations and proto.Message.
124
128
  def convert_to_genai_function_declarations(
125
- tools: Sequence[_ToolsType],
129
+ tools: _ToolsType,
126
130
  ) -> gapic.Tool:
127
131
  if not isinstance(tools, collections.abc.Sequence):
128
132
  logger.warning(
@@ -132,24 +136,54 @@ def convert_to_genai_function_declarations(
132
136
  tools = [tools]
133
137
  gapic_tool = gapic.Tool()
134
138
  for tool in tools:
135
- if isinstance(tool, gapic.Tool):
136
- gapic_tool.function_declarations.extend(tool.function_declarations) # type: ignore[union-attr]
137
- elif isinstance(tool, dict) and "function_declarations" not in tool:
138
- fd = _format_to_gapic_function_declaration(tool)
139
- gapic_tool.function_declarations.append(fd)
139
+ if any(f in gapic_tool for f in ["google_search_retrieval"]):
140
+ raise ValueError(
141
+ "Providing multiple google_search_retrieval"
142
+ " or mixing with function_declarations is not supported"
143
+ )
144
+ if isinstance(tool, (gapic.Tool)):
145
+ rt: gapic.Tool = (
146
+ tool if isinstance(tool, gapic.Tool) else tool._raw_tool # type: ignore
147
+ )
148
+ if "google_search_retrieval" in rt:
149
+ gapic_tool.google_search_retrieval = rt.google_search_retrieval
150
+ if "function_declarations" in rt:
151
+ gapic_tool.function_declarations.extend(rt.function_declarations)
152
+ if "google_search" in rt:
153
+ gapic_tool.google_search = rt.google_search
140
154
  elif isinstance(tool, dict):
141
- function_declarations = cast(_ToolDict, tool)["function_declarations"]
142
- if not isinstance(function_declarations, collections.abc.Sequence):
143
- raise ValueError(
144
- "function_declarations should be a list"
145
- f"got '{type(function_declarations)}'"
146
- )
147
- if function_declarations:
148
- fds = [
149
- _format_to_gapic_function_declaration(fd)
150
- for fd in function_declarations
155
+ # not _ToolDictLike
156
+ if not any(
157
+ f in tool
158
+ for f in [
159
+ "function_declarations",
160
+ "google_search_retrieval",
151
161
  ]
152
- gapic_tool.function_declarations.extend(fds)
162
+ ):
163
+ fd = _format_to_gapic_function_declaration(tool) # type: ignore[arg-type]
164
+ gapic_tool.function_declarations.append(fd)
165
+ continue
166
+ # _ToolDictLike
167
+ tool = cast(_ToolDict, tool)
168
+ if "function_declarations" in tool:
169
+ function_declarations = tool["function_declarations"]
170
+ if not isinstance(
171
+ tool["function_declarations"], collections.abc.Sequence
172
+ ):
173
+ raise ValueError(
174
+ "function_declarations should be a list"
175
+ f"got '{type(function_declarations)}'"
176
+ )
177
+ if function_declarations:
178
+ fds = [
179
+ _format_to_gapic_function_declaration(fd)
180
+ for fd in function_declarations
181
+ ]
182
+ gapic_tool.function_declarations.extend(fds)
183
+ if "google_search_retrieval" in tool:
184
+ gapic_tool.google_search_retrieval = gapic.GoogleSearchRetrieval(
185
+ tool["google_search_retrieval"]
186
+ )
153
187
  else:
154
188
  fd = _format_to_gapic_function_declaration(tool) # type: ignore[arg-type]
155
189
  gapic_tool.function_declarations.append(fd)
@@ -32,6 +32,7 @@ from google.ai.generativelanguage_v1beta import (
32
32
  from google.ai.generativelanguage_v1beta.types import (
33
33
  Blob,
34
34
  Candidate,
35
+ CodeExecution,
35
36
  Content,
36
37
  FileData,
37
38
  FunctionCall,
@@ -45,9 +46,7 @@ from google.ai.generativelanguage_v1beta.types import (
45
46
  ToolConfig,
46
47
  VideoMetadata,
47
48
  )
48
- from google.ai.generativelanguage_v1beta.types import (
49
- Tool as GoogleTool,
50
- )
49
+ from google.ai.generativelanguage_v1beta.types import Tool as GoogleTool
51
50
  from langchain_core.callbacks.manager import (
52
51
  AsyncCallbackManagerForLLMRun,
53
52
  CallbackManagerForLLMRun,
@@ -72,7 +71,7 @@ from langchain_core.output_parsers.openai_tools import (
72
71
  parse_tool_calls,
73
72
  )
74
73
  from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
75
- from langchain_core.runnables import Runnable, RunnablePassthrough
74
+ from langchain_core.runnables import Runnable, RunnableConfig, RunnablePassthrough
76
75
  from langchain_core.tools import BaseTool
77
76
  from langchain_core.utils.function_calling import convert_to_openai_tool
78
77
  from pydantic import (
@@ -458,6 +457,41 @@ def _parse_response_candidate(
458
457
  elif text:
459
458
  raise Exception("Unexpected content type")
460
459
 
460
+ if hasattr(part, "executable_code") and part.executable_code is not None:
461
+ if part.executable_code.code and part.executable_code.language:
462
+ code_message = {
463
+ "type": "executable_code",
464
+ "executable_code": part.executable_code.code,
465
+ "language": part.executable_code.language,
466
+ }
467
+ if not content:
468
+ content = [code_message]
469
+ elif isinstance(content, str):
470
+ content = [content, code_message]
471
+ elif isinstance(content, list):
472
+ content.append(code_message)
473
+ else:
474
+ raise Exception("Unexpected content type")
475
+
476
+ if (
477
+ hasattr(part, "code_execution_result")
478
+ and part.code_execution_result is not None
479
+ ):
480
+ if part.code_execution_result.output:
481
+ execution_result = {
482
+ "type": "code_execution_result",
483
+ "code_execution_result": part.code_execution_result.output,
484
+ }
485
+
486
+ if not content:
487
+ content = [execution_result]
488
+ elif isinstance(content, str):
489
+ content = [content, execution_result]
490
+ elif isinstance(content, list):
491
+ content.append(execution_result)
492
+ else:
493
+ raise Exception("Unexpected content type")
494
+
461
495
  if part.inline_data.mime_type.startswith("image/"):
462
496
  image_format = part.inline_data.mime_type[6:]
463
497
  message = {
@@ -521,6 +555,16 @@ def _parse_response_candidate(
521
555
  )
522
556
  if content is None:
523
557
  content = ""
558
+ if any(isinstance(item, dict) and "executable_code" in item for item in content):
559
+ warnings.warn(
560
+ """
561
+ ⚠️ Warning: Output may vary each run.
562
+ - 'executable_code': Always present.
563
+ - 'execution_result' & 'image_url': May be absent for some queries.
564
+
565
+ Validate before using in production.
566
+ """
567
+ )
524
568
 
525
569
  if streaming:
526
570
  return AIMessageChunk(
@@ -575,6 +619,8 @@ def _response_to_result(
575
619
  generation_info = {}
576
620
  if candidate.finish_reason:
577
621
  generation_info["finish_reason"] = candidate.finish_reason.name
622
+ # Add model_name in last chunk
623
+ generation_info["model_name"] = response.model_version
578
624
  generation_info["safety_ratings"] = [
579
625
  proto.Message.to_dict(safety_rating, use_integers_for_enums=False)
580
626
  for safety_rating in candidate.safety_ratings
@@ -736,6 +782,16 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
736
782
  'args': {'location': 'New York City, NY'},
737
783
  'id': '634582de-5186-4e4b-968b-f192f0a93678'}]
738
784
 
785
+ Use Search with Gemini 2:
786
+ .. code-block:: python
787
+
788
+ from google.ai.generativelanguage_v1beta.types import Tool as GenAITool
789
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash-exp")
790
+ resp = llm.invoke(
791
+ "When is the next total solar eclipse in US?",
792
+ tools=[GenAITool(google_search={})],
793
+ )
794
+
739
795
  Structured output:
740
796
  .. code-block:: python
741
797
 
@@ -847,6 +903,14 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
847
903
  def _llm_type(self) -> str:
848
904
  return "chat-google-generative-ai"
849
905
 
906
+ @property
907
+ def _supports_code_execution(self) -> bool:
908
+ return (
909
+ "gemini-1.5-pro" in self.model
910
+ or "gemini-1.5-flash" in self.model
911
+ or "gemini-2" in self.model
912
+ )
913
+
850
914
  @classmethod
851
915
  def is_lc_serializable(self) -> bool:
852
916
  return True
@@ -921,6 +985,42 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
921
985
  "response_modalities": self.response_modalities,
922
986
  }
923
987
 
988
+ def invoke(
989
+ self,
990
+ input: LanguageModelInput,
991
+ config: Optional[RunnableConfig] = None,
992
+ *,
993
+ code_execution: Optional[bool] = None,
994
+ stop: Optional[list[str]] = None,
995
+ **kwargs: Any,
996
+ ) -> BaseMessage:
997
+ """
998
+ Enable code execution. Supported on: gemini-1.5-pro, gemini-1.5-flash,
999
+ gemini-2.0-flash, and gemini-2.0-pro. When enabled, the model can execute
1000
+ code to solve problems.
1001
+ """
1002
+
1003
+ """Override invoke to add code_execution parameter."""
1004
+
1005
+ if code_execution is not None:
1006
+ if not self._supports_code_execution:
1007
+ raise ValueError(
1008
+ f"Code execution is only supported on Gemini 1.5 Pro, \
1009
+ Gemini 1.5 Flash, "
1010
+ f"Gemini 2.0 Flash, and Gemini 2.0 Pro models. \
1011
+ Current model: {self.model}"
1012
+ )
1013
+ if "tools" not in kwargs:
1014
+ code_execution_tool = GoogleTool(code_execution=CodeExecution())
1015
+ kwargs["tools"] = [code_execution_tool]
1016
+
1017
+ else:
1018
+ raise ValueError(
1019
+ "Tools are already defined." "code_execution tool can't be defined"
1020
+ )
1021
+
1022
+ return super().invoke(input, config, stop=stop, **kwargs)
1023
+
924
1024
  def _get_ls_params(
925
1025
  self, stop: Optional[List[str]] = None, **kwargs: Any
926
1026
  ) -> LangSmithParams:
@@ -1199,8 +1299,12 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1199
1299
  "Must specify at most one of tool_choice and tool_config, received "
1200
1300
  f"both:\n\n{tool_choice=}\n\n{tool_config=}"
1201
1301
  )
1302
+
1202
1303
  formatted_tools = None
1203
- if tools:
1304
+ code_execution_tool = GoogleTool(code_execution=CodeExecution())
1305
+ if tools == [code_execution_tool]:
1306
+ formatted_tools = tools
1307
+ elif tools:
1204
1308
  formatted_tools = [convert_to_genai_function_declarations(tools)]
1205
1309
  elif functions:
1206
1310
  formatted_tools = [convert_to_genai_function_declarations(functions)]
@@ -1226,9 +1330,20 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1226
1330
  f"be specified if 'tools' is specified."
1227
1331
  )
1228
1332
  raise ValueError(msg)
1229
- all_names = [
1230
- f.name for t in formatted_tools for f in t.function_declarations
1231
- ]
1333
+ all_names: List[str] = []
1334
+ for t in formatted_tools:
1335
+ if hasattr(t, "function_declarations"):
1336
+ t_with_declarations = cast(Any, t)
1337
+ all_names.extend(
1338
+ f.name for f in t_with_declarations.function_declarations
1339
+ )
1340
+ elif isinstance(t, GoogleTool) and hasattr(t, "code_execution"):
1341
+ continue
1342
+ else:
1343
+ raise TypeError(
1344
+ f"Tool {t} doesn't have function_declarations attribute"
1345
+ )
1346
+
1232
1347
  tool_config = _tool_choice_to_tool_config(tool_choice, all_names)
1233
1348
 
1234
1349
  formatted_tool_config = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-google-genai
3
- Version: 2.1.1
3
+ Version: 2.1.2
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.47,<0.4.0)
16
+ Requires-Dist: langchain-core (>=0.3.49,<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
@@ -98,7 +98,7 @@ meow_str = response.content[1]
98
98
 
99
99
  #### Multimodal Outputs in Chains
100
100
 
101
- '''
101
+ ```
102
102
  from langchain_core.runnables import RunnablePassthrough
103
103
  from langchain_core.prompts import ChatPromptTemplate
104
104
 
@@ -114,7 +114,7 @@ prompt = ChatPromptTemplate(
114
114
  )
115
115
  chain = {"animal": RunnablePassthrough()} | prompt | llm
116
116
  res = chain.invoke("cat")
117
- '''
117
+ ```
118
118
 
119
119
  ## Embeddings
120
120
 
@@ -1,16 +1,16 @@
1
1
  langchain_google_genai/__init__.py,sha256=IsTvA3UcECLDckt3zWxK6u-n3MEa5KeEQpqsS-Z8shM,2784
2
2
  langchain_google_genai/_common.py,sha256=RS_FN1k9BeeSRz8HuPyjh_lfDQPZKY6KdvK5EfVuxDw,5431
3
3
  langchain_google_genai/_enums.py,sha256=Zj3BXXLlkm_UybegCi6fLsfFhriJCt_LAJvgatgPWQ0,252
4
- langchain_google_genai/_function_utils.py,sha256=FPZ4CxI4iTFco_W6oWNCNV_lgNbCLRbj7Gf-D0zeTCY,17736
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=ZFjhFz3a4t-6CEYGSYujYrTf29QEvKsIHeKZu8nJftI,53404
7
+ langchain_google_genai/chat_models.py,sha256=PTWraSNiYBQJ-ev9R2OoWcu7cruBeS8JfRdUl2ydXZU,57944
8
8
  langchain_google_genai/embeddings.py,sha256=jQRWPXD9twXoVBlXJQG7Duz0fb8UC0kgRzzwAmW3Dic,10146
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
11
  langchain_google_genai/llms.py,sha256=QNPitkORf86w8WQpTbjuPFCQFkB-qKRMW2phhRBwAEA,4318
12
12
  langchain_google_genai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- langchain_google_genai-2.1.1.dist-info/LICENSE,sha256=DppmdYJVSc1jd0aio6ptnMUn5tIHrdAhQ12SclEBfBg,1072
14
- langchain_google_genai-2.1.1.dist-info/METADATA,sha256=600J_hgujuZPiW-FliQBH9SbAeSw5Bb3tAyZzG2NES8,4728
15
- langchain_google_genai-2.1.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
16
- langchain_google_genai-2.1.1.dist-info/RECORD,,
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,,