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.
- langchain_google_genai/__init__.py +3 -3
- langchain_google_genai/_common.py +28 -17
- langchain_google_genai/_function_utils.py +59 -59
- langchain_google_genai/_genai_extension.py +35 -21
- langchain_google_genai/_image_utils.py +10 -9
- langchain_google_genai/chat_models.py +459 -254
- langchain_google_genai/embeddings.py +62 -15
- langchain_google_genai/genai_aqa.py +15 -15
- langchain_google_genai/google_vector_store.py +26 -16
- langchain_google_genai/llms.py +9 -8
- {langchain_google_genai-2.1.10.dist-info → langchain_google_genai-2.1.12.dist-info}/METADATA +43 -39
- langchain_google_genai-2.1.12.dist-info/RECORD +17 -0
- {langchain_google_genai-2.1.10.dist-info → langchain_google_genai-2.1.12.dist-info}/WHEEL +1 -1
- langchain_google_genai-2.1.12.dist-info/entry_points.txt +4 -0
- langchain_google_genai-2.1.10.dist-info/RECORD +0 -16
- {langchain_google_genai-2.1.10.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,
|
|
@@ -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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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(
|
|
184
|
-
| retry_if_exception_type(
|
|
185
|
-
| retry_if_exception_type(
|
|
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
|
-
|
|
197
|
-
|
|
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
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
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
|
|
236
|
-
raise
|
|
237
|
-
except Exception
|
|
238
|
-
raise
|
|
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
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
424
|
-
|
|
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
|
-
|
|
474
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
557
|
-
|
|
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
|
-
|
|
578
|
+
if current_content is None:
|
|
572
579
|
return [new_item]
|
|
573
|
-
|
|
580
|
+
if isinstance(current_content, str):
|
|
574
581
|
return [current_content, new_item]
|
|
575
|
-
|
|
582
|
+
if isinstance(current_content, list):
|
|
576
583
|
current_content.append(new_item)
|
|
577
584
|
return current_content
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
|
|
663
|
-
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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(
|
|
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
|
|
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={
|
|
868
|
-
|
|
869
|
-
|
|
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(
|
|
881
|
-
|
|
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={
|
|
896
|
-
|
|
897
|
-
|
|
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
|
|
913
|
-
The ``cached_content`` parameter accepts a cache name created
|
|
914
|
-
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``.
|
|
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 ==
|
|
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 =
|
|
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=
|
|
1061
|
+
display_name="Cached Content",
|
|
941
1062
|
system_instruction=(
|
|
942
|
-
|
|
943
|
-
|
|
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 ==
|
|
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 ==
|
|
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=
|
|
1117
|
+
display_name="Cached Contents",
|
|
997
1118
|
system_instruction=(
|
|
998
|
-
|
|
999
|
-
|
|
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(
|
|
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
|
-
[
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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(
|
|
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=
|
|
1090
|
-
punchline=
|
|
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
|
-
|
|
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",
|
|
1126
|
-
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")
|
|
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
|
-
|
|
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",
|
|
1153
|
-
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")
|
|
1154
1297
|
|
|
1155
1298
|
message = HumanMessage(
|
|
1156
1299
|
content=[
|
|
1157
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
1203
|
-
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")
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1487
|
-
|
|
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
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
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
|
-
|
|
1544
|
-
|
|
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
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
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
|
-
) ->
|
|
1987
|
+
) -> GenerateContentRequest:
|
|
1787
1988
|
if tool_choice and tool_config:
|
|
1788
|
-
|
|
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
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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
|
-
|
|
1834
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
|
2014
|
-
except ValueError
|
|
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
|
-
|
|
2018
|
-
raise e
|
|
2222
|
+
return convert_to_openai_tool(cast("Dict", tool))["function"]["name"]
|
|
2223
|
+
raise
|