langchain-google-genai 1.0.2__tar.gz → 1.0.3__tar.gz

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.

Files changed (16) hide show
  1. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/PKG-INFO +3 -3
  2. langchain_google_genai-1.0.3/langchain_google_genai/_function_utils.py +241 -0
  3. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/chat_models.py +185 -25
  4. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/embeddings.py +6 -0
  5. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/llms.py +8 -0
  6. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/pyproject.toml +4 -4
  7. langchain_google_genai-1.0.2/langchain_google_genai/_function_utils.py +0 -116
  8. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/LICENSE +0 -0
  9. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/README.md +0 -0
  10. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/__init__.py +0 -0
  11. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/_common.py +0 -0
  12. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/_enums.py +0 -0
  13. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/_genai_extension.py +0 -0
  14. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/genai_aqa.py +0 -0
  15. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/google_vector_store.py +0 -0
  16. {langchain_google_genai-1.0.2 → langchain_google_genai-1.0.3}/langchain_google_genai/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-google-genai
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: An integration package connecting Google's genai package and LangChain
5
5
  Home-page: https://github.com/langchain-ai/langchain-google
6
6
  License: MIT
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Provides-Extra: images
15
- Requires-Dist: google-generativeai (>=0.5.0,<0.6.0)
16
- Requires-Dist: langchain-core (>=0.1.27,<0.2)
15
+ Requires-Dist: google-generativeai (>=0.5.2,<0.6.0)
16
+ Requires-Dist: langchain-core (>=0.1.45,<0.2)
17
17
  Requires-Dist: pillow (>=10.1.0,<11.0.0) ; extra == "images"
18
18
  Project-URL: Repository, https://github.com/langchain-ai/langchain-google
19
19
  Project-URL: Source Code, https://github.com/langchain-ai/langchain-google/tree/main/libs/genai
