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.
- langchain_google_genai/__init__.py +3 -3
- langchain_google_genai/_common.py +28 -17
- langchain_google_genai/_function_utils.py +47 -56
- langchain_google_genai/_genai_extension.py +35 -21
- langchain_google_genai/_image_utils.py +10 -9
- langchain_google_genai/chat_models.py +406 -226
- langchain_google_genai/embeddings.py +11 -6
- langchain_google_genai/genai_aqa.py +15 -15
- langchain_google_genai/google_vector_store.py +26 -16
- langchain_google_genai/llms.py +8 -7
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-2.1.12.dist-info}/METADATA +38 -25
- langchain_google_genai-2.1.12.dist-info/RECORD +17 -0
- langchain_google_genai-2.1.11.dist-info/RECORD +0 -17
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-2.1.12.dist-info}/WHEEL +0 -0
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-2.1.12.dist-info}/entry_points.txt +0 -0
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-2.1.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
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
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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(
|
|
178
|
-
| retry_if_exception_type(
|
|
179
|
-
| retry_if_exception_type(
|
|
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
|
-
|
|
191
|
-
|
|
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
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
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
|
|
230
|
-
raise
|
|
231
|
-
except Exception
|
|
232
|
-
raise
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
418
|
-
|
|
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
|
-
|
|
468
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
556
|
-
|
|
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
|
-
|
|
578
|
+
if current_content is None:
|
|
571
579
|
return [new_item]
|
|
572
|
-
|
|
580
|
+
if isinstance(current_content, str):
|
|
573
581
|
return [current_content, new_item]
|
|
574
|
-
|
|
582
|
+
if isinstance(current_content, list):
|
|
575
583
|
current_content.append(new_item)
|
|
576
584
|
return current_content
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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
|
|
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={
|
|
881
|
-
|
|
882
|
-
|
|
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(
|
|
894
|
-
|
|
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={
|
|
909
|
-
|
|
910
|
-
|
|
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
|
|
926
|
-
The ``cached_content`` parameter accepts a cache name created
|
|
927
|
-
Below are two examples: caching a single file
|
|
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 ==
|
|
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 =
|
|
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=
|
|
1061
|
+
display_name="Cached Content",
|
|
954
1062
|
system_instruction=(
|
|
955
|
-
|
|
956
|
-
|
|
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 ==
|
|
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 ==
|
|
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=
|
|
1117
|
+
display_name="Cached Contents",
|
|
1010
1118
|
system_instruction=(
|
|
1011
|
-
|
|
1012
|
-
|
|
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(
|
|
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
|
-
[
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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(
|
|
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=
|
|
1103
|
-
punchline=
|
|
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
|
-
|
|
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",
|
|
1139
|
-
pdf_base64 = base64.b64encode(pdf_bytes).decode(
|
|
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
|
-
|
|
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",
|
|
1166
|
-
video_base64 = base64.b64encode(video_bytes).decode(
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
1216
|
-
audio_base64 = base64.b64encode(audio_bytes).decode(
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1506
|
-
|
|
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
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
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
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1859
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
|
2039
|
-
except ValueError
|
|
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
|
-
|
|
2043
|
-
raise e
|
|
2222
|
+
return convert_to_openai_tool(cast("Dict", tool))["function"]["name"]
|
|
2223
|
+
raise
|