langchain-google-genai 2.1.10__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,
@@ -92,13 +93,7 @@ from langchain_core.utils.function_calling import (
92
93
  )
93
94
  from langchain_core.utils.pydantic import is_basemodel_subclass
94
95
  from langchain_core.utils.utils import _build_model_kwargs
95
- from pydantic import (
96
- BaseModel,
97
- ConfigDict,
98
- Field,
99
- SecretStr,
100
- model_validator,
101
- )
96
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
102
97
  from pydantic.v1 import BaseModel as BaseModelV1
103
98
  from tenacity import (
104
99
  before_sleep_log,
@@ -145,12 +140,11 @@ _FunctionDeclarationType = Union[
145
140
 
146
141
 
147
142
  class ChatGoogleGenerativeAIError(GoogleGenerativeAIError):
148
- """
149
- Custom exception class for errors associated with the `Google GenAI` API.
143
+ """Custom exception class for errors associated with the `Google GenAI` API.
150
144
 
151
- This exception is raised when there are specific issues related to the
152
- Google genai API usage in the ChatGoogleGenerativeAI class, such as unsupported
153
- 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.
154
148
  """
155
149
 
156
150
 
@@ -160,12 +154,11 @@ def _create_retry_decorator(
160
154
  wait_exponential_min: float = 1.0,
161
155
  wait_exponential_max: float = 60.0,
162
156
  ) -> Callable[[Any], Any]:
163
- """
164
- Creates and returns a preconfigured tenacity retry decorator.
157
+ """Creates and returns a preconfigured tenacity retry decorator.
165
158
 
166
- The retry decorator is configured to handle specific Google API exceptions
167
- such as ResourceExhausted and ServiceUnavailable. It uses an exponential
168
- 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.
169
162
 
170
163
  Returns:
171
164
  Callable[[Any], Any]: A retry decorator configured for handling specific
@@ -180,21 +173,20 @@ def _create_retry_decorator(
180
173
  max=wait_exponential_max,
181
174
  ),
182
175
  retry=(
183
- retry_if_exception_type(google.api_core.exceptions.ResourceExhausted)
184
- | retry_if_exception_type(google.api_core.exceptions.ServiceUnavailable)
185
- | 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)
186
179
  ),
187
180
  before_sleep=before_sleep_log(logger, logging.WARNING),
188
181
  )
189
182
 
190
183
 
191
184
  def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
192
- """
193
- Executes a chat generation method with retry logic using tenacity.
185
+ """Executes a chat generation method with retry logic using tenacity.
194
186
 
195
- This function is a wrapper that applies a retry mechanism to a provided
196
- chat generation function. It is useful for handling intermittent issues
197
- 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.
198
190
 
199
191
  Args:
200
192
  generation_method (Callable): The chat generation method to be executed.
@@ -214,7 +206,7 @@ def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
214
206
  def _chat_with_retry(**kwargs: Any) -> Any:
215
207
  try:
216
208
  return generation_method(**kwargs)
217
- except google.api_core.exceptions.FailedPrecondition as exc:
209
+ except FailedPrecondition as exc:
218
210
  if "location is not supported" in exc.message:
219
211
  error_msg = (
220
212
  "Your location is not supported by google-generativeai "
@@ -223,19 +215,18 @@ def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
223
215
  )
224
216
  raise ValueError(error_msg)
225
217
 
226
- except google.api_core.exceptions.InvalidArgument as e:
227
- raise ChatGoogleGenerativeAIError(
228
- f"Invalid argument provided to Gemini: {e}"
229
- ) from e
230
- 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:
231
222
  # Handle quota-exceeded error with recommended retry delay
232
- 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(
233
224
  "wait_exponential_max", 60.0
234
225
  ):
235
- time.sleep(e.retry_after)
236
- raise e
237
- except Exception as e:
238
- raise e
226
+ time.sleep(getattr(e, "retry_after"))
227
+ raise
228
+ except Exception:
229
+ raise
239
230
 
240
231
  params = (
241
232
  {k: v for k, v in kwargs.items() if k in _allowed_params_prediction_service}
@@ -248,12 +239,11 @@ def _chat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
248
239
 
249
240
 
250
241
  async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
251
- """
252
- Executes a chat generation method with retry logic using tenacity.
242
+ """Executes a chat generation method with retry logic using tenacity.
253
243
 
254
- This function is a wrapper that applies a retry mechanism to a provided
255
- chat generation function. It is useful for handling intermittent issues
256
- 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.
257
247
 
258
248
  Args:
259
249
  generation_method (Callable): The chat generation method to be executed.
@@ -262,8 +252,12 @@ async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
262
252
  Returns:
263
253
  Any: The result from the chat generation method.
264
254
  """
265
- retry_decorator = _create_retry_decorator()
266
- 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
+ )
267
261
 
268
262
  @retry_decorator
269
263
  async def _achat_with_retry(**kwargs: Any) -> Any:
@@ -271,11 +265,17 @@ async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any:
271
265
  return await generation_method(**kwargs)
272
266
  except InvalidArgument as e:
273
267
  # Do not retry for these errors.
274
- raise ChatGoogleGenerativeAIError(
275
- f"Invalid argument provided to Gemini: {e}"
276
- ) from e
277
- except Exception as e:
278
- 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
279
279
 
280
280
  params = (
281
281
  {k: v for k, v in kwargs.items() if k in _allowed_params_prediction_service}
@@ -328,12 +328,13 @@ def _convert_to_parts(
328
328
  elif part["source_type"] == "base64":
329
329
  bytes_ = base64.b64decode(part["data"])
330
330
  else:
331
- raise ValueError("source_type must be url or base64.")
331
+ msg = "source_type must be url or base64."
332
+ raise ValueError(msg)
332
333
  inline_data: dict = {"data": bytes_}
333
334
  if "mime_type" in part:
334
335
  inline_data["mime_type"] = part["mime_type"]
335
336
  else:
336
- source = cast(str, part.get("url") or part.get("data"))
337
+ source = cast("str", part.get("url") or part.get("data"))
337
338
  mime_type, _ = mimetypes.guess_type(source)
338
339
  if not mime_type:
339
340
  kind = filetype.guess(bytes_)
@@ -346,16 +347,16 @@ def _convert_to_parts(
346
347
  img_url = part["image_url"]
347
348
  if isinstance(img_url, dict):
348
349
  if "url" not in img_url:
349
- raise ValueError(
350
- f"Unrecognized message image format: {img_url}"
351
- )
350
+ msg = f"Unrecognized message image format: {img_url}"
351
+ raise ValueError(msg)
352
352
  img_url = img_url["url"]
353
353
  parts.append(image_loader.load_part(img_url))
354
354
  # Handle media type like LangChain.js
355
355
  # https://github.com/langchain-ai/langchainjs/blob/e536593e2585f1dd7b0afc187de4d07cb40689ba/libs/langchain-google-common/src/utils/gemini.ts#L93-L106
356
356
  elif part["type"] == "media":
357
357
  if "mime_type" not in part:
358
- raise ValueError(f"Missing mime_type in media part: {part}")
358
+ msg = f"Missing mime_type in media part: {part}"
359
+ raise ValueError(msg)
359
360
  mime_type = part["mime_type"]
360
361
  media_part = Part()
361
362
 
@@ -368,19 +369,19 @@ def _convert_to_parts(
368
369
  file_uri=part["file_uri"], mime_type=mime_type
369
370
  )
370
371
  else:
371
- raise ValueError(
372
- f"Media part must have either data or file_uri: {part}"
373
- )
372
+ msg = f"Media part must have either data or file_uri: {part}"
373
+ raise ValueError(msg)
374
374
  if "video_metadata" in part:
375
375
  metadata = VideoMetadata(part["video_metadata"])
376
376
  media_part.video_metadata = metadata
377
377
  parts.append(media_part)
378
378
  elif part["type"] == "executable_code":
379
379
  if "executable_code" not in part or "language" not in part:
380
- raise ValueError(
380
+ msg = (
381
381
  "Executable code part must have 'code' and 'language' "
382
382
  f"keys, got {part}"
383
383
  )
384
+ raise ValueError(msg)
384
385
  executable_code_part = Part(
385
386
  executable_code=ExecutableCode(
386
387
  language=part["language"], code=part["executable_code"]
@@ -389,10 +390,11 @@ def _convert_to_parts(
389
390
  parts.append(executable_code_part)
390
391
  elif part["type"] == "code_execution_result":
391
392
  if "code_execution_result" not in part:
392
- raise ValueError(
393
+ msg = (
393
394
  "Code execution result part must have "
394
395
  f"'code_execution_result', got {part}"
395
396
  )
397
+ raise ValueError(msg)
396
398
  if "outcome" in part:
397
399
  outcome = part["outcome"]
398
400
  else:
@@ -407,10 +409,11 @@ def _convert_to_parts(
407
409
  elif part["type"] == "thinking":
408
410
  parts.append(Part(text=part["thinking"], thought=True))
409
411
  else:
410
- raise ValueError(
412
+ msg = (
411
413
  f"Unrecognized message part type: {part['type']}. Only text, "
412
414
  f"image_url, and media types are supported."
413
415
  )
416
+ raise ValueError(msg)
414
417
  else:
415
418
  # Yolo
416
419
  logger.warning(
@@ -420,9 +423,8 @@ def _convert_to_parts(
420
423
  else:
421
424
  # TODO: Maybe some of Google's native stuff
422
425
  # would hit this branch.
423
- raise ChatGoogleGenerativeAIError(
424
- "Gemini only supports text and inline_data parts."
425
- )
426
+ msg = "Gemini only supports text and inline_data parts."
427
+ raise ChatGoogleGenerativeAIError(msg)
426
428
  return parts
427
429
 
428
430
 
@@ -469,14 +471,15 @@ def _convert_tool_message_to_parts(
469
471
  def _get_ai_message_tool_messages_parts(
470
472
  tool_messages: Sequence[ToolMessage], ai_message: AIMessage
471
473
  ) -> list[Part]:
472
- """
473
- Finds relevant tool messages for the AI message and converts them to a single
474
- 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.
475
478
  """
476
479
  # We are interested only in the tool messages that are part of the AI message
477
480
  tool_calls_ids = {tool_call["id"]: tool_call for tool_call in ai_message.tool_calls}
478
481
  parts = []
479
- for i, message in enumerate(tool_messages):
482
+ for _i, message in enumerate(tool_messages):
480
483
  if not tool_calls_ids:
481
484
  break
482
485
  if message.tool_call_id in tool_calls_ids:
@@ -496,7 +499,12 @@ def _parse_chat_history(
496
499
  messages: List[Content] = []
497
500
 
498
501
  if convert_system_message_to_human:
499
- warnings.warn("Convert_system_message_to_human will be deprecated!")
502
+ warnings.warn(
503
+ "The 'convert_system_message_to_human' parameter is deprecated and will be "
504
+ "removed in a future version. Use system instructions instead.",
505
+ DeprecationWarning,
506
+ stacklevel=2,
507
+ )
500
508
 
501
509
  system_instruction: Optional[Content] = None
502
510
  messages_without_tool_messages = [
@@ -515,7 +523,7 @@ def _parse_chat_history(
515
523
  else:
516
524
  pass
517
525
  continue
518
- elif isinstance(message, AIMessage):
526
+ if isinstance(message, AIMessage):
519
527
  role = "model"
520
528
  if message.tool_calls:
521
529
  ai_message_parts = []
@@ -533,7 +541,7 @@ def _parse_chat_history(
533
541
  messages.append(Content(role=role, parts=ai_message_parts))
534
542
  messages.append(Content(role="user", parts=tool_messages_parts))
535
543
  continue
536
- elif raw_function_call := message.additional_kwargs.get("function_call"):
544
+ if raw_function_call := message.additional_kwargs.get("function_call"):
537
545
  function_call = FunctionCall(
538
546
  {
539
547
  "name": raw_function_call["name"],
@@ -547,15 +555,14 @@ def _parse_chat_history(
547
555
  role = "user"
548
556
  parts = _convert_to_parts(message.content)
549
557
  if i == 1 and convert_system_message_to_human and system_instruction:
550
- parts = [p for p in system_instruction.parts] + parts
558
+ parts = list(system_instruction.parts) + parts
551
559
  system_instruction = None
552
560
  elif isinstance(message, FunctionMessage):
553
561
  role = "user"
554
562
  parts = _convert_tool_message_to_parts(message)
555
563
  else:
556
- raise ValueError(
557
- f"Unexpected message with type {type(message)} at the position {i}."
558
- )
564
+ msg = f"Unexpected message with type {type(message)} at the position {i}."
565
+ raise ValueError(msg)
559
566
 
560
567
  messages.append(Content(role=role, parts=parts))
561
568
  return system_instruction, messages
@@ -568,17 +575,17 @@ def _append_to_content(
568
575
  """Appends a new item to the content, handling different initial content types."""
569
576
  if current_content is None and isinstance(new_item, str):
570
577
  return new_item
571
- elif current_content is None:
578
+ if current_content is None:
572
579
  return [new_item]
573
- elif isinstance(current_content, str):
580
+ if isinstance(current_content, str):
574
581
  return [current_content, new_item]
575
- elif isinstance(current_content, list):
582
+ if isinstance(current_content, list):
576
583
  current_content.append(new_item)
577
584
  return current_content
578
- else:
579
- # This case should ideally not be reached with proper type checking,
580
- # but it catches any unexpected types that might slip through.
581
- 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)
582
589
 
583
590
 
584
591
  def _parse_response_candidate(
@@ -622,14 +629,13 @@ def _parse_response_candidate(
622
629
  if (
623
630
  hasattr(part, "code_execution_result")
624
631
  and part.code_execution_result is not None
625
- ):
626
- if part.code_execution_result.output:
627
- execution_result = {
628
- "type": "code_execution_result",
629
- "code_execution_result": part.code_execution_result.output,
630
- "outcome": part.code_execution_result.outcome,
631
- }
632
- 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)
633
639
 
634
640
  if part.inline_data.mime_type.startswith("audio/"):
635
641
  buffer = io.BytesIO()
@@ -659,9 +665,15 @@ def _parse_response_candidate(
659
665
  function_call = {"name": part.function_call.name}
660
666
  # dump to match other function calling llm for now
661
667
  function_call_args_dict = proto.Message.to_dict(part.function_call)["args"]
662
- function_call["arguments"] = json.dumps(
663
- {k: function_call_args_dict[k] for k in function_call_args_dict}
664
- )
668
+
669
+ # Fix: Correct integer-like floats from protobuf conversion
670
+ # The protobuf library sometimes converts integers to floats
671
+ corrected_args = {
672
+ k: int(v) if isinstance(v, float) and v.is_integer() else v
673
+ for k, v in function_call_args_dict.items()
674
+ }
675
+
676
+ function_call["arguments"] = json.dumps(corrected_args)
665
677
  additional_kwargs["function_call"] = function_call
666
678
 
667
679
  if streaming:
@@ -703,9 +715,9 @@ def _parse_response_candidate(
703
715
  ):
704
716
  warnings.warn(
705
717
  """
706
- ⚠️ Warning: Output may vary each run.
707
- - 'executable_code': Always present.
708
- - '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.
709
721
 
710
722
  Validate before using in production.
711
723
  """
@@ -713,13 +725,13 @@ def _parse_response_candidate(
713
725
 
714
726
  if streaming:
715
727
  return AIMessageChunk(
716
- content=cast(Union[str, List[Union[str, Dict[Any, Any]]]], content),
728
+ content=content,
717
729
  additional_kwargs=additional_kwargs,
718
730
  tool_call_chunks=tool_call_chunks,
719
731
  )
720
732
 
721
733
  return AIMessage(
722
- content=cast(Union[str, List[Union[str, Dict[Any, Any]]]], content),
734
+ content=content,
723
735
  additional_kwargs=additional_kwargs,
724
736
  tool_calls=tool_calls,
725
737
  invalid_tool_calls=invalid_tool_calls,
@@ -800,7 +812,7 @@ def _response_to_result(
800
812
  if stream:
801
813
  generations.append(
802
814
  ChatGenerationChunk(
803
- message=cast(AIMessageChunk, message),
815
+ message=cast("AIMessageChunk", message),
804
816
  generation_info=generation_info,
805
817
  )
806
818
  )
@@ -819,7 +831,15 @@ def _response_to_result(
819
831
  if stream:
820
832
  generations = [
821
833
  ChatGenerationChunk(
822
- message=AIMessageChunk(content=""), generation_info={}
834
+ message=AIMessageChunk(
835
+ content="",
836
+ response_metadata={
837
+ "prompt_feedback": proto.Message.to_dict(
838
+ response.prompt_feedback
839
+ )
840
+ },
841
+ ),
842
+ generation_info={},
823
843
  )
824
844
  ]
825
845
  else:
@@ -836,13 +856,14 @@ def _is_event_loop_running() -> bool:
836
856
 
837
857
 
838
858
  class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
839
- """`Google AI` chat models integration.
859
+ r"""`Google AI` chat models integration.
840
860
 
841
861
  Instantiation:
842
862
  To use, you must have either:
843
863
 
844
864
  1. The ``GOOGLE_API_KEY`` environment variable set with your API key, or
845
- 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.
846
867
 
847
868
  .. code-block:: python
848
869
 
@@ -864,9 +885,38 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
864
885
 
865
886
  AIMessage(
866
887
  content="J'adore programmer. \\n",
867
- 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}]},
868
- id='run-56cecc34-2e54-4b52-a974-337e47008ad2-0',
869
- 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
+ },
870
920
  )
871
921
 
872
922
  Stream:
@@ -877,8 +927,50 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
877
927
 
878
928
  .. code-block:: python
879
929
 
880
- 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})
881
- 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
+ )
882
974
 
883
975
  .. code-block:: python
884
976
 
@@ -892,9 +984,37 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
892
984
 
893
985
  AIMessageChunk(
894
986
  content="J'adore programmer. \\n",
895
- 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}]},
896
- id='run-3ce13a42-cd30-4ad7-a684-f1f0b37cdeec',
897
- 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
+ },
898
1018
  )
899
1019
 
900
1020
  Async:
@@ -909,9 +1029,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
909
1029
  # await llm.abatch([messages])
910
1030
 
911
1031
  Context Caching:
912
- Context caching allows you to store and reuse content (e.g., PDFs, images) for faster processing.
913
- The ``cached_content`` parameter accepts a cache name created via the Google Generative AI API.
914
- 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``.
915
1036
 
916
1037
  Single File Example:
917
1038
  This caches a single file and queries it.
@@ -928,23 +1049,23 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
928
1049
 
929
1050
  # Upload file
930
1051
  file = client.files.upload(file="./example_file")
931
- while file.state.name == 'PROCESSING':
1052
+ while file.state.name == "PROCESSING":
932
1053
  time.sleep(2)
933
1054
  file = client.files.get(name=file.name)
934
1055
 
935
1056
  # Create cache
936
- model = 'models/gemini-1.5-flash-latest'
1057
+ model = "models/gemini-1.5-flash-latest"
937
1058
  cache = client.caches.create(
938
1059
  model=model,
939
1060
  config=types.CreateCachedContentConfig(
940
- display_name='Cached Content',
1061
+ display_name="Cached Content",
941
1062
  system_instruction=(
942
- 'You are an expert content analyzer, and your job is to answer '
943
- '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."
944
1065
  ),
945
1066
  contents=[file],
946
1067
  ttl="300s",
947
- )
1068
+ ),
948
1069
  )
949
1070
 
950
1071
  # Query with LangChain
@@ -970,12 +1091,12 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
970
1091
 
971
1092
  # Upload files
972
1093
  file_1 = client.files.upload(file="./file1")
973
- while file_1.state.name == 'PROCESSING':
1094
+ while file_1.state.name == "PROCESSING":
974
1095
  time.sleep(2)
975
1096
  file_1 = client.files.get(name=file_1.name)
976
1097
 
977
1098
  file_2 = client.files.upload(file="./file2")
978
- while file_2.state.name == 'PROCESSING':
1099
+ while file_2.state.name == "PROCESSING":
979
1100
  time.sleep(2)
980
1101
  file_2 = client.files.get(name=file_2.name)
981
1102
 
@@ -993,14 +1114,14 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
993
1114
  cache = client.caches.create(
994
1115
  model=model,
995
1116
  config=CreateCachedContentConfig(
996
- display_name='Cached Contents',
1117
+ display_name="Cached Contents",
997
1118
  system_instruction=(
998
- 'You are an expert content analyzer, and your job is to answer '
999
- '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."
1000
1121
  ),
1001
1122
  contents=contents,
1002
1123
  ttl="300s",
1003
- )
1124
+ ),
1004
1125
  )
1005
1126
 
1006
1127
  # Query with LangChain
@@ -1008,7 +1129,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1008
1129
  model=model,
1009
1130
  cached_content=cache.name,
1010
1131
  )
1011
- 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
+ )
1012
1135
  llm.invoke([message])
1013
1136
 
1014
1137
  Tool calling:
@@ -1041,23 +1164,34 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1041
1164
 
1042
1165
  .. code-block:: python
1043
1166
 
1044
- [{'name': 'GetWeather',
1045
- 'args': {'location': 'Los Angeles, CA'},
1046
- 'id': 'c186c99f-f137-4d52-947f-9e3deabba6f6'},
1047
- {'name': 'GetWeather',
1048
- 'args': {'location': 'New York City, NY'},
1049
- 'id': 'cebd4a5d-e800-4fa5-babd-4aa286af4f31'},
1050
- {'name': 'GetPopulation',
1051
- 'args': {'location': 'Los Angeles, CA'},
1052
- 'id': '4f92d897-f5e4-4d34-a3bc-93062c92591e'},
1053
- {'name': 'GetPopulation',
1054
- 'args': {'location': 'New York City, NY'},
1055
- '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
+ ]
1056
1189
 
1057
1190
  Use Search with Gemini 2:
1058
1191
  .. code-block:: python
1059
1192
 
1060
1193
  from google.ai.generativelanguage_v1beta.types import Tool as GenAITool
1194
+
1061
1195
  llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
1062
1196
  resp = llm.invoke(
1063
1197
  "When is the next total solar eclipse in US?",
@@ -1077,7 +1211,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1077
1211
 
1078
1212
  setup: str = Field(description="The setup of the joke")
1079
1213
  punchline: str = Field(description="The punchline to the joke")
1080
- 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
+ )
1081
1217
 
1082
1218
 
1083
1219
  structured_llm = llm.with_structured_output(Joke)
@@ -1086,9 +1222,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1086
1222
  .. code-block:: python
1087
1223
 
1088
1224
  Joke(
1089
- setup='Why are cats so good at video games?',
1090
- punchline='They have nine lives on the internet',
1091
- rating=None
1225
+ setup="Why are cats so good at video games?",
1226
+ punchline="They have nine lives on the internet",
1227
+ rating=None,
1092
1228
  )
1093
1229
 
1094
1230
  Image input:
@@ -1114,7 +1250,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1114
1250
 
1115
1251
  .. code-block:: python
1116
1252
 
1117
- '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."
1118
1257
 
1119
1258
  PDF input:
1120
1259
  .. code-block:: python
@@ -1122,8 +1261,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1122
1261
  import base64
1123
1262
  from langchain_core.messages import HumanMessage
1124
1263
 
1125
- pdf_bytes = open("/path/to/your/test.pdf", 'rb').read()
1126
- 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")
1127
1266
 
1128
1267
  message = HumanMessage(
1129
1268
  content=[
@@ -1131,9 +1270,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1131
1270
  {
1132
1271
  "type": "file",
1133
1272
  "source_type": "base64",
1134
- "mime_type":"application/pdf",
1135
- "data": pdf_base64
1136
- }
1273
+ "mime_type": "application/pdf",
1274
+ "data": pdf_base64,
1275
+ },
1137
1276
  ]
1138
1277
  )
1139
1278
  ai_msg = llm.invoke([message])
@@ -1141,7 +1280,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1141
1280
 
1142
1281
  .. code-block:: python
1143
1282
 
1144
- '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."
1145
1288
 
1146
1289
  Video input:
1147
1290
  .. code-block:: python
@@ -1149,18 +1292,21 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1149
1292
  import base64
1150
1293
  from langchain_core.messages import HumanMessage
1151
1294
 
1152
- video_bytes = open("/path/to/your/video.mp4", 'rb').read()
1153
- 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")
1154
1297
 
1155
1298
  message = HumanMessage(
1156
1299
  content=[
1157
- {"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
+ },
1158
1304
  {
1159
1305
  "type": "file",
1160
1306
  "source_type": "base64",
1161
1307
  "mime_type": "video/mp4",
1162
- "data": video_base64
1163
- }
1308
+ "data": video_base64,
1309
+ },
1164
1310
  ]
1165
1311
  )
1166
1312
  ai_msg = llm.invoke([message])
@@ -1168,7 +1314,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1168
1314
 
1169
1315
  .. code-block:: python
1170
1316
 
1171
- '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."
1172
1320
 
1173
1321
  You can also pass YouTube URLs directly:
1174
1322
 
@@ -1183,7 +1331,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1183
1331
  "type": "media",
1184
1332
  "file_uri": "https://www.youtube.com/watch?v=9hE5-98ZeCg",
1185
1333
  "mime_type": "video/mp4",
1186
- }
1334
+ },
1187
1335
  ]
1188
1336
  )
1189
1337
  ai_msg = llm.invoke([message])
@@ -1191,7 +1339,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1191
1339
 
1192
1340
  .. code-block:: python
1193
1341
 
1194
- '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."
1195
1346
 
1196
1347
  Audio input:
1197
1348
  .. code-block:: python
@@ -1199,8 +1350,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1199
1350
  import base64
1200
1351
  from langchain_core.messages import HumanMessage
1201
1352
 
1202
- audio_bytes = open("/path/to/your/audio.mp3", 'rb').read()
1203
- 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")
1204
1355
 
1205
1356
  message = HumanMessage(
1206
1357
  content=[
@@ -1208,9 +1359,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1208
1359
  {
1209
1360
  "type": "file",
1210
1361
  "source_type": "base64",
1211
- "mime_type":"audio/mp3",
1212
- "data": audio_base64
1213
- }
1362
+ "mime_type": "audio/mp3",
1363
+ "data": audio_base64,
1364
+ },
1214
1365
  ]
1215
1366
  )
1216
1367
  ai_msg = llm.invoke([message])
@@ -1218,7 +1369,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1218
1369
 
1219
1370
  .. code-block:: python
1220
1371
 
1221
- "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."
1222
1377
 
1223
1378
  File upload (URI-based):
1224
1379
  You can also upload files to Google's servers and reference them by URI.
@@ -1252,7 +1407,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1252
1407
 
1253
1408
  .. code-block:: python
1254
1409
 
1255
- "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."
1256
1414
 
1257
1415
  Token usage:
1258
1416
  .. code-block:: python
@@ -1262,7 +1420,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1262
1420
 
1263
1421
  .. code-block:: python
1264
1422
 
1265
- {'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23}
1423
+ {"input_tokens": 18, "output_tokens": 5, "total_tokens": 23}
1266
1424
 
1267
1425
 
1268
1426
  Response metadata
@@ -1274,9 +1432,30 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1274
1432
  .. code-block:: python
1275
1433
 
1276
1434
  {
1277
- 'prompt_feedback': {'block_reason': 0, 'safety_ratings': []},
1278
- 'finish_reason': 'STOP',
1279
- '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
+ ],
1280
1459
  }
1281
1460
 
1282
1461
  """ # noqa: E501
@@ -1290,35 +1469,42 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1290
1469
  convert_system_message_to_human: bool = False
1291
1470
  """Whether to merge any leading SystemMessage into the following HumanMessage.
1292
1471
 
1293
- Gemini does not support system messages; any unsupported messages will
1294
- raise an error."""
1472
+ Gemini does not support system messages; any unsupported messages will raise an
1473
+ error.
1474
+ """
1295
1475
 
1296
1476
  response_mime_type: Optional[str] = None
1297
1477
  """Optional. Output response mimetype of the generated candidate text. Only
1298
1478
  supported in Gemini 1.5 and later models.
1299
-
1479
+
1300
1480
  Supported mimetype:
1301
1481
  * ``'text/plain'``: (default) Text output.
1302
1482
  * ``'application/json'``: JSON response in the candidates.
1303
1483
  * ``'text/x.enum'``: Enum in plain text.
1304
-
1484
+
1305
1485
  The model also needs to be prompted to output the appropriate response
1306
1486
  type, otherwise the behavior is undefined. This is a preview feature.
1307
1487
  """
1308
1488
 
1309
1489
  response_schema: Optional[Dict[str, Any]] = None
1310
- """ Optional. Enforce an schema to the output.
1311
- 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.
1312
1492
  """
1313
1493
 
1314
1494
  cached_content: Optional[str] = None
1315
- """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.
1316
1496
 
1317
- Note: only used in explicit caching, where users can have control over caching
1318
- (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:
1319
1499
  ``cachedContents/{cachedContent}``.
1320
1500
  """
1321
1501
 
1502
+ stop: Optional[List[str]] = None
1503
+ """Stop sequences for the model."""
1504
+
1505
+ streaming: Optional[bool] = None
1506
+ """Whether to stream responses from the model."""
1507
+
1322
1508
  model_kwargs: dict[str, Any] = Field(default_factory=dict)
1323
1509
  """Holds any unexpected initialization parameters."""
1324
1510
 
@@ -1365,7 +1551,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1365
1551
  )
1366
1552
 
1367
1553
  @classmethod
1368
- def is_lc_serializable(self) -> bool:
1554
+ def is_lc_serializable(cls) -> bool:
1369
1555
  return True
1370
1556
 
1371
1557
  @model_validator(mode="before")
@@ -1373,20 +1559,22 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1373
1559
  def build_extra(cls, values: dict[str, Any]) -> Any:
1374
1560
  """Build extra kwargs from additional params that were passed in."""
1375
1561
  all_required_field_names = get_pydantic_field_names(cls)
1376
- values = _build_model_kwargs(values, all_required_field_names)
1377
- return values
1562
+ return _build_model_kwargs(values, all_required_field_names)
1378
1563
 
1379
1564
  @model_validator(mode="after")
1380
1565
  def validate_environment(self) -> Self:
1381
1566
  """Validates params and passes them to google-generativeai package."""
1382
1567
  if self.temperature is not None and not 0 <= self.temperature <= 2.0:
1383
- 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)
1384
1570
 
1385
1571
  if self.top_p is not None and not 0 <= self.top_p <= 1:
1386
- 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)
1387
1574
 
1388
1575
  if self.top_k is not None and self.top_k <= 0:
1389
- raise ValueError("top_k must be positive")
1576
+ msg = "top_k must be positive"
1577
+ raise ValueError(msg)
1390
1578
 
1391
1579
  if not any(self.model.startswith(prefix) for prefix in ("models/",)):
1392
1580
  self.model = f"models/{self.model}"
@@ -1462,30 +1650,28 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1462
1650
  stop: Optional[list[str]] = None,
1463
1651
  **kwargs: Any,
1464
1652
  ) -> BaseMessage:
1465
- """
1466
- 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,
1467
1654
  gemini-2.0-flash, and gemini-2.0-pro. When enabled, the model can execute
1468
1655
  code to solve problems.
1469
1656
  """
1470
-
1471
1657
  """Override invoke to add code_execution parameter."""
1472
1658
 
1473
1659
  if code_execution is not None:
1474
1660
  if not self._supports_code_execution:
1475
- raise ValueError(
1661
+ msg = (
1476
1662
  f"Code execution is only supported on Gemini 1.5 Pro, \
1477
1663
  Gemini 1.5 Flash, "
1478
1664
  f"Gemini 2.0 Flash, and Gemini 2.0 Pro models. \
1479
1665
  Current model: {self.model}"
1480
1666
  )
1667
+ raise ValueError(msg)
1481
1668
  if "tools" not in kwargs:
1482
1669
  code_execution_tool = GoogleTool(code_execution=CodeExecution())
1483
1670
  kwargs["tools"] = [code_execution_tool]
1484
1671
 
1485
1672
  else:
1486
- raise ValueError(
1487
- "Tools are already defined.code_execution tool can't be defined"
1488
- )
1673
+ msg = "Tools are already defined.code_execution tool can't be defined"
1674
+ raise ValueError(msg)
1489
1675
 
1490
1676
  return super().invoke(input, config, stop=stop, **kwargs)
1491
1677
 
@@ -1530,18 +1716,21 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1530
1716
  "response_modalities": self.response_modalities,
1531
1717
  "thinking_config": (
1532
1718
  (
1533
- {"thinking_budget": self.thinking_budget}
1534
- if self.thinking_budget is not None
1535
- else {}
1536
- )
1537
- | (
1538
- {"include_thoughts": self.include_thoughts}
1539
- if self.include_thoughts is not None
1540
- else {}
1719
+ (
1720
+ {"thinking_budget": self.thinking_budget}
1721
+ if self.thinking_budget is not None
1722
+ else {}
1723
+ )
1724
+ | (
1725
+ {"include_thoughts": self.include_thoughts}
1726
+ if self.include_thoughts is not None
1727
+ else {}
1728
+ )
1541
1729
  )
1542
- )
1543
- if self.thinking_budget is not None or self.include_thoughts is not None
1544
- else None,
1730
+ if self.thinking_budget is not None
1731
+ or self.include_thoughts is not None
1732
+ else None
1733
+ ),
1545
1734
  }.items()
1546
1735
  if v is not None
1547
1736
  }
@@ -1594,6 +1783,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1594
1783
  tool_choice=tool_choice,
1595
1784
  **kwargs,
1596
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
1597
1790
  response: GenerateContentResponse = _chat_with_retry(
1598
1791
  request=request,
1599
1792
  **kwargs,
@@ -1620,13 +1813,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1620
1813
  if not self.async_client:
1621
1814
  updated_kwargs = {
1622
1815
  **kwargs,
1623
- **{
1624
- "tools": tools,
1625
- "functions": functions,
1626
- "safety_settings": safety_settings,
1627
- "tool_config": tool_config,
1628
- "generation_config": generation_config,
1629
- },
1816
+ "tools": tools,
1817
+ "functions": functions,
1818
+ "safety_settings": safety_settings,
1819
+ "tool_config": tool_config,
1820
+ "generation_config": generation_config,
1630
1821
  }
1631
1822
  return await super()._agenerate(
1632
1823
  messages, stop, run_manager, **updated_kwargs
@@ -1644,6 +1835,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1644
1835
  tool_choice=tool_choice,
1645
1836
  **kwargs,
1646
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
1647
1842
  response: GenerateContentResponse = await _achat_with_retry(
1648
1843
  request=request,
1649
1844
  **kwargs,
@@ -1679,6 +1874,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1679
1874
  tool_choice=tool_choice,
1680
1875
  **kwargs,
1681
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
1682
1881
  response: GenerateContentResponse = _chat_with_retry(
1683
1882
  request=request,
1684
1883
  generation_method=self.client.stream_generate_content,
@@ -1691,8 +1890,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1691
1890
  _chat_result = _response_to_result(
1692
1891
  chunk, stream=True, prev_usage=prev_usage_metadata
1693
1892
  )
1694
- gen = cast(ChatGenerationChunk, _chat_result.generations[0])
1695
- message = cast(AIMessageChunk, gen.message)
1893
+ gen = cast("ChatGenerationChunk", _chat_result.generations[0])
1894
+ message = cast("AIMessageChunk", gen.message)
1696
1895
 
1697
1896
  prev_usage_metadata = (
1698
1897
  message.usage_metadata
@@ -1722,13 +1921,11 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1722
1921
  if not self.async_client:
1723
1922
  updated_kwargs = {
1724
1923
  **kwargs,
1725
- **{
1726
- "tools": tools,
1727
- "functions": functions,
1728
- "safety_settings": safety_settings,
1729
- "tool_config": tool_config,
1730
- "generation_config": generation_config,
1731
- },
1924
+ "tools": tools,
1925
+ "functions": functions,
1926
+ "safety_settings": safety_settings,
1927
+ "tool_config": tool_config,
1928
+ "generation_config": generation_config,
1732
1929
  }
1733
1930
  async for value in super()._astream(
1734
1931
  messages, stop, run_manager, **updated_kwargs
@@ -1747,6 +1944,10 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1747
1944
  tool_choice=tool_choice,
1748
1945
  **kwargs,
1749
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
1750
1951
  prev_usage_metadata: UsageMetadata | None = None # cumulative usage
1751
1952
  async for chunk in await _achat_with_retry(
1752
1953
  request=request,
@@ -1757,8 +1958,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1757
1958
  _chat_result = _response_to_result(
1758
1959
  chunk, stream=True, prev_usage=prev_usage_metadata
1759
1960
  )
1760
- gen = cast(ChatGenerationChunk, _chat_result.generations[0])
1761
- message = cast(AIMessageChunk, gen.message)
1961
+ gen = cast("ChatGenerationChunk", _chat_result.generations[0])
1962
+ message = cast("AIMessageChunk", gen.message)
1762
1963
 
1763
1964
  prev_usage_metadata = (
1764
1965
  message.usage_metadata
@@ -1783,12 +1984,13 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1783
1984
  generation_config: Optional[Dict[str, Any]] = None,
1784
1985
  cached_content: Optional[str] = None,
1785
1986
  **kwargs: Any,
1786
- ) -> Tuple[GenerateContentRequest, Dict[str, Any]]:
1987
+ ) -> GenerateContentRequest:
1787
1988
  if tool_choice and tool_config:
1788
- raise ValueError(
1989
+ msg = (
1789
1990
  "Must specify at most one of tool_choice and tool_config, received "
1790
1991
  f"both:\n\n{tool_choice=}\n\n{tool_config=}"
1791
1992
  )
1993
+ raise ValueError(msg)
1792
1994
 
1793
1995
  formatted_tools = None
1794
1996
  code_execution_tool = GoogleTool(code_execution=CodeExecution())
@@ -1809,10 +2011,13 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1809
2011
  filtered_messages.append(message)
1810
2012
  messages = filtered_messages
1811
2013
 
1812
- system_instruction, history = _parse_chat_history(
1813
- messages,
1814
- convert_system_message_to_human=self.convert_system_message_to_human,
1815
- )
2014
+ if self.convert_system_message_to_human:
2015
+ system_instruction, history = _parse_chat_history(
2016
+ messages,
2017
+ convert_system_message_to_human=self.convert_system_message_to_human,
2018
+ )
2019
+ else:
2020
+ system_instruction, history = _parse_chat_history(messages)
1816
2021
  if tool_choice:
1817
2022
  if not formatted_tools:
1818
2023
  msg = (
@@ -1823,16 +2028,15 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1823
2028
  all_names: List[str] = []
1824
2029
  for t in formatted_tools:
1825
2030
  if hasattr(t, "function_declarations"):
1826
- t_with_declarations = cast(Any, t)
2031
+ t_with_declarations = cast("Any", t)
1827
2032
  all_names.extend(
1828
2033
  f.name for f in t_with_declarations.function_declarations
1829
2034
  )
1830
2035
  elif isinstance(t, GoogleTool) and hasattr(t, "code_execution"):
1831
2036
  continue
1832
2037
  else:
1833
- raise TypeError(
1834
- f"Tool {t} doesn't have function_declarations attribute"
1835
- )
2038
+ msg = f"Tool {t} doesn't have function_declarations attribute"
2039
+ raise TypeError(msg)
1836
2040
 
1837
2041
  tool_config = _tool_choice_to_tool_config(tool_choice, all_names)
1838
2042
 
@@ -1891,7 +2095,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1891
2095
  ) -> Runnable[LanguageModelInput, Union[Dict, BaseModel]]:
1892
2096
  _ = kwargs.pop("strict", None)
1893
2097
  if kwargs:
1894
- raise ValueError(f"Received unsupported arguments {kwargs}")
2098
+ msg = f"Received unsupported arguments {kwargs}"
2099
+ raise ValueError(msg)
1895
2100
 
1896
2101
  parser: OutputParserLike
1897
2102
 
@@ -1908,7 +2113,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1908
2113
  elif isinstance(schema, dict):
1909
2114
  schema_json = schema
1910
2115
  else:
1911
- raise ValueError(f"Unsupported schema type {type(schema)}")
2116
+ msg = f"Unsupported schema type {type(schema)}"
2117
+ raise ValueError(msg)
1912
2118
  parser = JsonOutputParser()
1913
2119
 
1914
2120
  # Resolve refs in schema because they are not supported
@@ -1951,8 +2157,7 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1951
2157
  exception_key="parsing_error",
1952
2158
  )
1953
2159
  return {"raw": llm} | parser_with_fallback
1954
- else:
1955
- return llm | parser
2160
+ return llm | parser
1956
2161
 
1957
2162
  def bind_tools(
1958
2163
  self,
@@ -1970,20 +2175,21 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
1970
2175
 
1971
2176
  Args:
1972
2177
  tools: A list of tool definitions to bind to this chat model.
1973
- Can be a pydantic model, callable, or BaseTool. Pydantic
1974
- models, callables, and BaseTools will be automatically converted to
1975
- their schema dictionary representation. Tools with Union types in
1976
- 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.
1977
2182
  **kwargs: Any additional parameters to pass to the
1978
2183
  :class:`~langchain.runnable.Runnable` constructor.
1979
2184
  """
1980
2185
  if tool_choice and tool_config:
1981
- raise ValueError(
2186
+ msg = (
1982
2187
  "Must specify at most one of tool_choice and tool_config, received "
1983
2188
  f"both:\n\n{tool_choice=}\n\n{tool_config=}"
1984
2189
  )
2190
+ raise ValueError(msg)
1985
2191
  try:
1986
- 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]
1987
2193
  except Exception:
1988
2194
  formatted_tools = [
1989
2195
  tool_to_dict(convert_to_genai_function_declarations(tools))
@@ -2010,9 +2216,8 @@ def _get_tool_name(
2010
2216
  ) -> str:
2011
2217
  try:
2012
2218
  genai_tool = tool_to_dict(convert_to_genai_function_declarations([tool]))
2013
- return [f["name"] for f in genai_tool["function_declarations"]][0] # type: ignore[index]
2014
- 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
2015
2221
  if is_typeddict(tool):
2016
- return convert_to_openai_tool(cast(Dict, tool))["function"]["name"]
2017
- else:
2018
- raise e
2222
+ return convert_to_openai_tool(cast("Dict", tool))["function"]["name"]
2223
+ raise