@@ -0,0 +1,241 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import (
4
+ Any,
5
+ Dict,
6
+ List,
7
+ Literal,
8
+ Optional,
9
+ Sequence,
10
+ Type,
11
+ TypedDict,
12
+ Union,
13
+ )
14
+
15
+ import google.ai.generativelanguage as glm
16
+ from google.generativeai.types import Tool as GoogleTool # type: ignore[import]
17
+ from google.generativeai.types.content_types import ( # type: ignore[import]
18
+ FunctionCallingConfigType,
19
+ FunctionDeclarationType,
20
+ ToolDict,
21
+ ToolType,
22
+ )
23
+ from langchain_core.pydantic_v1 import BaseModel
24
+ from langchain_core.tools import BaseTool
25
+ from langchain_core.tools import tool as callable_as_lc_tool
26
+ from langchain_core.utils.json_schema import dereference_refs
27
+
28
+ TYPE_ENUM = {
29
+ "string": glm.Type.STRING,
30
+ "number": glm.Type.NUMBER,
31
+ "integer": glm.Type.INTEGER,
32
+ "boolean": glm.Type.BOOLEAN,
33
+ "array": glm.Type.ARRAY,
34
+ "object": glm.Type.OBJECT,
35
+ }
36
+
37
+ TYPE_ENUM_REVERSE = {v: k for k, v in TYPE_ENUM.items()}
38
+
39
+
40
+ def convert_to_genai_function_declarations(
41
+ tool: Union[
42
+ GoogleTool, ToolDict, FunctionDeclarationType, Sequence[FunctionDeclarationType]
43
+ ],
44
+ ) -> ToolType:
45
+ """Convert any tool-like object to a ToolType.
46
+
47
+ https://github.com/google-gemini/generative-ai-python/blob/668695ebe3e9de496a36eeb95cb2ed2faba9b939/google/generativeai/types/content_types.py#L574
48
+ """
49
+ if isinstance(tool, GoogleTool):
50
+ return tool
51
+ # check whether a dict is supported by glm, otherwise we parse it explicitly
52
+ if isinstance(tool, dict):
53
+ first_function_declaration = tool.get("function_declarations", [None])[0]
54
+ if isinstance(first_function_declaration, glm.FunctionDeclaration):
55
+ return tool
56
+ schema = None
57
+ try:
58
+ schema = first_function_declaration.parameters
59
+ except AttributeError:
60
+ pass
61
+ if schema is None:
62
+ schema = first_function_declaration.get("parameters")
63
+ if schema is None or isinstance(schema, glm.Schema):
64
+ return tool
65
+ return glm.Tool(
66
+ function_declarations=[
67
+ _convert_to_genai_function(fc) for fc in tool["function_declarations"]
68
+ ],
69
+ )
70
+ elif isinstance(tool, type) and issubclass(tool, BaseModel):
71
+ return glm.Tool(function_declarations=[_convert_to_genai_function(tool)])
72
+ elif callable(tool):
73
+ return _convert_tool_to_genai_function(callable_as_lc_tool()(tool))
74
+ elif isinstance(tool, list):
75
+ return glm.Tool(
76
+ function_declarations=[_convert_to_genai_function(fc) for fc in tool]
77
+ )
78
+ return glm.Tool(function_declarations=[_convert_to_genai_function(tool)])
79
+
80
+
81
+ def tool_to_dict(tool: Union[glm.Tool, GoogleTool]) -> ToolDict:
82
+ if isinstance(tool, GoogleTool):
83
+ tool = tool._proto
84
+ function_declarations = []
85
+ for function_declaration_proto in tool.function_declarations:
86
+ properties: Dict[str, Any] = {}
87
+ for property in function_declaration_proto.parameters.properties:
88
+ property_type = function_declaration_proto.parameters.properties[
89
+ property
90
+ ].type
91
+ property_dict = {"type": TYPE_ENUM_REVERSE[property_type]}
92
+ property_description = function_declaration_proto.parameters.properties[
93
+ property
94
+ ].description
95
+ if property_description:
96
+ property_dict["description"] = property_description
97
+ properties[property] = property_dict
98
+ function_declaration = {
99
+ "name": function_declaration_proto.name,
100
+ "description": function_declaration_proto.description,
101
+ "parameters": {"type": "object", "properties": properties},
102
+ }
103
+ if function_declaration_proto.parameters.required:
104
+ function_declaration["parameters"][ # type: ignore[index]
105
+ "required"
106
+ ] = function_declaration_proto.parameters.required
107
+ function_declarations.append(function_declaration)
108
+ return {"function_declarations": function_declarations}
109
+
110
+
111
+ def _convert_to_genai_function(fc: FunctionDeclarationType) -> glm.FunctionDeclaration:
112
+ if isinstance(fc, BaseTool):
113
+ return _convert_tool_to_genai_function(fc)
114
+ elif isinstance(fc, type) and issubclass(fc, BaseModel):
115
+ return _convert_pydantic_to_genai_function(fc)
116
+ elif callable(fc):
117
+ return _convert_tool_to_genai_function(callable_as_lc_tool()(fc))
118
+ elif isinstance(fc, dict):
119
+ return glm.FunctionDeclaration(
120
+ name=fc["name"],
121
+ description=fc.get("description"),
122
+ parameters={
123
+ "properties": {
124
+ k: {
125
+ "type_": TYPE_ENUM[v["type"]],
126
+ "description": v.get("description"),
127
+ }
128
+ for k, v in fc["parameters"]["properties"].items()
129
+ },
130
+ "required": fc["parameters"].get("required", []),
131
+ "type_": TYPE_ENUM[fc["parameters"]["type"]],
132
+ },
133
+ )
134
+ else:
135
+ raise ValueError(f"Unsupported function call type {fc}")
136
+
137
+
138
+ def _convert_tool_to_genai_function(tool: BaseTool) -> glm.FunctionDeclaration:
139
+ if tool.args_schema:
140
+ schema = dereference_refs(tool.args_schema.schema())
141
+ schema.pop("definitions", None)
142
+ return glm.FunctionDeclaration(
143
+ name=tool.name or schema["title"],
144
+ description=tool.description or schema["description"],
145
+ parameters={
146
+ "properties": {
147
+ k: {
148
+ "type_": TYPE_ENUM[v["type"]],
149
+ "description": v.get("description"),
150
+ }
151
+ for k, v in schema["properties"].items()
152
+ },
153
+ "required": schema.get("required", []),
154
+ "type_": TYPE_ENUM[schema["type"]],
155
+ },
156
+ )
157
+ else:
158
+ return glm.FunctionDeclaration(
159
+ name=tool.name,
160
+ description=tool.description,
161
+ parameters={
162
+ "properties": {
163
+ "__arg1": {"type_": TYPE_ENUM["string"]},
164
+ },
165
+ "required": ["__arg1"],
166
+ "type_": TYPE_ENUM["object"],
167
+ },
168
+ )
169
+
170
+
171
+ def _convert_pydantic_to_genai_function(
172
+ pydantic_model: Type[BaseModel],
173
+ ) -> glm.FunctionDeclaration:
174
+ schema = dereference_refs(pydantic_model.schema())
175
+ schema.pop("definitions", None)
176
+ return glm.FunctionDeclaration(
177
+ name=schema["title"],
178
+ description=schema.get("description", ""),
179
+ parameters={
180
+ "properties": {
181
+ k: {
182
+ "type_": TYPE_ENUM[v["type"]],
183
+ "description": v.get("description"),
184
+ }
185
+ for k, v in schema["properties"].items()
186
+ },
187
+ "required": schema["required"],
188
+ "type_": TYPE_ENUM[schema["type"]],
189
+ },
190
+ )
191
+
192
+
193
+ _ToolChoiceType = Union[
194
+ dict, List[str], str, Literal["auto", "none", "any"], Literal[True]
195
+ ]
196
+
197
+
198
+ class _ToolConfigDict(TypedDict):
199
+ function_calling_config: FunctionCallingConfigType
200
+
201
+
202
+ def _tool_choice_to_tool_config(
203
+ tool_choice: _ToolChoiceType,
204
+ all_names: List[str],
205
+ ) -> _ToolConfigDict:
206
+ allowed_function_names: Optional[List[str]] = None
207
+ if tool_choice is True or tool_choice == "any":
208
+ mode = "any"
209
+ allowed_function_names = all_names
210
+ elif tool_choice == "auto":
211
+ mode = "auto"
212
+ elif tool_choice == "none":
213
+ mode = "none"
214
+ elif isinstance(tool_choice, str):
215
+ mode = "any"
216
+ allowed_function_names = [tool_choice]
217
+ elif isinstance(tool_choice, list):
218
+ mode = "any"
219
+ allowed_function_names = tool_choice
220
+ elif isinstance(tool_choice, dict):
221
+ if "mode" in tool_choice:
222
+ mode = tool_choice["mode"]
223
+ allowed_function_names = tool_choice.get("allowed_function_names")
224
+ elif "function_calling_config" in tool_choice:
225
+ mode = tool_choice["function_calling_config"]["mode"]
226
+ allowed_function_names = tool_choice["function_calling_config"].get(
227
+ "allowed_function_names"
228
+ )
229
+ else:
230
+ raise ValueError(
231
+ f"Unrecognized tool choice format:\n\n{tool_choice=}\n\nShould match "
232
+ f"Google GenerativeAI ToolConfig or FunctionCallingConfig format."
233
+ )
234
+ else:
235
+ raise ValueError(f"Unrecognized tool choice format:\n\n{tool_choice=}")
236
+ return _ToolConfigDict(
237
+ function_calling_config={
238
+ "mode": mode,
239
+ "allowed_function_names": allowed_function_names,
240
+ }
241
+ )
@@ -4,6 +4,7 @@ import base64
4
4
  import json
