langchain-google-genai 1.0.6__tar.gz → 1.0.7__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 (17) hide show
  1. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/PKG-INFO +3 -3
  2. langchain_google_genai-1.0.7/langchain_google_genai/_function_utils.py +340 -0
  3. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/chat_models.py +43 -6
  4. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/embeddings.py +2 -0
  5. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/llms.py +10 -3
  6. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/pyproject.toml +5 -4
  7. langchain_google_genai-1.0.6/langchain_google_genai/_function_utils.py +0 -237
  8. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/LICENSE +0 -0
  9. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/README.md +0 -0
  10. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/__init__.py +0 -0
  11. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/_common.py +0 -0
  12. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/_enums.py +0 -0
  13. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/_genai_extension.py +0 -0
  14. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/_image_utils.py +0 -0
  15. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/genai_aqa.py +0 -0
  16. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/langchain_google_genai/google_vector_store.py +0 -0
  17. {langchain_google_genai-1.0.6 → langchain_google_genai-1.0.7}/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.6
3
+ Version: 1.0.7
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.2,<0.6.0)
16
- Requires-Dist: langchain-core (>=0.2.2,<0.3)
15
+ Requires-Dist: google-generativeai (>=0.7.0,<0.8.0)
16
+ Requires-Dist: langchain-core (>=0.2.9,<0.3)
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,340 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import (
4
+ Any,
5
+ Callable,
6
+ Collection,
7
+ Dict,
8
+ List,
9
+ Literal,
10
+ Optional,
11
+ Sequence,
12
+ Type,
13
+ TypedDict,
14
+ Union,
15
+ cast,
16
+ )
17
+
18
+ import google.ai.generativelanguage as glm
19
+ from google.ai.generativelanguage import FunctionCallingConfig, FunctionDeclaration
20
+ from google.ai.generativelanguage import Tool as GoogleTool
21
+ from google.generativeai.types.content_types import ToolDict # type: ignore[import]
22
+ from langchain_core.pydantic_v1 import BaseModel
23
+ from langchain_core.tools import BaseTool
24
+ from langchain_core.tools import tool as callable_as_lc_tool
25
+ from langchain_core.utils.json_schema import dereference_refs
26
+
27
+ TYPE_ENUM = {
28
+ "string": glm.Type.STRING,
29
+ "number": glm.Type.NUMBER,
30
+ "integer": glm.Type.INTEGER,
31
+ "boolean": glm.Type.BOOLEAN,
32
+ "array": glm.Type.ARRAY,
33
+ "object": glm.Type.OBJECT,
34
+ }
35
+
36
+ TYPE_ENUM_REVERSE = {v: k for k, v in TYPE_ENUM.items()}
37
+
38
+
39
+ class _ToolDictLike(TypedDict):
40
+ function_declarations: _FunctionDeclarationLikeList
41
+
42
+
43
+ class _FunctionDeclarationDict(TypedDict):
44
+ name: str
45
+ description: str
46
+ parameters: Dict[str, Collection[str]]
47
+
48
+
49
+ class _ToolDict(TypedDict):
50
+ function_declarations: Sequence[_FunctionDeclarationDict]
51
+
52
+
53
+ # Info: This is a FunctionDeclaration(=fc).
54
+ _FunctionDeclarationLike = Union[
55
+ BaseTool, Type[BaseModel], FunctionDeclaration, Callable, Dict[str, Any]
56
+ ]
57
+
58
+ # Info: This mean one tool.
59
+ _FunctionDeclarationLikeList = Sequence[_FunctionDeclarationLike]
60
+
61
+
62
+ # Info: This means one tool=Sequence of FunctionDeclaration
63
+ # The dict should be GoogleTool like. {"function_declarations": [ { "name": ...}.
64
+ # OpenAI like dict is not be accepted. {{'type': 'function', 'function': {'name': ...}
65
+ _ToolsType = Union[
66
+ GoogleTool,
67
+ ToolDict,
68
+ _ToolDictLike,
69
+ _FunctionDeclarationLikeList,
70
+ _FunctionDeclarationLike,
71
+ ]
72
+
73
+
74
+ #
75
+ # Info: GoogleTool means function_declarations and proto.Message.
76
+ def convert_to_genai_function_declarations(
77
+ tool: _ToolsType,
78
+ ) -> GoogleTool:
79
+ if isinstance(tool, list):
80
+ # multiple _FunctionDeclarationLike
81
+ return GoogleTool(
82
+ function_declarations=_convert_fc_likes_to_genai_function(tool)
83
+ )
84
+ elif isinstance(tool, (BaseTool, FunctionDeclaration)):
85
+ # single _FunctionDeclarationLike
86
+ return GoogleTool(
87
+ function_declarations=[_convert_fc_like_to_genai_function(tool)]
88
+ )
89
+ elif isinstance(tool, type) and issubclass(tool, BaseModel):
90
+ # single _FunctionDeclarationLike
91
+ return GoogleTool(
92
+ function_declarations=[_convert_fc_like_to_genai_function(tool)]
93
+ )
94
+ elif isinstance(tool, GoogleTool):
95
+ return cast(GoogleTool, tool)
96
+ elif callable(tool):
97
+ return GoogleTool(
98
+ function_declarations=[
99
+ _convert_tool_to_genai_function(callable_as_lc_tool()(tool))
100
+ ]
101
+ )
102
+ elif isinstance(tool, dict):
103
+ return GoogleTool(function_declarations=_convert_dict_to_genai_functions(tool)) # type: ignore
104
+ else:
105
+ raise ValueError(f"Unsupported tool type {tool}")
106
+
107
+
108
+ def tool_to_dict(tool: GoogleTool) -> _ToolDict:
109
+ function_declarations = []
110
+ for function_declaration_proto in tool.function_declarations:
111
+ properties: Dict[str, Any] = {}
112
+ for property in function_declaration_proto.parameters.properties:
113
+ property_type = function_declaration_proto.parameters.properties[
114
+ property
115
+ ].type
116
+ property_dict = {"type": TYPE_ENUM_REVERSE[property_type]}
117
+ property_description = function_declaration_proto.parameters.properties[
118
+ property
119
+ ].description
120
+ if property_description:
121
+ property_dict["description"] = property_description
122
+ properties[property] = property_dict
123
+ name = function_declaration_proto.name
124
+ description = function_declaration_proto.description
125
+ parameters = {"type": "object", "properties": properties}
126
+ if function_declaration_proto.parameters.required:
127
+ parameters["required"] = function_declaration_proto.parameters.required
128
+ function_declaration = _FunctionDeclarationDict(
129
+ name=name, description=description, parameters=parameters
130
+ )
131
+ function_declarations.append(function_declaration)
132
+ return {"function_declarations": function_declarations}
133
+
134
+
135
+ def _convert_fc_likes_to_genai_function(
136
+ fc_likes: _FunctionDeclarationLikeList,
137
+ ) -> Sequence[FunctionDeclaration]:
138
+ if isinstance(fc_likes, list):
139
+ return [_convert_fc_like_to_genai_function(fc) for fc in fc_likes]
140
+ raise ValueError(f"Unsupported fc_likes type {fc_likes}")
141
+
142
+
143
+ def _convert_fc_like_to_genai_function(
144
+ fc_like: _FunctionDeclarationLike,
145
+ ) -> FunctionDeclaration:
146
+ if isinstance(fc_like, BaseTool):
147
+ return _convert_tool_to_genai_function(fc_like)
148
+ elif isinstance(fc_like, type) and issubclass(fc_like, BaseModel):
149
+ return _convert_pydantic_to_genai_function(fc_like)
150
+ elif isinstance(fc_like, dict):
151
+ # TODO: add declaration_index
152
+ return _convert_dict_to_genai_function(fc_like)
153
+ elif callable(fc_like):
154
+ return _convert_tool_to_genai_function(callable_as_lc_tool()(fc_like))
155
+ else:
156
+ raise ValueError(f"Unsupported fc_like type {fc_like}")
157
+
158
+
159
+ def _convert_tool_dict_to_genai_functions(
160
+ tool_dict: _ToolDictLike,
161
+ ) -> Sequence[FunctionDeclaration]:
162
+ if "function_declarations" in tool_dict:
163
+ return _convert_dicts_to_genai_functions(tool_dict["function_declarations"]) # type: ignore
164
+ else:
165
+ raise ValueError(f"Unsupported function tool_dict type {tool_dict}")
166
+
167
+
168
+ def _convert_dict_to_genai_functions(
169
+ function_declarations_dict: Dict[str, Any],
170
+ ) -> Sequence[FunctionDeclaration]:
171
+ if "function_declarations" in function_declarations_dict:
172
+ # GoogleTool like
173
+ return [
174
+ _convert_dict_to_genai_function(fc, i)
175
+ for i, fc in enumerate(function_declarations_dict["function_declarations"])
176
+ ]
177
+ d = function_declarations_dict
178
+ if "name" in d and "description" in d and "parameters" in d:
179
+ # _FunctionDeclarationDict
180
+ return [_convert_dict_to_genai_function(d)]
181
+ else:
182
+ # OpenAI like?
183
+ raise ValueError(f"Unsupported function call type {function_declarations_dict}")
184
+
185
+
186
+ def _convert_dicts_to_genai_functions(
187
+ function_declaration_dicts: Sequence[Dict[str, Any]],
188
+ ) -> Sequence[FunctionDeclaration]:
189
+ return [
190
+ _convert_dict_to_genai_function(function_declaration_dict, i)
191
+ for i, function_declaration_dict in enumerate(function_declaration_dicts)
192
+ ]
193
+
194
+
195
+ def _convert_dict_to_genai_function(
196
+ function_declaration_dict: Dict[str, Any], declaration_index: int = 0
197
+ ) -> FunctionDeclaration:
198
+ formatted_fc = {
199
+ "name": function_declaration_dict.get("name", f"unknown-{declaration_index}"),
200
+ "description": function_declaration_dict.get("description", "no-description"),
201
+ }
202
+ if "parameters" in function_declaration_dict:
203
+ formatted_fc["parameters"] = {
204
+ "properties": {
205
+ k: {
206
+ "type_": TYPE_ENUM[v["type"]],
207
+ "description": v.get("description"),
208
+ }
209
+ for k, v in function_declaration_dict["parameters"][
210
+ "properties"
211
+ ].items()
212
+ },
213
+ "required": function_declaration_dict.get("parameters", []).get(
214
+ "required", []
215
+ ),
216
+ "type_": TYPE_ENUM[function_declaration_dict["parameters"]["type"]],
217
+ }
218
+ return FunctionDeclaration(**formatted_fc)
219
+
220
+
221
+ def _convert_tool_to_genai_function(tool: BaseTool) -> FunctionDeclaration:
222
+ if tool.args_schema:
223
+ fc = tool.args_schema
224
+ if isinstance(fc, type) and issubclass(fc, BaseModel):
225
+ return _convert_pydantic_to_genai_function(
226
+ fc, tool_name=tool.name, tool_description=tool.description
227
+ )
228
+ raise ValueError(f"Unsupported function call type {fc}")
229
+ else:
230
+ return FunctionDeclaration(
231
+ name=tool.name,
232
+ description=tool.description,
233
+ parameters={
234
+ "properties": {
235
+ "__arg1": {"type_": TYPE_ENUM["string"]},
236
+ },
237
+ "required": ["__arg1"],
238
+ "type_": TYPE_ENUM["object"],
239
+ },
240
+ )
241
+
242
+
243
+ def _convert_pydantic_to_genai_function(
244
+ pydantic_model: Type[BaseModel],
245
+ tool_name: Optional[str] = None,
246
+ tool_description: Optional[str] = None,
247
+ ) -> FunctionDeclaration:
248
+ schema = dereference_refs(pydantic_model.schema())
249
+ schema.pop("definitions", None)
250
+ function_declaration = FunctionDeclaration(
251
+ name=tool_name if tool_name else schema.get("title"),
252
+ description=tool_description if tool_description else schema.get("description"),
253
+ parameters={
254
+ "properties": {
255
+ k: {
256
+ "type_": _get_type_from_schema(v),
257
+ "description": v.get("description"),
258
+ }
259
+ for k, v in schema["properties"].items()
260
+ },
261
+ "required": schema.get("required", []),
262
+ "type_": TYPE_ENUM[schema["type"]],
263
+ },
264
+ )
265
+ return function_declaration
266
+
267
+
268
+ def _get_type_from_schema(schema: Dict[str, Any]) -> int:
269
+ if "anyOf" in schema:
270
+ types = [_get_type_from_schema(sub_schema) for sub_schema in schema["anyOf"]]
271
+ types = [t for t in types if t is not None] # Remove None values
272
+ if types:
273
+ return types[-1] # TODO: update FunctionDeclaration and pass all types?
274
+ else:
275
+ pass
276
+ elif "type" in schema:
277
+ stype = str(schema["type"])
278
+ if stype in TYPE_ENUM:
279
+ return TYPE_ENUM[stype]
280
+ else:
281
+ pass
282
+ else:
283
+ pass
284
+ return TYPE_ENUM["string"] # Default to string if no valid types found
285
+
286
+
287
+ _ToolChoiceType = Union[
288
+ dict, List[str], str, Literal["auto", "none", "any"], Literal[True]
289
+ ]
290
+
291
+
292
+ class _FunctionCallingConfigDict(TypedDict):
293
+ mode: Union[FunctionCallingConfig.Mode, str]
294
+ allowed_function_names: Optional[List[str]]
295
+
296
+
297
+ class _ToolConfigDict(TypedDict):
298
+ function_calling_config: _FunctionCallingConfigDict
299
+
300
+
301
+ def _tool_choice_to_tool_config(
302
+ tool_choice: _ToolChoiceType,
303
+ all_names: List[str],
304
+ ) -> _ToolConfigDict:
305
+ allowed_function_names: Optional[List[str]] = None
306
+ if tool_choice is True or tool_choice == "any":
307
+ mode = "any"
308
+ allowed_function_names = all_names
309
+ elif tool_choice == "auto":
310
+ mode = "auto"
311
+ elif tool_choice == "none":
312
+ mode = "none"
313
+ elif isinstance(tool_choice, str):
314
+ mode = "any"
315
+ allowed_function_names = [tool_choice]
316
+ elif isinstance(tool_choice, list):
317
+ mode = "any"
318
+ allowed_function_names = tool_choice
319
+ elif isinstance(tool_choice, dict):
320
+ if "mode" in tool_choice:
321
+ mode = tool_choice["mode"]
322
+ allowed_function_names = tool_choice.get("allowed_function_names")
323
+ elif "function_calling_config" in tool_choice:
324
+ mode = tool_choice["function_calling_config"]["mode"]
325
+ allowed_function_names = tool_choice["function_calling_config"].get(
326
+ "allowed_function_names"
327
+ )
328
+ else:
329
+ raise ValueError(
330
+ f"Unrecognized tool choice format:\n\n{tool_choice=}\n\nShould match "
331
+ f"Google GenerativeAI ToolConfig or FunctionCallingConfig format."
332
+ )
333
+ else:
334
+ raise ValueError(f"Unrecognized tool choice format:\n\n{tool_choice=}")
335
+ return _ToolConfigDict(
336
+ function_calling_config={
337
+ "mode": mode,
338
+ "allowed_function_names": allowed_function_names,
339
+ }
340
+ )
@@ -8,6 +8,7 @@ import os
8
8
  import uuid
