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

@@ -10,30 +10,24 @@ import time
10
10
  import uuid
11
11
  import warnings
12
12
  import wave
13
+ from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
13
14
  from difflib import get_close_matches
14
15
  from operator import itemgetter
15
16
  from typing import (
16
17
  Any,
17
- AsyncIterator,
18
18
  Callable,
19
19
  Dict,
20
- Iterator,
21
20
  List,
22
21
  Literal,
23
- Mapping,
24
22
  Optional,
25
- Sequence,
26
23
  Tuple,
27
24
  Type,
28
25
  Union,
29
26
  cast,
30
27
  )
31
28
 
32
- import filetype # type: ignore[import]
33
- import google.api_core
34
-
35
- # TODO: remove ignore once the Google package is published with types
36
- import proto # type: ignore[import]
29
+ import filetype # type: ignore[import-untyped]
30
+ import proto # type: ignore[import-untyped]
37
31
  from google.ai.generativelanguage_v1beta import (
38
32
  GenerativeServiceAsyncClient as v1betaGenerativeServiceAsyncClient,
39
33
  )
@@ -57,12 +51,19 @@ from google.ai.generativelanguage_v1beta.types import (
57
51
  VideoMetadata,
58
52
  )
59
53
  from google.ai.generativelanguage_v1beta.types import Tool as GoogleTool
54
+ from google.api_core.exceptions import (
55
+ FailedPrecondition,
56
+ GoogleAPIError,
57
+ InvalidArgument,
58
+ ResourceExhausted,
59
+ ServiceUnavailable,
60
+ )
60
61
  from langchain_core.callbacks.manager import (
61
62
  AsyncCallbackManagerForLLMRun,
62
63
  CallbackManagerForLLMRun,
63
64
  )