5
5
  import logging
6
6
  import os
7
+ import uuid
7
8
  import warnings
8
9
  from io import BytesIO
9
10
  from typing import (
@@ -29,10 +30,17 @@ import google.api_core
29
30
  import google.generativeai as genai # type: ignore[import]
30
31
  import proto # type: ignore[import]
31
32
  import requests
33
+ from google.generativeai.types import SafetySettingDict # type: ignore[import]
34
+ from google.generativeai.types import Tool as GoogleTool # type: ignore[import]
35
+ from google.generativeai.types.content_types import ( # type: ignore[import]
36
+ FunctionDeclarationType,
37
+ ToolDict,
38
+ )
32
39
  from langchain_core.callbacks.manager import (
33
40
  AsyncCallbackManagerForLLMRun,
34
41
  CallbackManagerForLLMRun,
35
42
  )
43
+ from langchain_core.language_models import LanguageModelInput
36
44
  from langchain_core.language_models.chat_models import BaseChatModel
37
45
  from langchain_core.messages import (
38
46
  AIMessage,
@@ -40,10 +48,16 @@ from langchain_core.messages import (
40
48
  BaseMessage,
41
49
  FunctionMessage,
42
50
  HumanMessage,
51
+ InvalidToolCall,
43
52
  SystemMessage,
53
+ ToolCall,
54
+ ToolCallChunk,
55
+ ToolMessage,
44
56
  )
57
+ from langchain_core.output_parsers.openai_tools import parse_tool_calls
45
58
  from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
46
59
  from langchain_core.pydantic_v1 import SecretStr, root_validator
60
+ from langchain_core.runnables import Runnable
47
61
  from langchain_core.utils import get_from_dict_or_env
48
62
  from tenacity import (
49
63
  before_sleep_log,
@@ -55,7 +69,11 @@ from tenacity import (
55
69
 
56
70
  from langchain_google_genai._common import GoogleGenerativeAIError
57
71
  from langchain_google_genai._function_utils import (
72
+ _tool_choice_to_tool_config,
73
+ _ToolChoiceType,
74
+ _ToolConfigDict,
58
75
  convert_to_genai_function_declarations,
76
+ tool_to_dict,
59
77
  )
60
78
  from langchain_google_genai.llms import GoogleModelFamily, _BaseGoogleGenerativeAI
61
79
 
@@ -350,6 +368,40 @@ def _parse_chat_history(
350
368
  )
351
369
  )
352
370
  ]
371
+ elif isinstance(message, ToolMessage):
372
+ role = "user"
373
+ prev_message: Optional[BaseMessage] = (
374
+ input_messages[i - 1] if i > 0 else None
375
+ )
376
+ if (
377
+ prev_message
378
+ and isinstance(prev_message, AIMessage)
379
+ and prev_message.tool_calls
380
+ ):
381
+ # message.name can be null for ToolMessage
382
+ name: str = prev_message.tool_calls[0]["name"]
383
+ else:
384
+ name = message.name # type: ignore
385
+ tool_response: Any
386
+ if not isinstance(message.content, str):
387
+ tool_response = message.content
388
+ else:
389
+ try:
390
+ tool_response = json.loads(message.content)
391
+ except json.JSONDecodeError:
392
+ tool_response = message.content # leave as str representation
393
+ parts = [
394
+ glm.Part(
395
+ function_response=glm.FunctionResponse(
396
+ name=name,
397
+ response=(
398
+ {"output": tool_response}
399
+ if not isinstance(tool_response, dict)
400
+ else tool_response
401
+ ),
402
+ )
403
+ )
404
+ ]
353
405
  else:
354
406
  raise ValueError(
355
407
  f"Unexpected message with type {type(message)} at the position {i}."
@@ -360,24 +412,89 @@ def _parse_chat_history(
360
412
 
361
413
 
362
414
  def _parse_response_candidate(
363
- response_candidate: glm.Candidate, stream: bool
415
+ response_candidate: glm.Candidate, streaming: bool = False
364
416
  ) -> AIMessage:
365
- first_part = response_candidate.content.parts[0]
366
- if first_part.function_call:
367
- function_call = proto.Message.to_dict(first_part.function_call)
368
- function_call["arguments"] = json.dumps(function_call.pop("args", {}))
369
- return (AIMessageChunk if stream else AIMessage)(
370
- content="", additional_kwargs={"function_call": function_call}
417
+ content: Union[None, str, List[str]] = None
418
+ additional_kwargs = {}
419
+ tool_calls = []
420
+ invalid_tool_calls = []
421
+ tool_call_chunks = []
422
+
423
+ for part in response_candidate.content.parts:
424
+ try:
425
+ text: Optional[str] = part.text
426
+ except AttributeError:
427
+ text = None
428
+
429
+ if text is not None:
430
+ if not content:
431
+ content = text
432
+ elif isinstance(content, str) and text:
433
+ content = [content, text]
434
+ elif isinstance(content, list) and text:
435
+ content.append(text)
436
+ elif text:
437
+ raise Exception("Unexpected content type")
438
+
439
+ if part.function_call:
440
+ # TODO: support multiple function calls
441
+ if "function_call" in additional_kwargs:
442
+ raise Exception("Multiple function calls are not currently supported")
443
+ function_call = {"name": part.function_call.name}
444
+ # dump to match other function calling llm for now
445
+ function_call_args_dict = proto.Message.to_dict(part.function_call)["args"]
446
+ function_call["arguments"] = json.dumps(
447
+ {k: function_call_args_dict[k] for k in function_call_args_dict}
448
+ )
449
+ additional_kwargs["function_call"] = function_call
450
+
451
+ if streaming:
452
+ tool_call_chunks.append(
453
+ ToolCallChunk(
454
+ name=function_call.get("name"),
455
+ args=function_call.get("arguments"),
456
+ id=function_call.get("id", str(uuid.uuid4())),
457
+ index=function_call.get("index"), # type: ignore
458
+ )
459
+ )
460
+ else:
461
+ try:
462
+ tool_calls_dicts = parse_tool_calls(
463
+ [{"function": function_call}],
464
+ return_id=False,
465
+ )
466
+ tool_calls = [
467
+ ToolCall(
468
+ name=tool_call["name"],
469
+ args=tool_call["args"],
470
+ id=tool_call.get("id", str(uuid.uuid4())),
471
+ )
472
+ for tool_call in tool_calls_dicts
473
+ ]
474
+ except Exception as e:
475
+ invalid_tool_calls = [
476
+ InvalidToolCall(
477
+ name=function_call.get("name"),
478
+ args=function_call.get("arguments"),
479
+ id=function_call.get("id", str(uuid.uuid4())),
480
+ error=str(e),
481
+ )
482
+ ]
483
+ if content is None:
484
+ content = ""
485
+
486
+ if streaming:
487
+ return AIMessageChunk(
488
+ content=cast(Union[str, List[Union[str, Dict[Any, Any]]]], content),
489
+ additional_kwargs=additional_kwargs,
490
+ tool_call_chunks=tool_call_chunks,
371
491
  )
372
- else:
373
- parts = response_candidate.content.parts
374
-
375
- if len(parts) == 1 and parts[0].text:
376
- content: Union[str, List[Union[str, Dict]]] = parts[0].text
377
- else:
378
- content = [proto.Message.to_dict(part) for part in parts]
379
- return (AIMessageChunk if stream else AIMessage)(
380
- content=content, additional_kwargs={}
492
+
493
+ return AIMessage(
494
+ content=cast(Union[str, List[Union[str, Dict[Any, Any]]]], content),
495
+ additional_kwargs=additional_kwargs,
496
+ tool_calls=tool_calls,
497
+ invalid_tool_calls=invalid_tool_calls,
381
498
  )
382
499
 
383
500
 
@@ -400,7 +517,7 @@ def _response_to_result(
400
517
  ]
401
518
  generations.append(
402
519
  (ChatGenerationChunk if stream else ChatGeneration)(
403
- message=_parse_response_candidate(candidate, stream=stream),
520
+ message=_parse_response_candidate(candidate, streaming=stream),
404
521
  generation_info=generation_info,
405
522
  )
406
523
  )
@@ -627,20 +744,29 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
627
744
  self,
628
745
  messages: List[BaseMessage],
629
746
  stop: Optional[List[str]] = None,
747
+ tools: Optional[Sequence[Union[ToolDict, GoogleTool]]] = None,
748
+ functions: Optional[Sequence[FunctionDeclarationType]] = None,
749
+ safety_settings: Optional[SafetySettingDict] = None,
750
+ tool_config: Optional[Union[Dict, _ToolConfigDict]] = None,
630
751
  **kwargs: Any,
631
752
  ) -> Tuple[Dict[str, Any], genai.ChatSession, genai.types.ContentDict]:
632
753
  client = self.client
633
- functions = kwargs.pop("functions", None)
634
- safety_settings = kwargs.pop("safety_settings", self.safety_settings)
635
- if functions or safety_settings:
636
- tools = (
637
- convert_to_genai_function_declarations(functions) if functions else None
638
- )
754
+ formatted_tools = None
755
+ if tools:
756
+ formatted_tools = [
757
+ convert_to_genai_function_declarations(tool) for tool in tools
758
+ ]
759
+ elif functions:
760
+ formatted_tools = [convert_to_genai_function_declarations(functions)]
761
+
762
+ if formatted_tools or safety_settings:
639
763
  client = genai.GenerativeModel(
640
- model_name=self.model, tools=tools, safety_settings=safety_settings
764
+ model_name=self.model,
765
+ tools=formatted_tools,
766
+ safety_settings=safety_settings,
641
767
  )
642
768
 
643
- params = self._prepare_params(stop, **kwargs)
769
+ params = self._prepare_params(stop, tool_config=tool_config, **kwargs)
644
770
  system_instruction, history = _parse_chat_history(
645
771
  messages,
646
772
  convert_system_message_to_human=self.convert_system_message_to_human,
@@ -672,3 +798,37 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
672
798
  token_count = result["token_count"]
673
799
 
674
800
  return token_count
801
+
802
+ def bind_tools(
803
+ self,
804
+ tools: Sequence[Union[ToolDict, GoogleTool]],
805
+ tool_config: Optional[Union[Dict, _ToolConfigDict]] = None,
806
+ *,
807
+ tool_choice: Optional[Union[_ToolChoiceType, bool]] = None,
808
+ **kwargs: Any,
809
+ ) -> Runnable[LanguageModelInput, BaseMessage]:
810
+ """Bind tool-like objects to this chat model.
811
+
812
+ Assumes model is compatible with google-generativeAI tool-calling API.
813
+
814
+ Args:
815
+ tools: A list of tool definitions to bind to this chat model.
816
+ Can be a pydantic model, callable, or BaseTool. Pydantic
817
+ models, callables, and BaseTools will be automatically converted to
818
+ their schema dictionary representation.
819
+ **kwargs: Any additional parameters to pass to the
820
+ :class:`~langchain.runnable.Runnable` constructor.
821
+ """
822
+ if tool_choice and tool_config:
823
+ raise ValueError(
824
+ "Must specify at most one of tool_choice and tool_config, received "
825
+ f"both:\n\n{tool_choice=}\n\n{tool_config=}"
826
+ )
827
+ # Bind dicts for easier serialization/deserialization.
828
+ genai_tools = [tool_to_dict(convert_to_genai_function_declarations(tools))]
829
+ if tool_choice:
830
+ all_names = [
831
+ f["name"] for t in genai_tools for f in t["function_declarations"]
832
+ ]
833
+ tool_config = _tool_choice_to_tool_config(tool_choice, all_names)
834
+ return self.bind(tools=genai_tools, tool_config=tool_config, **kwargs)
@@ -61,6 +61,11 @@ class GoogleGenerativeAIEmbeddings(BaseModel, Embeddings):
61
61
  None,
62
62
  description="A string, one of: [`rest`, `grpc`, `grpc_asyncio`].",
63
63
  )
64
+ request_options: Optional[Dict] = Field(
65
+ None,
66
+ description="A dictionary of request options to pass to the Google API client."
67
+ "Example: `{'timeout': 10}`",
68
+ )
64
69
 
65
70
  @root_validator()
66
71
  def validate_environment(cls, values: Dict) -> Dict:
@@ -95,6 +100,7 @@ class GoogleGenerativeAIEmbeddings(BaseModel, Embeddings):
95
100
  content=texts,
96
101
  task_type=task_type,
97
102
  title=title,
103
+ request_options=self.request_options,
98
104
  )
99
105
  except Exception as e:
100
106
  raise GoogleGenerativeAIError(f"Error embedding content: {e}") from e
@@ -86,6 +86,7 @@ def _completion_with_retry(
86
86
  stream=stream,
87
87
  generation_config=generation_config,
88
88
  safety_settings=kwargs.pop("safety_settings", None),
89
+ request_options={"timeout": llm.timeout} if llm.timeout else None,
89
90
  )
90
91
  return llm.client.generate_text(prompt=prompt, **kwargs)
91
92
  except google.api_core.exceptions.FailedPrecondition as exc:
@@ -143,6 +144,10 @@ Supported examples:
143
144
  not return the full n completions if duplicates are generated."""
144
145
  max_retries: int = 6
145
146
  """The maximum number of retries to make when generating."""
147
+
148
+ timeout: Optional[float] = None
149
+ """The maximum number of seconds to wait for a response."""
150
+
146
151
  client_options: Optional[Dict] = Field(
147
152
  None,
148
153
  description=(
@@ -259,6 +264,9 @@ class GoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseLLM):
259
264
  if values["max_output_tokens"] is not None and values["max_output_tokens"] <= 0:
260
265
  raise ValueError("max_output_tokens must be greater than zero")
261
266
 
267
+ if values["timeout"] is not None and values["timeout"] <= 0:
268
+ raise ValueError("timeout must be greater than zero")
269
+
262
270
  return values
263
271
 
264
272
  def _generate(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langchain-google-genai"
3
- version = "1.0.2"
3
+ version = "1.0.3"
4
4
  description = "An integration package connecting Google's genai package and LangChain"
5
5
  authors = []
6
6
  readme = "README.md"
@@ -12,8 +12,8 @@ license = "MIT"
12
12
 
13
13
  [tool.poetry.dependencies]
14
14
  python = ">=3.9,<4.0"
15
- langchain-core = ">=0.1.27,<0.2"
16
- google-generativeai = "^0.5.0"
15
+ langchain-core = ">=0.1.45,<0.2"
16
+ google-generativeai = "^0.5.2"
17
17
  pillow = { version = "^10.1.0", optional = true }
18
18
 
19
19
  [tool.poetry.extras]
@@ -96,7 +96,7 @@ build-backend = "poetry.core.masonry.api"
96
96
  #
97
97
  # https://github.com/tophat/syrupy
98
98
  # --snapshot-warn-unused Prints a warning on unused snapshots rather than fail the test suite.
99
- addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5"
99
+ #addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5"
100
100
  # Registering custom markers.
101
101
  # https://docs.pytest.org/en/7.1.x/example/markers.html#registering-markers
102
102
  markers = [
@@ -1,116 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import (
4
- Dict,
5
- List,
6
- Type,
7
- Union,
8
- )
9
-
10
- import google.ai.generativelanguage as glm
11
- from langchain_core.pydantic_v1 import BaseModel
12
- from langchain_core.tools import BaseTool
13
- from langchain_core.utils.json_schema import dereference_refs
14
-
15
- FunctionCallType = Union[BaseTool, Type[BaseModel], Dict]
16
-
17
- TYPE_ENUM = {
18
- "string": glm.Type.STRING,
19
- "number": glm.Type.NUMBER,
20
- "integer": glm.Type.INTEGER,
21
- "boolean": glm.Type.BOOLEAN,
22
- "array": glm.Type.ARRAY,
23
- "object": glm.Type.OBJECT,
24
- }
25
-
26
-
27
- def convert_to_genai_function_declarations(
28
- function_calls: List[FunctionCallType],
29
- ) -> List[glm.Tool]:
30
- return [
31
- glm.Tool(
32
- function_declarations=[_convert_to_genai_function(fc)],
33
- )
34
- for fc in function_calls
35
- ]
36
-
37
-
38
- def _convert_to_genai_function(fc: FunctionCallType) -> glm.FunctionDeclaration:
39
- if isinstance(fc, BaseTool):
40
- return _convert_tool_to_genai_function(fc)
41
- elif isinstance(fc, type) and issubclass(fc, BaseModel):
42
- return _convert_pydantic_to_genai_function(fc)
43
- elif isinstance(fc, dict):
44
- return glm.FunctionDeclaration(
45
- name=fc["name"],
46
- description=fc.get("description"),
47
- parameters={
48
- "properties": {
49
- k: {
50
- "type_": TYPE_ENUM[v["type"]],
51
- "description": v.get("description"),
52
- }
53
- for k, v in fc["parameters"]["properties"].items()
54
- },
55
- "required": fc["parameters"].get("required", []),
56
- "type_": TYPE_ENUM[fc["parameters"]["type"]],
57
- },
58
- )
59
- else:
60
- raise ValueError(f"Unsupported function call type {fc}")
61
-
62
-
63
- def _convert_tool_to_genai_function(tool: BaseTool) -> glm.FunctionDeclaration:
64
- if tool.args_schema:
65
- schema = dereference_refs(tool.args_schema.schema())
66
- schema.pop("definitions", None)
67
-
68
- return glm.FunctionDeclaration(
69
- name=tool.name or schema["title"],
70
- description=tool.description or schema["description"],
71
- parameters={
72
- "properties": {
73
- k: {
74
- "type_": TYPE_ENUM[v["type"]],
75
- "description": v.get("description"),
76
- }
77
- for k, v in schema["properties"].items()
78
- },
79
- "required": schema["required"],
80
- "type_": TYPE_ENUM[schema["type"]],
81
- },
82
- )
83
- else:
84
- return glm.FunctionDeclaration(
85
- name=tool.name,
86
- description=tool.description,
87
- parameters={
88
- "properties": {
89
- "__arg1": {"type_": TYPE_ENUM["string"]},
90
- },
91
- "required": ["__arg1"],
92
- "type_": TYPE_ENUM["object"],
93
- },
94
- )
95
-
96
-
97
- def _convert_pydantic_to_genai_function(
98
- pydantic_model: Type[BaseModel],
99
- ) -> glm.FunctionDeclaration:
100
- schema = dereference_refs(pydantic_model.schema())
101
- schema.pop("definitions", None)
102
- return glm.FunctionDeclaration(
103
- name=schema["title"],
104
- description=schema.get("description", ""),
105
- parameters={
106
- "properties": {
107
- k: {
108
- "type_": TYPE_ENUM[v["type"]],
109
- "description": v.get("description"),
110
- }
111
- for k, v in schema["properties"].items()
112
- },
113
- "required": schema["required"],
114
- "type_": TYPE_ENUM[schema["type"]],
115
- },
116
- )