9
9
  import warnings
10
10
  from io import BytesIO
11
+ from operator import itemgetter
11
12
  from typing import (
12
13
  Any,
13
14
  AsyncIterator,
@@ -19,6 +20,7 @@ from typing import (
19
20
  Optional,
20
21
  Sequence,
21
22
  Tuple,
23
+ Type,
22
24
  Union,
23
25
  cast,
24
26
  )
@@ -65,10 +67,15 @@ from langchain_core.messages import (
65
67
  ToolMessage,
66
68
  )
67
69
  from langchain_core.messages.ai import UsageMetadata
68
- from langchain_core.output_parsers.openai_tools import parse_tool_calls
70
+ from langchain_core.output_parsers.base import OutputParserLike
71
+ from langchain_core.output_parsers.openai_tools import (
72
+ JsonOutputToolsParser,
73
+ PydanticToolsParser,
74
+ parse_tool_calls,
75
+ )
69
76
  from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
70
- from langchain_core.pydantic_v1 import Field, SecretStr, root_validator
71
- from langchain_core.runnables import Runnable
77
+ from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr, root_validator
78
+ from langchain_core.runnables import Runnable, RunnablePassthrough
72
79
  from langchain_core.utils import get_from_dict_or_env
73
80
  from tenacity import (
74
81
  before_sleep_log,
@@ -612,8 +619,8 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
612
619
 
613
620
  convert_system_message_to_human: bool = False
614
621
  """Whether to merge any leading SystemMessage into the following HumanMessage.
615
-
616
- Gemini does not support system messages; any unsupported messages will
622
+
623
+ Gemini does not support system messages; any unsupported messages will
617
624
  raise an error."""
618
625
 
619
626
  class Config:
@@ -937,6 +944,33 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
937
944
  )
938
945
  return result.total_tokens
939
946
 
947
+ def with_structured_output(
948
+ self,
949
+ schema: Union[Dict, Type[BaseModel]],
950
+ *,
951
+ include_raw: bool = False,
952
+ **kwargs: Any,
953
+ ) -> Runnable[LanguageModelInput, Union[Dict, BaseModel]]:
954
+ if kwargs:
955
+ raise ValueError(f"Received unsupported arguments {kwargs}")
956
+ if isinstance(schema, type) and issubclass(schema, BaseModel):
957
+ parser: OutputParserLike = PydanticToolsParser(
958
+ tools=[schema], first_tool_only=True
959
+ )
960
+ else:
961
+ parser = JsonOutputToolsParser()
962
+ llm = self.bind_tools([schema], tool_choice=False)
963
+ if include_raw:
964
+ parser_with_fallback = RunnablePassthrough.assign(
965
+ parsed=itemgetter("raw") | parser, parsing_error=lambda _: None
966
+ ).with_fallbacks(
967
+ [RunnablePassthrough.assign(parsed=lambda _: None)],
968
+ exception_key="parsing_error",
969
+ )
970
+ return {"raw": llm} | parser_with_fallback
971
+ else:
972
+ return llm | parser
973
+
940
974
  def bind_tools(
941
975
  self,
942
976
  tools: Sequence[Union[ToolDict, GoogleTool]],
@@ -963,7 +997,9 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
963
997
  f"both:\n\n{tool_choice=}\n\n{tool_config=}"
964
998
  )
965
999
  # Bind dicts for easier serialization/deserialization.
966
- genai_tools = [tool_to_dict(convert_to_genai_function_declarations(tools))]
1000
+ genai_tools = [
1001
+ tool_to_dict(convert_to_genai_function_declarations(tool)) for tool in tools
1002
+ ]
967
1003
  if tool_choice:
968
1004
  all_names = [
969
1005
  f["name"] # type: ignore[index]
@@ -971,4 +1007,5 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseChatModel):
971
1007
  for f in t["function_declarations"]
972
1008
  ]