64
- from langchain_core.language_models import LanguageModelInput
65
- from langchain_core.language_models.chat_models import BaseChatModel, LangSmithParams
65
+ from langchain_core.language_models import LangSmithParams, LanguageModelInput
66
+ from langchain_core.language_models.chat_models import BaseChatModel
66
67
  from langchain_core.messages import (
67
68
  AIMessage,
68
69
  AIMessageChunk,
@@ -139,12 +140,11 @@ _FunctionDeclarationType = Union[
139
140
 
140
141
 
141
142
  class ChatGoogleGenerativeAIError(GoogleGenerativeAIError):
142
- """
143
- Custom exception class for errors associated with the `Google GenAI` API.
143
+ """Custom exception class for errors associated with the `Google GenAI` API.
144
144
 
145
- This exception is raised when there are specific issues related to the
146
- Google genai API usage in the ChatGoogleGenerativeAI class, such as unsupported
147
- message types or roles.
145
+ This exception is raised when there are specific issues related to the Google genai
146
+ API usage in the ChatGoogleGenerativeAI class, such as unsupported message types or
147
+ roles.
148
148
  """
149
149
 
150
150
 
@@ -154,12 +154,11 @@ def _create_retry_decorator(
154
154
  wait_exponential_min: float = 1.0,
155
155
  wait_exponential_max: float = 60.0,
156
156
  ) -> Callable[[Any], Any]:
157
- """
158
- Creates and returns a preconfigured tenacity retry decorator.
157
+ """Creates and returns a preconfigured tenacity retry decorator.
159
158
 
160
- The retry decorator is configured to handle specific Google API exceptions
161
- such as ResourceExhausted and ServiceUnavailable. It uses an exponential
162
- backoff strategy for retries.
159
+ The retry decorator is configured to handle specific Google API exceptions such as
160
+ ResourceExhausted and ServiceUnavailable. It uses an exponential backoff strategy
161
+ for retries.
163
162
 
164
163
  Returns:
165
164
  Callable[[Any], Any]: A retry decorator configured for handling specific
@@ -174,21 +173,20 @@ def _create_retry_decorator(
174
173
  max=wait_exponential_max,
175
174
  ),
176
175
  retry=(
177
- retry_if_exception_type(google.api_core.exceptions.ResourceExhausted)
178
- | retry_if_exception_type(google.api_core.exceptions.ServiceUnavailable)
179
- | retry_if_exception_type(google.api_core.exceptions.GoogleAPIError)
176
+ retry_if_exception_type(ResourceExhausted)
177
+ | retry_if_exception_type(ServiceUnavailable)
178
+ | retry_if_exception_type(GoogleAPIError)
180
179
  ),
181
180
  before_sleep=before_sleep_log(logger, logging.WARNING),
182
181
  )
183
182
 
184
183
 
185
184
  def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
186
- """
187
- Executes a chat generation method with retry logic using tenacity.
185
+ """Executes a chat generation method with retry logic using tenacity.
188
186
 
189
- This function is a wrapper that applies a retry mechanism to a provided
190
- chat generation function. It is useful for handling intermittent issues
191
- like network errors or temporary service unavailability.
187
+ This function is a wrapper that applies a retry mechanism to a provided chat
188
+ generation function. It is useful for handling intermittent issues like network
189
+ errors or temporary service unavailability.
192
190
 
193
191
  Args:
194
192
  generation_method (Callable): The chat generation method to be executed.
@@ -208,7 +206,7 @@ def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
208
206
  def _chat_with_retry(**kwargs: Any) -> Any:
209
207
  try:
210
208
  return generation_method(**kwargs)
211
- except google.api_core.exceptions.FailedPrecondition as exc:
209
+ except FailedPrecondition as exc:
212
210
  if "location is not supported" in exc.message:
213
211
  error_msg = (
214
212
  "Your location is not supported by google-generativeai "
@@ -217,19 +215,18 @@ def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
217
215
  )
218
216
  raise ValueError(error_msg)
219
217
 
220
- except google.api_core.exceptions.InvalidArgument as e:
221
- raise ChatGoogleGenerativeAIError(
222
- f"Invalid argument provided to Gemini: {e}"
223
- ) from e
224
- except google.api_core.exceptions.ResourceExhausted as e:
218
+ except InvalidArgument as e:
219
+ msg = f"Invalid argument provided to Gemini: {e}"
220
+ raise ChatGoogleGenerativeAIError(msg) from e
221
+ except ResourceExhausted as e:
225
222
  # Handle quota-exceeded error with recommended retry delay
226
- if hasattr(e, "retry_after") and e.retry_after < kwargs.get(
223
+ if hasattr(e, "retry_after") and getattr(e, "retry_after", 0) < kwargs.get(
227
224
  "wait_exponential_max", 60.0
228
225
  ):
229
- time.sleep(e.retry_after)
230
- raise e
231
- except Exception as e:
232
- raise e
226
+ time.sleep(getattr(e, "retry_after"))
227
+ raise
228
+ except Exception:
229
+ raise
233
230
 
234
231
  params = (
235
232
  {k: v for k, v in kwargs.items() if k in _allowed_params_prediction_service}
@@ -242,12 +239,11 @@ def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
242
239
 
243
240
 
244
241
  async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
245
- """
246
- Executes a chat generation method with retry logic using tenacity.
242
+ """Executes a chat generation method with retry logic using tenacity.
247
243
 
248
- This function is a wrapper that applies a retry mechanism to a provided
249
- chat generation function. It is useful for handling intermittent issues
250
- like network errors or temporary service unavailability.
244
+ This function is a wrapper that applies a retry mechanism to a provided chat
245
+ generation function. It is useful for handling intermittent issues like network
246
+ errors or temporary service unavailability.
251
247
 
252
248
  Args:
253
249
  generation_method (Callable): The chat generation method to be executed.
@@ -256,8 +252,12 @@ async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
256
252
  Returns:
257
253
  Any: The result from the chat generation method.
258
254
  """
259
- retry_decorator = _create_retry_decorator()
260
- from google.api_core.exceptions import InvalidArgument # type: ignore
255
+ retry_decorator = _create_retry_decorator(
256
+ max_retries=kwargs.get("max_retries", 6),
257
+ wait_exponential_multiplier=kwargs.get("wait_exponential_multiplier", 2.0),
258
+ wait_exponential_min=kwargs.get("wait_exponential_min", 1.0),
259
+ wait_exponential_max=kwargs.get("wait_exponential_max", 60.0),
260
+ )
261
261
 
262
262
  @retry_decorator
263
263
  async def _achat_with_retry(**kwargs: Any) -> Any:
@@ -265,11 +265,17 @@ async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
265
265
  return await generation_method(**kwargs)
266
266
  except InvalidArgument as e:
267
267
  # Do not retry for these errors.
268
- raise ChatGoogleGenerativeAIError(
269
- f"Invalid argument provided to Gemini: {e}"
270
- ) from e
271
- except Exception as e:
272
- raise e
268
+ msg = f"Invalid argument provided to Gemini: {e}"
269
+ raise ChatGoogleGenerativeAIError(msg) from e
270
+ except ResourceExhausted as e:
271
+ # Handle quota-exceeded error with recommended retry delay
272
+ if hasattr(e, "retry_after") and getattr(e, "retry_after", 0) < kwargs.get(
273
+ "wait_exponential_max", 60.0
274
+ ):
275
+ time.sleep(getattr(e, "retry_after"))
276
+ raise
277
+ except Exception:
278
+ raise
273
279
 
274
280
  params = (
275
281
  {k: v for k, v in kwargs.items() if k in _allowed_params_prediction_service}
@@ -322,12 +328,13 @@ def _convert_to_parts(
322
328
  elif part["source_type"] == "base64":
323
329
  bytes_ = base64.b64decode(part["data"])
324
330
  else:
325
- raise ValueError("source_type must be url or base64.")
331
+ msg = "source_type must be url or base64."
332
+ raise ValueError(msg)
326
333
  inline_data: dict = {"data": bytes_}
327
334
  if "mime_type" in part:
328
335
  inline_data["mime_type"] = part["mime_type"]
329
336
  else:
330
- source = cast(str, part.get("url") or part.get("data"))
337
+ source = cast("str", part.get("url") or part.get("data"))
331
338
  mime_type, _ = mimetypes.guess_type(source)
332
339
  if not mime_type:
333
340
  kind = filetype.guess(bytes_)
@@ -340,16 +347,16 @@ def _convert_to_parts(
340
347
  img_url = part["image_url"]
341
348
  if isinstance(img_url, dict):
342
349
  if "url" not in img_url:
343
- raise ValueError(
344
- f"Unrecognized message image format: {img_url}"
345
- )
350
+ msg = f"Unrecognized message image format: {img_url}"
351
+ raise ValueError(msg)
346
352
  img_url = img_url["url"]
347
353
  parts.append(image_loader.load_part(img_url))
348
354
  # Handle media type like LangChain.js
349
355
  # https://github.com/langchain-ai/langchainjs/blob/e536593e2585f1dd7b0afc187de4d07cb40689ba/libs/langchain-google-common/src/utils/gemini.ts#L93-L106
350
356
  elif part["type"] == "media":
351
357
  if "mime_type" not in part:
352
- raise ValueError(f"Missing mime_type in media part: {part}")
358
+ msg = f"Missing mime_type in media part: {part}"
359
+ raise ValueError(msg)
353
360
  mime_type = part["mime_type"]
354
361
  media_part = Part()
355
362
 
@@ -362,19 +369,19 @@ def _convert_to_parts(
362
369
  file_uri=part["file_uri"], mime_type=mime_type
363
370
  )
364
371
  else:
365
- raise ValueError(
366
- f"Media part must have either data or file_uri: {part}"
367
- )
372
+ msg = f"Media part must have either data or file_uri: {part}"
373
+ raise ValueError(msg)
368
374
  if "video_metadata" in part:
369
375
  metadata = VideoMetadata(part["video_metadata"])
370
376
  media_part.video_metadata = metadata
371
377
  parts.append(media_part)
372
378
  elif part["type"] == "executable_code":
373
379
  if "executable_code" not in part or "language" not in part:
374
- raise ValueError(
380
+ msg = (
375
381
  "Executable code part must have 'code' and 'language' "
376
382
  f"keys, got {part}"
377
383
  )
384
+ raise ValueError(msg)
378
385
  executable_code_part = Part(
379
386
  executable_code=ExecutableCode(
380
387
  language=part["language"], code=part["executable_code"]
@@ -383,10 +390,11 @@ def _convert_to_parts(
383
390
  parts.append(executable_code_part)
384
391
  elif part["type"] == "code_execution_result":
385
392
  if "code_execution_result" not in part:
386
- raise ValueError(
393
+ msg = (
387
394
  "Code execution result part must have "
388
395
  f"'code_execution_result', got {part}"
389
396
  )
397
+ raise ValueError(msg)
390
398
  if "outcome" in part:
391
399
  outcome = part["outcome"]
392
400
  else:
@@ -401,10 +409,11 @@ def _convert_to_parts(
401
409
  elif part["type"] == "thinking":
402
410
  parts.append(Part(text=part["thinking"], thought=True))
403
411
  else:
404
- raise ValueError(
412
+ msg = (
405
413
  f"Unrecognized message part type: {part['type']}. Only text, "
406
414
  f"image_url, and media types are supported."
407
415
  )
416
+ raise ValueError(msg)
408
417
  else:
409
418
  # Yolo
410
419
  logger.warning(
@@ -414,9 +423,8 @@ def _convert_to_parts(
414
423
  else:
415
424
  # TODO: Maybe some of Google's native stuff
416
425
  # would hit this branch.
417
- raise ChatGoogleGenerativeAIError(
418
- "Gemini only supports text and inline_data parts."
419
- )
426
+ msg = "Gemini only supports text and inline_data parts."
427
+ raise ChatGoogleGenerativeAIError(msg)
420
428
  return parts
421
429
 
422
430
 
@@ -463,14 +471,15 @@ def _convert_tool_message_to_parts(
463
471
  def _get_ai_message_tool_messages_parts(
464
472
  tool_messages: Sequence[ToolMessage], ai_message: AIMessage
465
473
  ) -> list[Part]:
466
- """
467
- Finds relevant tool messages for the AI message and converts them to a single
468
- list of Parts.
474
+ """Conversion.
475
+
476
+ Finds relevant tool messages for the AI message and converts them to a single list
477
+ of Parts.
469
478
  """
470
479
  # We are interested only in the tool messages that are part of the AI message
471
480
  tool_calls_ids = {tool_call["id"]: tool_call for tool_call in ai_message.tool_calls}
472
481
  parts = []
473
- for i, message in enumerate(tool_messages):
482
+ for _i, message in enumerate(tool_messages):
474
483
  if not tool_calls_ids:
475
484
  break
476
485
  if message.tool_call_id in tool_calls_ids:
@@ -514,7 +523,7 @@ def _parse_chat_history(
514
523
  else:
515
524
  pass
516
525
  continue
517
- elif isinstance(message, AIMessage):
526
+ if isinstance(message, AIMessage):
518
527
  role = "model"
519
528
  if message.tool_calls:
520
529
  ai_message_parts = []
@@ -532,7 +541,7 @@ def _parse_chat_history(
532
541
  messages.append(Content(role=role, parts=ai_message_parts))
533
542
  messages.append(Content(role="user", parts=tool_messages_parts))
534
543
  continue
535
- elif raw_function_call := message.additional_kwargs.get("function_call"):
544
+ if raw_function_call := message.additional_kwargs.get("function_call"):
536
545
  function_call = FunctionCall(
537
546
  {
538
547
  "name": raw_function_call["name"],
@@ -546,15 +555,14 @@ def _parse_chat_history(
546
555
  role = "user"
547
556
  parts = _convert_to_parts(message.content)
548
557
  if i == 1 and convert_system_message_to_human and system_instruction:
549
- parts = [p for p in system_instruction.parts] + parts
558
+ parts = list(system_instruction.parts) + parts
550
559
  system_instruction = None
551
560
  elif isinstance(message, FunctionMessage):
552
561
  role = "user"
553
562
  parts = _convert_tool_message_to_parts(message)
554
563
  else:
555
- raise ValueError(
556
- f"Unexpected message with type {type(message)} at the position {i}."
557
- )
564
+ msg = f"Unexpected message with type {type(message)} at the position {i}."
565
+ raise ValueError(msg)
558
566
 
559
567
  messages.append(Content(role=role, parts=parts))
560
568
  return system_instruction, messages
@@ -567,17 +575,17 @@ def _append_to_content(
567
575
  """Appends a new item to the content, handling different initial content types."""
568
576
  if current_content is None and isinstance(new_item, str):
569
577
  return new_item
570
- elif current_content is None:
578
+ if current_content is None:
571
579
  return [new_item]
572
- elif isinstance(current_content, str):
580
+ if isinstance(current_content, str):
573
581
  return [current_content, new_item]
574
- elif isinstance(current_content, list):
582
+ if isinstance(current_content, list):
575
583
  current_content.append(new_item)
576
584
  return current_content
577
- else:
578
- # This case should ideally not be reached with proper type checking,
579
- # but it catches any unexpected types that might slip through.
580
- raise TypeError(f"Unexpected content type: {type(current_content)}")
585
+ # This case should ideally not be reached with proper type checking,
586
+ # but it catches any unexpected types that might slip through.
587
+ msg = f"Unexpected content type: {type(current_content)}"
588
+ raise TypeError(msg)
581
589
 
582
590
 
583
591
  def _parse_response_candidate(
@@ -621,14 +629,13 @@ def _parse_response_candidate(
621
629
  if (
622
630
  hasattr(part, "code_execution_result")
623
631
  and part.code_execution_result is not None
624
- ):
625
- if part.code_execution_result.output:
626
- execution_result = {
627
- "type": "code_execution_result",
628
- "code_execution_result": part.code_execution_result.output,
629
- "outcome": part.code_execution_result.outcome,
630
- }
631
- content = _append_to_content(content, execution_result)
632
+ ) and part.code_execution_result.output:
633
+ execution_result = {
634
+ "type": "code_execution_result",
635
+ "code_execution_result": part.code_execution_result.output,
636
+ "outcome": part.code_execution_result.outcome,
637
+ }
638
+ content = _append_to_content(content, execution_result)
632
639
 
633
640
  if part.inline_data.mime_type.startswith("audio/"):
634
641
  buffer = io.BytesIO()
@@ -708,9 +715,9 @@ def _parse_response_candidate(
708
715
  ):
709
716
  warnings.warn(
710
717
  """
711
- ⚠️ Warning: Output may vary each run.
712
- - 'executable_code': Always present.
713
- - 'execution_result' & 'image_url': May be absent for some queries.
718
+ Warning: Output may vary each run.
719
+ - 'executable_code': Always present.
720
+ - 'execution_result' & 'image_url': May be absent for some queries.
714
721
 
715
722
  Validate before using in production.
716
723
  """
@@ -718,13 +725,13 @@ def _parse_response_candidate(
718
725
 
719
726
  if streaming:
720
727
  return AIMessageChunk(
721
- content=cast(Union[str, List[Union[str, Dict[Any, Any]]]], content),
728
+ content=content,
722
729
  additional_kwargs=additional_kwargs,
723
730
  tool_call_chunks=tool_call_chunks,
724
731
  )
725
732
 
726
733
  return AIMessage(
727
- content=cast(Union[str, List[Union[str, Dict[Any, Any]]]], content),
734
+ content=content,
728
735
  additional_kwargs=additional_kwargs,
729
736
  tool_calls=tool_calls,
730
737
  invalid_tool_calls=invalid_tool_calls,
@@ -805,7 +812,7 @@ def _response_to_result(
805
812
  if stream:
806
813
  generations.append(
807
814
  ChatGenerationChunk(
808
- message=cast(AIMessageChunk, message),
815
+ message=cast("AIMessageChunk", message),
809
816
  generation_info=generation_info,
810
817
  )
811
818
  )
@@ -849,13 +856,14 @@ def _is_event_loop_running() -> bool:
849
856
 
850
857
 
851
858
  class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
852
- """`Google AI` chat models integration.
859
+ r"""`Google AI` chat models integration.
853
860
 
854
861
  Instantiation:
855
862
  To use, you must have either:
856
863
 
857
864
  1. The ``GOOGLE_API_KEY`` environment variable set with your API key, or
858
- 2. Pass your API key using the ``google_api_key`` kwarg to the ChatGoogleGenerativeAI constructor.
865
+ 2. Pass your API key using the ``google_api_key`` kwarg to the
866
+ ChatGoogleGenerativeAI constructor.
859
867
 
860
868
  .. code-block:: python
861
869
 
@@ -877,9 +885,38 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
877
885
 
878
886
  AIMessage(
879
887
  content="J'adore programmer. \\n",
880
- response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, '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}]},
881
- id='run-56cecc34-2e54-4b52-a974-337e47008ad2-0',
882
- usage_metadata={'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23}
888
+ response_metadata={
889
+ "prompt_feedback": {"block_reason": 0, "safety_ratings": []},
890
+ "finish_reason": "STOP",
891
+ "safety_ratings": [
892
+ {
893
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
894
+ "probability": "NEGLIGIBLE",
895
+ "blocked": False,
896
+ },
897
+ {
898
+ "category": "HARM_CATEGORY_HATE_SPEECH",
899
+ "probability": "NEGLIGIBLE",
900
+ "blocked": False,
901
+ },
902
+ {
903
+ "category": "HARM_CATEGORY_HARASSMENT",
904
+ "probability": "NEGLIGIBLE",
905
+ "blocked": False,
906
+ },
907
+ {
908
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
909
+ "probability": "NEGLIGIBLE",
910
+ "blocked": False,
911
+ },
912
+ ],
913
+ },
914
+ id="run-56cecc34-2e54-4b52-a974-337e47008ad2-0",
915
+ usage_metadata={
916
+ "input_tokens": 18,
917
+ "output_tokens": 5,
918
+ "total_tokens": 23,
919
+ },
883
920
  )
884
921
 
885
922
  Stream:
@@ -890,8 +927,50 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
890
927
 
891
928
  .. code-block:: python
892
929
 
893
- 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})
894
- 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})
930
+ AIMessageChunk(
931
+ content="J",
932
+ response_metadata={"finish_reason": "STOP", "safety_ratings": []},
933
+ id="run-e905f4f4-58cb-4a10-a960-448a2bb649e3",
934
+ usage_metadata={
935
+ "input_tokens": 18,
936
+ "output_tokens": 1,
937
+ "total_tokens": 19,
938
+ },
939
+ )
940
+ AIMessageChunk(
941
+ content="'adore programmer. \\n",
942
+ response_metadata={
943
+ "finish_reason": "STOP",
944
+ "safety_ratings": [
945
+ {
946
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
947
+ "probability": "NEGLIGIBLE",
948
+ "blocked": False,
949
+ },
950
+ {
951
+ "category": "HARM_CATEGORY_HATE_SPEECH",
952
+ "probability": "NEGLIGIBLE",
953
+ "blocked": False,
954
+ },
955
+ {
956
+ "category": "HARM_CATEGORY_HARASSMENT",
957
+ "probability": "NEGLIGIBLE",
958
+ "blocked": False,
959
+ },
960
+ {
961
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
962
+ "probability": "NEGLIGIBLE",
963
+ "blocked": False,
964
+ },
965
+ ],
966
+ },
967
+ id="run-e905f4f4-58cb-4a10-a960-448a2bb649e3",
968
+ usage_metadata={
969
+ "input_tokens": 18,
970
+ "output_tokens": 5,
971
+ "total_tokens": 23,
972
+ },
973
+ )
895
974
 
896
975
  .. code-block:: python
897
976
 
@@ -905,9 +984,37 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
905
984
 
906
985
  AIMessageChunk(
907
986
  content="J'adore programmer. \\n",
908
- response_metadata={'finish_reason': 'STOPSTOP', '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}]},
909
- id='run-3ce13a42-cd30-4ad7-a684-f1f0b37cdeec',
910
- usage_metadata={'input_tokens': 36, 'output_tokens': 6, 'total_tokens': 42}
987
+ response_metadata={
988
+ "finish_reason": "STOPSTOP",
989
+ "safety_ratings": [
990
+ {
991
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
992
+ "probability": "NEGLIGIBLE",
993
+ "blocked": False,
994
+ },
995
+ {
996
+ "category": "HARM_CATEGORY_HATE_SPEECH",
997
+ "probability": "NEGLIGIBLE",
998
+ "blocked": False,
999
+ },
1000
+ {
1001
+ "category": "HARM_CATEGORY_HARASSMENT",
1002
+ "probability": "NEGLIGIBLE",
1003
+ "blocked": False,
1004
+ },
1005
+ {
1006
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
1007
+ "probability": "NEGLIGIBLE",
1008
+ "blocked": False,
1009
+ },
1010
+ ],
1011
+ },
1012
+ id="run-3ce13a42-cd30-4ad7-a684-f1f0b37cdeec",
1013
+ usage_metadata={
1014
+ "input_tokens": 36,
1015
+ "output_tokens": 6,
1016
+ "total_tokens": 42,
1017
+ },
911
1018
  )
912
1019
 
913
1020
  Async:
@@ -922,9 +1029,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
922
1029
  # await llm.abatch([messages])
923
1030
 
924
1031
  Context Caching:
925
- Context caching allows you to store and reuse content (e.g., PDFs, images) for faster processing.
926
- The ``cached_content`` parameter accepts a cache name created via the Google Generative AI API.
927
- Below are two examples: caching a single file directly and caching multiple files using ``Part``.
1032
+ Context caching allows you to store and reuse content (e.g., PDFs, images) for
1033
+ faster processing. The ``cached_content`` parameter accepts a cache name created
1034
+ via the Google Generative AI API. Below are two examples: caching a single file
1035
+ directly and caching multiple files using ``Part``.
928
1036
 
929
1037
  Single File Example:
930
1038
  This caches a single file and queries it.
@@ -941,23 +1049,23 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
941
1049
 
942
1050
  # Upload file
943
1051
  file = client.files.upload(file="./example_file")
944
- while file.state.name == 'PROCESSING':
1052
+ while file.state.name == "PROCESSING":
945
1053
  time.sleep(2)
946
1054
  file = client.files.get(name=file.name)
947
1055
 
948
1056
  # Create cache
949
- model = 'models/gemini-1.5-flash-latest'
1057
+ model = "models/gemini-1.5-flash-latest"
950
1058
  cache = client.caches.create(
951
1059
  model=model,
952
1060
  config=types.CreateCachedContentConfig(
953
- display_name='Cached Content',
1061
+ display_name="Cached Content",
954
1062
  system_instruction=(
955
- 'You are an expert content analyzer, and your job is to answer '
956
- 'the user\'s query based on the file you have access to.'
1063
+ "You are an expert content analyzer, and your job is to answer "
1064
+ "the user's query based on the file you have access to."
957
1065
  ),
958
1066
  contents=[file],
959
1067
  ttl="300s",
960
- )
1068
+ ),
961
1069
  )
962
1070
 
963
1071
  # Query with LangChain
@@ -983,12 +1091,12 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
983
1091
 
984
1092
  # Upload files
985
1093
  file_1 = client.files.upload(file="./file1")
986
- while file_1.state.name == 'PROCESSING':
1094
+ while file_1.state.name == "PROCESSING":
987
1095
  time.sleep(2)
988
1096
  file_1 = client.files.get(name=file_1.name)
989
1097
 
990
1098
  file_2 = client.files.upload(file="./file2")
991
- while file_2.state.name == 'PROCESSING':
1099
+ while file_2.state.name == "PROCESSING":
992
1100
  time.sleep(2)
993
1101
  file_2 = client.files.get(name=file_2.name)
994
1102
 
@@ -1006,14 +1114,14 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1006
1114
  cache = client.caches.create(
1007
1115
  model=model,
1008
1116
  config=CreateCachedContentConfig(
1009
- display_name='Cached Contents',
1117
+ display_name="Cached Contents",
1010
1118
  system_instruction=(
1011
- 'You are an expert content analyzer, and your job is to answer '
1012
- 'the user\'s query based on the files you have access to.'
1119
+ "You are an expert content analyzer, and your job is to answer "
1120
+ "the user's query based on the files you have access to."
1013
1121
  ),
1014
1122
  contents=contents,
1015
1123
  ttl="300s",
1016
- )
1124
+ ),
1017
1125
  )
1018
1126
 
1019
1127
  # Query with LangChain
@@ -1021,7 +1129,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1021
1129
  model=model,
1022
1130
  cached_content=cache.name,
1023
1131
  )
1024
- message = HumanMessage(content="Provide a summary of the key information across both files.")
1132
+ message = HumanMessage(
1133
+ content="Provide a summary of the key information across both files."
1134
+ )
1025
1135
  llm.invoke([message])
1026
1136
 
1027
1137
  Tool calling:
@@ -1054,23 +1164,34 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1054
1164
 
1055
1165
  .. code-block:: python
1056
1166
 
1057
- [{'name': 'GetWeather',
1058
- 'args': {'location': 'Los Angeles, CA'},
1059
- 'id': 'c186c99f-f137-4d52-947f-9e3deabba6f6'},
1060
- {'name': 'GetWeather',
1061
- 'args': {'location': 'New York City, NY'},
1062
- 'id': 'cebd4a5d-e800-4fa5-babd-4aa286af4f31'},
1063
- {'name': 'GetPopulation',
1064
- 'args': {'location': 'Los Angeles, CA'},
1065
- 'id': '4f92d897-f5e4-4d34-a3bc-93062c92591e'},
1066
- {'name': 'GetPopulation',
1067
- 'args': {'location': 'New York City, NY'},
1068
- 'id': '634582de-5186-4e4b-968b-f192f0a93678'}]
1167
+ [
1168
+ {
1169
+ "name": "GetWeather",
1170
+ "args": {"location": "Los Angeles, CA"},
1171
+ "id": "c186c99f-f137-4d52-947f-9e3deabba6f6",
1172
+ },
1173
+ {
1174
+ "name": "GetWeather",
1175
+ "args": {"location": "New York City, NY"},
1176
+ "id": "cebd4a5d-e800-4fa5-babd-4aa286af4f31",
1177
+ },
1178
+ {
1179
+ "name": "GetPopulation",
1180
+ "args": {"location": "Los Angeles, CA"},
1181
+ "id": "4f92d897-f5e4-4d34-a3bc-93062c92591e",
1182
+ },
1183
+ {
1184
+ "name": "GetPopulation",
1185
+ "args": {"location": "New York City, NY"},
1186
+ "id": "634582de-5186-4e4b-968b-f192f0a93678",
1187
+ },
1188
+ ]
1069
1189
 
1070
1190
  Use Search with Gemini 2:
1071
1191
  .. code-block:: python
1072
1192
 
1073
1193
  from google.ai.generativelanguage_v1beta.types import Tool as GenAITool
1194
+
1074
1195
  llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
1075
1196
  resp = llm.invoke(
1076
1197
  "When is the next total solar eclipse in US?",
@@ -1090,7 +1211,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1090
1211
 
1091
1212
  setup: str = Field(description="The setup of the joke")
1092
1213
  punchline: str = Field(description="The punchline to the joke")
1093
- rating: Optional[int] = Field(description="How funny the joke is, from 1 to 10")
1214
+ rating: Optional[int] = Field(
1215
+ description="How funny the joke is, from 1 to 10"
1216
+ )
1094
1217
 
1095
1218
 
1096
1219
  structured_llm = llm.with_structured_output(Joke)
@@ -1099,9 +1222,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1099
1222
  .. code-block:: python
1100
1223
 
1101
1224
  Joke(
1102
- setup='Why are cats so good at video games?',
1103
- punchline='They have nine lives on the internet',
1104
- rating=None
1225
+ setup="Why are cats so good at video games?",
1226
+ punchline="They have nine lives on the internet",
1227
+ rating=None,
1105
1228
  )
1106
1229
 
1107
1230
  Image input:
@@ -1127,7 +1250,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1127
1250
 
1128
1251
  .. code-block:: python
1129
1252
 
1130
- '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.'
1253
+ "The weather in this image appears to be sunny and pleasant. The sky is a
1254
+ bright blue with scattered white clouds, suggesting fair weather. The lush
1255
+ green grass and trees indicate a warm and possibly slightly breezy day.
1256
+ There are no signs of rain or storms."
1131
1257
 
1132
1258
  PDF input:
1133
1259
  .. code-block:: python
@@ -1135,8 +1261,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1135
1261
  import base64
1136
1262
  from langchain_core.messages import HumanMessage
1137
1263
 
1138
- pdf_bytes = open("/path/to/your/test.pdf", 'rb').read()
1139
- pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
1264
+ pdf_bytes = open("/path/to/your/test.pdf", "rb").read()
1265
+ pdf_base64 = base64.b64encode(pdf_bytes).decode("utf-8")
1140
1266
 
1141
1267
  message = HumanMessage(
1142
1268
  content=[
@@ -1144,9 +1270,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1144
1270
  {
1145
1271
  "type": "file",
1146
1272
  "source_type": "base64",
1147
- "mime_type":"application/pdf",
1148
- "data": pdf_base64
1149
- }
1273
+ "mime_type": "application/pdf",
1274
+ "data": pdf_base64,
1275
+ },
1150
1276
  ]
1151
1277
  )
1152
1278
  ai_msg = llm.invoke([message])
@@ -1154,7 +1280,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1154
1280
 
1155
1281
  .. code-block:: python
1156
1282
 
1157
- 'This research paper describes a system developed for SemEval-2025 Task 9, which aims to automate the detection of food hazards from recall reports, addressing the class imbalance problem by leveraging LLM-based data augmentation techniques and transformer-based models to improve performance.'
1283
+ "This research paper describes a system developed for SemEval-2025 Task 9,
1284
+ which aims to automate the detection of food hazards from recall reports,
1285
+ addressing the class imbalance problem by leveraging LLM-based data
1286
+ augmentation techniques and transformer-based models to improve
1287
+ performance."
1158
1288
 
1159
1289
  Video input:
1160
1290
  .. code-block:: python
@@ -1162,18 +1292,21 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1162
1292
  import base64
1163
1293
  from langchain_core.messages import HumanMessage
1164
1294
 
1165
- video_bytes = open("/path/to/your/video.mp4", 'rb').read()
1166
- video_base64 = base64.b64encode(video_bytes).decode('utf-8')
1295
+ video_bytes = open("/path/to/your/video.mp4", "rb").read()
1296
+ video_base64 = base64.b64encode(video_bytes).decode("utf-8")
1167
1297
 
1168
1298
  message = HumanMessage(
1169
1299
  content=[
1170
- {"type": "text", "text": "describe what's in this video in a sentence"},
1300
+ {
1301
+ "type": "text",
1302
+ "text": "describe what's in this video in a sentence",
1303
+ },
1171
1304
  {
1172
1305
  "type": "file",
1173
1306
  "source_type": "base64",
1174
1307
  "mime_type": "video/mp4",
1175
- "data": video_base64
1176
- }
1308
+ "data": video_base64,
1309
+ },
1177
1310
  ]
1178
1311
  )
1179
1312
  ai_msg = llm.invoke([message])
@@ -1181,7 +1314,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1181
1314
 
1182
1315
  .. code-block:: python
1183
1316
 
1184
- 'Tom and Jerry, along with a turkey, engage in a chaotic Thanksgiving-themed adventure involving a corn-on-the-cob chase, maze antics, and a disastrous attempt to prepare a turkey dinner.'
1317
+ "Tom and Jerry, along with a turkey, engage in a chaotic Thanksgiving-themed
1318
+ adventure involving a corn-on-the-cob chase, maze antics, and a disastrous
1319
+ attempt to prepare a turkey dinner."
1185
1320
 
1186
1321
  You can also pass YouTube URLs directly:
1187
1322
 
@@ -1196,7 +1331,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1196
1331
  "type": "media",
1197
1332
  "file_uri": "https://www.youtube.com/watch?v=9hE5-98ZeCg",
1198
1333
  "mime_type": "video/mp4",
1199
- }
1334
+ },
1200
1335
  ]
1201
1336
  )
1202
1337
  ai_msg = llm.invoke([message])
@@ -1204,7 +1339,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1204
1339
 
1205
1340
  .. code-block:: python
1206
1341
 
1207
- 'The video is a demo of multimodal live streaming in Gemini 2.0. The narrator is sharing his screen in AI Studio and asks if the AI can see it. The AI then reads text that is highlighted on the screen, defines the word “multimodal,” and summarizes everything that was seen and heard.'
1342
+ "The video is a demo of multimodal live streaming in Gemini 2.0. The
1343
+ narrator is sharing his screen in AI Studio and asks if the AI can see it.
1344
+ The AI then reads text that is highlighted on the screen, defines the word
1345
+ “multimodal,” and summarizes everything that was seen and heard."
1208
1346
 
1209
1347
  Audio input:
1210
1348
  .. code-block:: python
@@ -1212,8 +1350,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1212
1350
  import base64
1213
1351
  from langchain_core.messages import HumanMessage
1214
1352
 
1215
- audio_bytes = open("/path/to/your/audio.mp3", 'rb').read()
1216
- audio_base64 = base64.b64encode(audio_bytes).decode('utf-8')
1353
+ audio_bytes = open("/path/to/your/audio.mp3", "rb").read()
1354
+ audio_base64 = base64.b64encode(audio_bytes).decode("utf-8")
1217
1355
 
1218
1356
  message = HumanMessage(
1219
1357
  content=[
@@ -1221,9 +1359,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1221
1359
  {
1222
1360
  "type": "file",
1223
1361
  "source_type": "base64",
1224
- "mime_type":"audio/mp3",
1225
- "data": audio_base64
1226
- }
1362
+ "mime_type": "audio/mp3",
1363
+ "data": audio_base64,
1364
+ },
1227
1365
  ]
1228
1366
  )
1229
1367
  ai_msg = llm.invoke([message])
@@ -1231,7 +1369,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1231
1369
 
1232
1370
  .. code-block:: python
1233
1371
 
1234
- "In this episode of the Made by Google podcast, Stephen Johnson and Simon Tokumine discuss NotebookLM, a tool designed to help users understand complex material in various modalities, with a focus on its unexpected uses, the development of audio overviews, and the implementation of new features like mind maps and source discovery."
1372
+ "In this episode of the Made by Google podcast, Stephen Johnson and Simon
1373
+ Tokumine discuss NotebookLM, a tool designed to help users understand
1374
+ complex material in various modalities, with a focus on its unexpected uses,
1375
+ the development of audio overviews, and the implementation of new features
1376
+ like mind maps and source discovery."
1235
1377
 
1236
1378
  File upload (URI-based):
1237
1379
  You can also upload files to Google's servers and reference them by URI.
@@ -1265,7 +1407,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1265
1407
 
1266
1408
  .. code-block:: python
1267
1409
 
1268
- "This research paper assesses and mitigates multi-turn jailbreak vulnerabilities in large language models using the Crescendo attack study, evaluating attack success rates and mitigation strategies like prompt hardening and LLM-as-guardrail."
1410
+ "This research paper assesses and mitigates multi-turn jailbreak
1411
+ vulnerabilities in large language models using the Crescendo attack study,
1412
+ evaluating attack success rates and mitigation strategies like prompt
1413
+ hardening and LLM-as-guardrail."
1269
1414
 
1270
1415
  Token usage:
1271
1416
  .. code-block:: python
@@ -1275,7 +1420,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1275
1420
 
1276
1421
  .. code-block:: python
1277
1422
 
1278
- {'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23}
1423
+ {"input_tokens": 18, "output_tokens": 5, "total_tokens": 23}
1279
1424
 
1280
1425
 
1281
1426
  Response metadata
@@ -1287,9 +1432,30 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1287
1432
  .. code-block:: python
1288
1433
 
1289
1434
  {
1290
- 'prompt_feedback': {'block_reason': 0, 'safety_ratings': []},
1291
- 'finish_reason': 'STOP',
1292
- '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}]
1435
+ "prompt_feedback": {"block_reason": 0, "safety_ratings": []},
1436
+ "finish_reason": "STOP",
1437
+ "safety_ratings": [
1438
+ {
1439
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
1440
+ "probability": "NEGLIGIBLE",
1441
+ "blocked": False,
1442
+ },
1443
+ {
1444
+ "category": "HARM_CATEGORY_HATE_SPEECH",
1445
+ "probability": "NEGLIGIBLE",
1446
+ "blocked": False,
1447
+ },
1448
+ {
1449
+ "category": "HARM_CATEGORY_HARASSMENT",
1450
+ "probability": "NEGLIGIBLE",
1451
+ "blocked": False,
1452
+ },
1453
+ {
1454
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
1455
+ "probability": "NEGLIGIBLE",
1456
+ "blocked": False,
1457
+ },
1458
+ ],
1293
1459
  }
1294
1460
 
1295
1461
  """ # noqa: E501
@@ -1303,32 +1469,33 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1303
1469
  convert_system_message_to_human: bool = False
1304
1470
  """Whether to merge any leading SystemMessage into the following HumanMessage.
1305
1471
 
1306
- Gemini does not support system messages; any unsupported messages will
1307
- raise an error."""
1472
+ Gemini does not support system messages; any unsupported messages will raise an
1473
+ error.
1474
+ """
1308
1475
 
1309
1476
  response_mime_type: Optional[str] = None
1310
1477
  """Optional. Output response mimetype of the generated candidate text. Only
1311
1478
  supported in Gemini 1.5 and later models.
1312
-
1479
+
1313
1480
  Supported mimetype:
1314
1481
  * ``'text/plain'``: (default) Text output.
1315
1482
  * ``'application/json'``: JSON response in the candidates.
1316
1483
  * ``'text/x.enum'``: Enum in plain text.
1317
-
1484
+
1318
1485
  The model also needs to be prompted to output the appropriate response
1319
1486
  type, otherwise the behavior is undefined. This is a preview feature.
1320
1487
  """
1321
1488
 
1322
1489
  response_schema: Optional[Dict[str, Any]] = None
1323
- """ Optional. Enforce an schema to the output.
1324
- The format of the dictionary should follow Open API schema.
1490
+ """ Optional. Enforce an schema to the output. The format of the dictionary should
1491
+ follow Open API schema.
1325
1492
  """
1326
1493
 
1327
1494
  cached_content: Optional[str] = None
1328
- """The name of the cached content used as context to serve the prediction.
1495
+ """The name of the cached content used as context to serve the prediction.
1329
1496
 
1330
- Note: only used in explicit caching, where users can have control over caching
1331
- (e.g. what content to cache) and enjoy guaranteed cost savings. Format:
1497
+ Note: only used in explicit caching, where users can have control over caching
1498
+ (e.g. what content to cache) and enjoy guaranteed cost savings. Format:
1332
1499
  ``cachedContents/{cachedContent}``.
1333
1500
  """
1334
1501
 
@@ -1384,7 +1551,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1384
1551
  )
1385
1552
 
1386
1553
  @classmethod
1387
- def is_lc_serializable(self) -> bool:
1554
+ def is_lc_serializable(cls) -> bool:
1388
1555
  return True
1389
1556
 
1390
1557
  @model_validator(mode="before")
@@ -1392,20 +1559,22 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1392
1559
  def build_extra(cls, values: dict[str, Any]) -> Any:
1393
1560
  """Build extra kwargs from additional params that were passed in."""
1394
1561
  all_required_field_names = get_pydantic_field_names(cls)
1395
- values = _build_model_kwargs(values, all_required_field_names)
1396
- return values
1562
+ return _build_model_kwargs(values, all_required_field_names)
1397
1563
 
1398
1564
  @model_validator(mode="after")
1399
1565
  def validate_environment(self) -> Self:
1400
1566
  """Validates params and passes them to google-generativeai package."""
1401
1567
  if self.temperature is not None and not 0 <= self.temperature <= 2.0:
1402
- raise ValueError("temperature must be in the range [0.0, 2.0]")
1568
+ msg = "temperature must be in the range [0.0, 2.0]"
1569
+ raise ValueError(msg)
1403
1570
 
1404
1571
  if self.top_p is not None and not 0 <= self.top_p <= 1:
1405
- raise ValueError("top_p must be in the range [0.0, 1.0]")
1572
+ msg = "top_p must be in the range [0.0, 1.0]"
1573
+ raise ValueError(msg)
1406
1574
 
1407
1575
  if self.top_k is not None and self.top_k <= 0:
1408
- raise ValueError("top_k must be positive")
1576
+ msg = "top_k must be positive"
1577
+ raise ValueError(msg)
1409
1578
 
1410
1579
  if not any(self.model.startswith(prefix) for prefix in ("models/",)):
1411
1580
  self.model = f"models/{self.model}"
@@ -1481,30 +1650,28 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1481
1650
  stop: Optional[list[str]] = None,
1482
1651
  **kwargs: Any,
1483
1652
  ) -> BaseMessage:
1484
- """
1485
- Enable code execution. Supported on: gemini-1.5-pro, gemini-1.5-flash,
1653
+ """Enable code execution. Supported on: gemini-1.5-pro, gemini-1.5-flash,
1486
1654
  gemini-2.0-flash, and gemini-2.0-pro. When enabled, the model can execute
1487
1655
  code to solve problems.
1488
1656
  """
1489
-
1490
1657
  """Override invoke to add code_execution parameter."""
1491
1658
 
1492
1659
  if code_execution is not None:
1493
1660
  if not self._supports_code_execution:
1494
- raise ValueError(
1661
+ msg = (
1495
1662
  f"Code execution is only supported on Gemini 1.5 Pro, \
1496
1663
  Gemini 1.5 Flash, "
1497
1664
  f"Gemini 2.0 Flash, and Gemini 2.0 Pro models. \
1498
1665
  Current model: {self.model}"
1499
1666
  )
1667
+ raise ValueError(msg)
1500
1668
  if "tools" not in kwargs:
1501
1669
  code_execution_tool = GoogleTool(code_execution=CodeExecution())
1502
1670
  kwargs["tools"] = [code_execution_tool]
1503
1671
 
1504
1672
  else:
1505
- raise ValueError(
1506
- "Tools are already defined.code_execution tool can't be defined"
1507
- )
1673
+ msg = "Tools are already defined.code_execution tool can't be defined"
1674
+ raise ValueError(msg)
1508
1675
 
1509
1676
  return super().invoke(input, config, stop=stop, **kwargs)
1510
1677
 
@@ -1616,6 +1783,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1616
1783
  tool_choice=tool_choice,
1617
1784
  **kwargs,
1618
1785
  )
1786
+ if self.timeout is not None and "timeout" not in kwargs:
1787
+ kwargs["timeout"] = self.timeout
1788
+ if "max_retries" not in kwargs:
1789
+ kwargs["max_retries"] = self.max_retries
1619
1790
  response: GenerateContentResponse = _chat_with_retry(
1620
1791
  request=request,
1621
1792
  **kwargs,
@@ -1642,13 +1813,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1642
1813
  if not self.async_client:
1643
1814
  updated_kwargs = {
1644
1815
  **kwargs,
1645
- **{
1646
- "tools": tools,
1647
- "functions": functions,
1648
- "safety_settings": safety_settings,
1649
- "tool_config": tool_config,
1650
- "generation_config": generation_config,
1651
- },
1816
+ "tools": tools,
1817
+ "functions": functions,
1818
+ "safety_settings": safety_settings,
1819
+ "tool_config": tool_config,
1820
+ "generation_config": generation_config,
1652
1821
  }
1653
1822
  return await super()._agenerate(
1654
1823
  messages, stop, run_manager, **updated_kwargs
@@ -1666,6 +1835,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1666
1835
  tool_choice=tool_choice,
1667
1836
  **kwargs,
1668
1837
  )
1838
+ if self.timeout is not None and "timeout" not in kwargs:
1839
+ kwargs["timeout"] = self.timeout
1840
+ if "max_retries" not in kwargs:
1841
+ kwargs["max_retries"] = self.max_retries
1669
1842
  response: GenerateContentResponse = await _achat_with_retry(
1670
1843
  request=request,
1671
1844
  **kwargs,
@@ -1701,6 +1874,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1701
1874
  tool_choice=tool_choice,
1702
1875
  **kwargs,
1703
1876
  )
1877
+ if self.timeout is not None and "timeout" not in kwargs:
1878
+ kwargs["timeout"] = self.timeout
1879
+ if "max_retries" not in kwargs:
1880
+ kwargs["max_retries"] = self.max_retries
1704
1881
  response: GenerateContentResponse = _chat_with_retry(
1705
1882
  request=request,
1706
1883
  generation_method=self.client.stream_generate_content,
@@ -1713,8 +1890,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1713
1890
  _chat_result = _response_to_result(
1714
1891
  chunk, stream=True, prev_usage=prev_usage_metadata
1715
1892
  )
1716
- gen = cast(ChatGenerationChunk, _chat_result.generations[0])
1717
- message = cast(AIMessageChunk, gen.message)
1893
+ gen = cast("ChatGenerationChunk", _chat_result.generations[0])
1894
+ message = cast("AIMessageChunk", gen.message)
1718
1895
 
1719
1896
  prev_usage_metadata = (
1720
1897
  message.usage_metadata
@@ -1744,13 +1921,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1744
1921
  if not self.async_client:
1745
1922
  updated_kwargs = {
1746
1923
  **kwargs,
1747
- **{
1748
- "tools": tools,
1749
- "functions": functions,
1750
- "safety_settings": safety_settings,
1751
- "tool_config": tool_config,
1752
- "generation_config": generation_config,
1753
- },
1924
+ "tools": tools,
1925
+ "functions": functions,
1926
+ "safety_settings": safety_settings,
1927
+ "tool_config": tool_config,
1928
+ "generation_config": generation_config,
1754
1929
  }
1755
1930
  async for value in super()._astream(
1756
1931
  messages, stop, run_manager, **updated_kwargs
@@ -1769,6 +1944,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1769
1944
  tool_choice=tool_choice,
1770
1945
  **kwargs,
1771
1946
  )
1947
+ if self.timeout is not None and "timeout" not in kwargs:
1948
+ kwargs["timeout"] = self.timeout
1949
+ if "max_retries" not in kwargs:
1950
+ kwargs["max_retries"] = self.max_retries
1772
1951
  prev_usage_metadata: UsageMetadata | None = None # cumulative usage
1773
1952
  async for chunk in await _achat_with_retry(
1774
1953
  request=request,
@@ -1779,8 +1958,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1779
1958
  _chat_result = _response_to_result(
1780
1959
  chunk, stream=True, prev_usage=prev_usage_metadata
1781
1960
  )
1782
- gen = cast(ChatGenerationChunk, _chat_result.generations[0])
1783
- message = cast(AIMessageChunk, gen.message)
1961
+ gen = cast("ChatGenerationChunk", _chat_result.generations[0])
1962
+ message = cast("AIMessageChunk", gen.message)
1784
1963
 
1785
1964
  prev_usage_metadata = (
1786
1965
  message.usage_metadata
@@ -1807,10 +1986,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1807
1986
  **kwargs: Any,
1808
1987
  ) -> GenerateContentRequest:
1809
1988
  if tool_choice and tool_config:
1810
- raise ValueError(
1989
+ msg = (
1811
1990
  "Must specify at most one of tool_choice and tool_config, received "
1812
1991
  f"both:\n\n{tool_choice=}\n\n{tool_config=}"
1813
1992
  )
1993
+ raise ValueError(msg)
1814
1994
 
1815
1995
  formatted_tools = None
1816
1996
  code_execution_tool = GoogleTool(code_execution=CodeExecution())
@@ -1848,16 +2028,15 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1848
2028
  all_names: List[str] = []
1849
2029
  for t in formatted_tools:
1850
2030
  if hasattr(t, "function_declarations"):
1851
- t_with_declarations = cast(Any, t)
2031
+ t_with_declarations = cast("Any", t)
1852
2032
  all_names.extend(
1853
2033
  f.name for f in t_with_declarations.function_declarations
1854
2034
  )
1855
2035
  elif isinstance(t, GoogleTool) and hasattr(t, "code_execution"):
1856
2036
  continue
1857
2037
  else:
1858
- raise TypeError(
1859
- f"Tool {t} doesn't have function_declarations attribute"
1860
- )
2038
+ msg = f"Tool {t} doesn't have function_declarations attribute"
2039
+ raise TypeError(msg)
1861
2040
 
1862
2041
  tool_config = _tool_choice_to_tool_config(tool_choice, all_names)
1863
2042
 
@@ -1916,7 +2095,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1916
2095
  ) -> Runnable[LanguageModelInput, Union[Dict, BaseModel]]:
1917
2096
  _ = kwargs.pop("strict", None)
1918
2097
  if kwargs:
1919
- raise ValueError(f"Received unsupported arguments {kwargs}")
2098
+ msg = f"Received unsupported arguments {kwargs}"
2099
+ raise ValueError(msg)
1920
2100
 
1921
2101
  parser: OutputParserLike
1922
2102
 
@@ -1933,7 +2113,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1933
2113
  elif isinstance(schema, dict):
1934
2114
  schema_json = schema
1935
2115
  else:
1936
- raise ValueError(f"Unsupported schema type {type(schema)}")
2116
+ msg = f"Unsupported schema type {type(schema)}"
2117
+ raise ValueError(msg)
1937
2118
  parser = JsonOutputParser()
1938
2119
 
1939
2120
  # Resolve refs in schema because they are not supported
@@ -1976,8 +2157,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1976
2157
  exception_key="parsing_error",
1977
2158
  )
1978
2159
  return {"raw": llm} | parser_with_fallback
1979
- else:
1980
- return llm | parser
2160
+ return llm | parser
1981
2161
 
1982
2162
  def bind_tools(
1983
2163
  self,
@@ -1995,20 +2175,21 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1995
2175
 
1996
2176
  Args:
1997
2177
  tools: A list of tool definitions to bind to this chat model.
1998
- Can be a pydantic model, callable, or BaseTool. Pydantic
1999
- models, callables, and BaseTools will be automatically converted to
2000
- their schema dictionary representation. Tools with Union types in
2001
- their arguments are now supported and converted to `anyOf` schemas.
2178
+ Can be a pydantic model, callable, or BaseTool. Pydantic models,
2179
+ callables, and BaseTools will be automatically converted to their schema
2180
+ dictionary representation. Tools with Union types in their arguments are
2181
+ now supported and converted to `anyOf` schemas.
2002
2182
  **kwargs: Any additional parameters to pass to the
2003
2183
  :class:`~langchain.runnable.Runnable` constructor.
2004
2184
  """
2005
2185
  if tool_choice and tool_config:
2006
- raise ValueError(
2186
+ msg = (
2007
2187
  "Must specify at most one of tool_choice and tool_config, received "
2008
2188
  f"both:\n\n{tool_choice=}\n\n{tool_config=}"
2009
2189
  )
2190
+ raise ValueError(msg)
2010
2191
  try:
2011
- formatted_tools: list = [convert_to_openai_tool(tool) for tool in tools] # type: ignore[arg-type]
2192
+ formatted_tools: list = [convert_to_openai_tool(tool) for tool in tools]
2012
2193
  except Exception:
2013
2194
  formatted_tools = [
2014
2195
  tool_to_dict(convert_to_genai_function_declarations(tools))
@@ -2035,9 +2216,8 @@ def _get_tool_name(
2035
2216
  ) -> str:
2036
2217
  try:
2037
2218
  genai_tool = tool_to_dict(convert_to_genai_function_declarations([tool]))
2038
- return [f["name"] for f in genai_tool["function_declarations"]][0] # type: ignore[index]
2039
- except ValueError as e: # other TypedDict
2219
+ return next(f["name"] for f in genai_tool["function_declarations"]) # type: ignore[index]
2220
+ except ValueError: # other TypedDict
2040
2221
  if is_typeddict(tool):
2041
- return convert_to_openai_tool(cast(Dict, tool))["function"]["name"]
2042
- else:
2043
- raise e
2222
+ return convert_to_openai_tool(cast("Dict", tool))["function"]["name"]
2223
+ raise