973
1009
  tool_config = _tool_choice_to_tool_config(tool_choice, all_names)
1010
+
974
1011
  return self.bind(tools=genai_tools, tool_config=tool_config, **kwargs)
@@ -86,6 +86,8 @@ class GoogleGenerativeAIEmbeddings(BaseModel, Embeddings):
86
86
  google_api_key = get_from_dict_or_env(
87
87
  values, "google_api_key", "GOOGLE_API_KEY"
88
88
  )
89
+ if isinstance(google_api_key, SecretStr):
90
+ google_api_key = google_api_key.get_secret_value()
89
91
  client_info = get_client_info("GoogleGenerativeAIEmbeddings")
90
92
 
91
93
  values["client"] = build_generative_service(
@@ -325,9 +325,16 @@ class GoogleGenerativeAI(_BaseGoogleGenerativeAI, BaseLLM):
325
325
  run_manager: Optional[CallbackManagerForLLMRun] = None,
326
326
  **kwargs: Any,
327
327
  ) -> Iterator[GenerationChunk]:
328
- generation_config = kwargs.get("generation_config", {})
329
- if stop:
330
- generation_config["stop_sequences"] = stop
328
+ generation_config = {
329
+ "stop_sequences": stop,
330
+ "temperature": self.temperature,
331
+ "top_p": self.top_p,
332
+ "top_k": self.top_k,
333
+ "max_output_tokens": self.max_output_tokens,
334
+ "candidate_count": self.n,
335
+ }
336
+ generation_config = generation_config | kwargs.get("generation_config", {})
337
+
331
338
  for stream_resp in _completion_with_retry(
332
339
  self,
333
340
  prompt,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langchain-google-genai"
3
- version = "1.0.6"
3
+ version = "1.0.7"
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.2.2,<0.3"
16
- google-generativeai = "^0.5.2"
15
+ langchain-core = ">=0.2.9,<0.3"
16
+ google-generativeai = "^0.7.0"
17
17
  pillow = { version = "^10.1.0", optional = true }
18
18
 
19
19
  [tool.poetry.extras]
@@ -61,6 +61,7 @@ types-google-cloud-ndb = "^2.2.0.1"
61
61
  types-pillow = "^10.1.0.2"
62
62
  types-protobuf = "^4.24.0.20240302"
63
63
  langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core" }
64
+ numpy = "^1.26.2"
64
65
 
65
66
  [tool.poetry.group.dev]
66
67
  optional = true
@@ -72,7 +73,7 @@ types-pillow = "^10.1.0.2"
72
73
  types-google-cloud-ndb = "^2.2.0.1"
73
74
  langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core" }
74
75
 
75
- [tool.ruff]
76
+ [tool.ruff.lint]
76
77
  select = [
77
78
  "E", # pycodestyle
78
79
  "F", # pyflakes
@@ -1,237 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import (
4
- Any,
5
- Callable,
6
- Dict,
7
- List,
8
- Literal,
9
- Optional,
10
- Sequence,
11
- Type,
12
- TypedDict,
13
- Union,
14
- cast,
15
- )
16
-
17
- import google.ai.generativelanguage as glm
18
- from google.ai.generativelanguage import (
19
- FunctionCallingConfig,
20
- FunctionDeclaration,
21
- )
22
- from google.ai.generativelanguage import (
23
- Tool as GoogleTool,
24
- )
25
- from langchain_core.pydantic_v1 import BaseModel
26
- from langchain_core.tools import BaseTool
27
- from langchain_core.tools import tool as callable_as_lc_tool
28
- from langchain_core.utils.json_schema import dereference_refs
29
-
30
- TYPE_ENUM = {
31
- "string": glm.Type.STRING,
32
- "number": glm.Type.NUMBER,
33
- "integer": glm.Type.INTEGER,
34
- "boolean": glm.Type.BOOLEAN,
35
- "array": glm.Type.ARRAY,
36
- "object": glm.Type.OBJECT,
37
- }
38
-
39
- TYPE_ENUM_REVERSE = {v: k for k, v in TYPE_ENUM.items()}
40
-
41
- _FunctionDeclarationLike = Union[
42
- BaseTool, Type[BaseModel], dict, Callable, FunctionDeclaration
43
- ]
44
-
45
-
46
- class _ToolDict(TypedDict):
47
- function_declarations: Sequence[_FunctionDeclarationLike]
48
-
49
-
50
- def convert_to_genai_function_declarations(
51
- tool: Union[
52
- GoogleTool,
53
- _ToolDict,
54
- _FunctionDeclarationLike,
55
- Sequence[_FunctionDeclarationLike],
56
- ],
57
- ) -> GoogleTool:
58
- if isinstance(tool, GoogleTool):
59
- return cast(GoogleTool, tool)
60
- if isinstance(tool, type) and issubclass(tool, BaseModel):
61
- return GoogleTool(function_declarations=[_convert_to_genai_function(tool)])
62
- if callable(tool):
63
- return _convert_tool_to_genai_function(callable_as_lc_tool()(tool))
64
- if isinstance(tool, list):
65
- return convert_to_genai_function_declarations({"function_declarations": tool})
66
- if isinstance(tool, dict) and "function_declarations" in tool:
67
- return GoogleTool(
68
- function_declarations=[
69
- _convert_to_genai_function(fc) for fc in tool["function_declarations"]
70
- ],
71
- )
72
- return GoogleTool(function_declarations=[_convert_to_genai_function(tool)]) # type: ignore[arg-type]
73
-
74
-
75
- def tool_to_dict(tool: GoogleTool) -> _ToolDict:
76
- function_declarations = []
77
- for function_declaration_proto in tool.function_declarations:
78
- properties: Dict[str, Any] = {}
79
- for property in function_declaration_proto.parameters.properties:
80
- property_type = function_declaration_proto.parameters.properties[
81
- property
82
- ].type
83
- property_dict = {"type": TYPE_ENUM_REVERSE[property_type]}
84
- property_description = function_declaration_proto.parameters.properties[
85
- property
86
- ].description
87
- if property_description:
88
- property_dict["description"] = property_description
89
- properties[property] = property_dict
90
- function_declaration = {
91
- "name": function_declaration_proto.name,
92
- "description": function_declaration_proto.description,
93
- "parameters": {"type": "object", "properties": properties},
94
- }
95
- if function_declaration_proto.parameters.required:
96
- function_declaration["parameters"][ # type: ignore[index]
97
- "required"
98
- ] = function_declaration_proto.parameters.required
99
- function_declarations.append(function_declaration)
100
- return {"function_declarations": function_declarations}
101
-
102
-
103
- def _convert_to_genai_function(fc: _FunctionDeclarationLike) -> FunctionDeclaration:
104
- if isinstance(fc, BaseTool):
105
- return _convert_tool_to_genai_function(fc)
106
- elif isinstance(fc, type) and issubclass(fc, BaseModel):
107
- return _convert_pydantic_to_genai_function(fc)
108
- elif callable(fc):
109
- return _convert_tool_to_genai_function(callable_as_lc_tool()(fc))
110
- elif isinstance(fc, dict):
111
- formatted_fc = {"name": fc["name"], "description": fc.get("description")}
112
- if "parameters" in fc:
113
- formatted_fc["parameters"] = {
114
- "properties": {
115
- k: {
116
- "type_": TYPE_ENUM[v["type"]],
117
- "description": v.get("description"),
118
- }
119
- for k, v in fc["parameters"]["properties"].items()
120
- },
121
- "required": fc.get("parameters", []).get("required", []),
122
- "type_": TYPE_ENUM[fc["parameters"]["type"]],
123
- }
124
- return FunctionDeclaration(**formatted_fc)
125
- else:
126
- raise ValueError(f"Unsupported function call type {fc}")
127
-
128
-
129
- def _convert_tool_to_genai_function(tool: BaseTool) -> FunctionDeclaration:
130
- if tool.args_schema:
131
- schema = dereference_refs(tool.args_schema.schema())
132
- schema.pop("definitions", None)
133
- return FunctionDeclaration(
134
- name=tool.name or schema["title"],
135
- description=tool.description or schema["description"],
136
- parameters={
137
- "properties": {
138
- k: {
139
- "type_": TYPE_ENUM[v["type"]],
140
- "description": v.get("description"),
141
- }
142
- for k, v in schema["properties"].items()
143
- },
144
- "required": schema.get("required", []),
145
- "type_": TYPE_ENUM[schema["type"]],
146
- },
147
- )
148
- else:
149
- return FunctionDeclaration(
150
- name=tool.name,
151
- description=tool.description,
152
- parameters={
153
- "properties": {
154
- "__arg1": {"type_": TYPE_ENUM["string"]},
155
- },
156
- "required": ["__arg1"],
157
- "type_": TYPE_ENUM["object"],
158
- },
159
- )
160
-
161
-
162
- def _convert_pydantic_to_genai_function(
163
- pydantic_model: Type[BaseModel],
164
- ) -> FunctionDeclaration:
165
- schema = dereference_refs(pydantic_model.schema())
166
- schema.pop("definitions", None)
167
- return FunctionDeclaration(
168
- name=schema["title"],
169
- description=schema.get("description", ""),
170
- parameters={
171
- "properties": {
172
- k: {
173
- "type_": TYPE_ENUM[v["type"]],
174
- "description": v.get("description"),
175
- }
176
- for k, v in schema["properties"].items()
177
- },
178
- "required": schema["required"],
179
- "type_": TYPE_ENUM[schema["type"]],
180
- },
181
- )
182
-
183
-
184
- _ToolChoiceType = Union[
185
- dict, List[str], str, Literal["auto", "none", "any"], Literal[True]
186
- ]
187
-
188
-
189
- class _FunctionCallingConfigDict(TypedDict):
190
- mode: Union[FunctionCallingConfig.Mode, str]
191
- allowed_function_names: Optional[List[str]]
192
-
193
-
194
- class _ToolConfigDict(TypedDict):
195
- function_calling_config: _FunctionCallingConfigDict
196
-
197
-
198
- def _tool_choice_to_tool_config(
199
- tool_choice: _ToolChoiceType,
200
- all_names: List[str],
201
- ) -> _ToolConfigDict:
202
- allowed_function_names: Optional[List[str]] = None
203
- if tool_choice is True or tool_choice == "any":
204
- mode = "any"
205
- allowed_function_names = all_names
206
- elif tool_choice == "auto":
207
- mode = "auto"
208
- elif tool_choice == "none":
209
- mode = "none"
210
- elif isinstance(tool_choice, str):
211
- mode = "any"
212
- allowed_function_names = [tool_choice]
213
- elif isinstance(tool_choice, list):
214
- mode = "any"
215
- allowed_function_names = tool_choice
216
- elif isinstance(tool_choice, dict):
217
- if "mode" in tool_choice:
218
- mode = tool_choice["mode"]
219
- allowed_function_names = tool_choice.get("allowed_function_names")
220
- elif "function_calling_config" in tool_choice:
221
- mode = tool_choice["function_calling_config"]["mode"]
222
- allowed_function_names = tool_choice["function_calling_config"].get(
223
- "allowed_function_names"
224
- )
225
- else:
226
- raise ValueError(
227
- f"Unrecognized tool choice format:\n\n{tool_choice=}\n\nShould match "
228
- f"Google GenerativeAI ToolConfig or FunctionCallingConfig format."
229
- )
230
- else:
231
- raise ValueError(f"Unrecognized tool choice format:\n\n{tool_choice=}")
232
- return _ToolConfigDict(
233
- function_calling_config={
234
- "mode": mode,
235
- "allowed_function_names": allowed_function_names,
236
- }
237
- )