langchain-google-genai 2.1.11__py3-none-any.whl → 3.0.0a1__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 +29 -17
- langchain_google_genai/_compat.py +248 -0
- langchain_google_genai/_function_utils.py +77 -59
- langchain_google_genai/_genai_extension.py +60 -27
- langchain_google_genai/_image_utils.py +10 -9
- langchain_google_genai/chat_models.py +803 -297
- langchain_google_genai/embeddings.py +15 -22
- langchain_google_genai/genai_aqa.py +15 -15
- langchain_google_genai/google_vector_store.py +26 -16
- langchain_google_genai/llms.py +8 -7
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-3.0.0a1.dist-info}/METADATA +43 -30
- langchain_google_genai-3.0.0a1.dist-info/RECORD +18 -0
- langchain_google_genai-2.1.11.dist-info/RECORD +0 -17
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-3.0.0a1.dist-info}/WHEEL +0 -0
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-3.0.0a1.dist-info}/entry_points.txt +0 -0
- {langchain_google_genai-2.1.11.dist-info → langchain_google_genai-3.0.0a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""**LangChain Google Generative AI Integration
|
|
1
|
+
"""**LangChain Google Generative AI Integration**.
|
|
2
2
|
|
|
3
3
|
This module integrates Google's Generative AI models, specifically the Gemini series, with the LangChain framework. It provides classes for interacting with chat models and generating embeddings, leveraging Google's advanced AI capabilities.
|
|
4
4
|
|
|
@@ -76,12 +76,12 @@ __all__ = [
|
|
|
76
76
|
"AqaOutput",
|
|
77
77
|
"ChatGoogleGenerativeAI",
|
|
78
78
|
"DoesNotExistsException",
|
|
79
|
+
"DoesNotExistsException",
|
|
79
80
|
"GenAIAqa",
|
|
80
|
-
"GoogleGenerativeAIEmbeddings",
|
|
81
81
|
"GoogleGenerativeAI",
|
|
82
|
+
"GoogleGenerativeAIEmbeddings",
|
|
82
83
|
"GoogleVectorStore",
|
|
83
84
|
"HarmBlockThreshold",
|
|
84
85
|
"HarmCategory",
|
|
85
86
|
"Modality",
|
|
86
|
-
"DoesNotExistsException",
|
|
87
87
|
]
|
|
@@ -13,19 +13,17 @@ _TELEMETRY_ENV_VARIABLE_NAME = "GOOGLE_CLOUD_AGENT_ENGINE_ID"
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class GoogleGenerativeAIError(Exception):
|
|
16
|
-
"""
|
|
17
|
-
Custom exception class for errors associated with the `Google GenAI` API.
|
|
18
|
-
"""
|
|
16
|
+
"""Custom exception class for errors associated with the `Google GenAI` API."""
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
class _BaseGoogleGenerativeAI(BaseModel):
|
|
22
|
-
"""Base class for Google Generative AI LLMs"""
|
|
20
|
+
"""Base class for Google Generative AI LLMs."""
|
|
23
21
|
|
|
24
22
|
model: str = Field(
|
|
25
23
|
...,
|
|
26
24
|
description="""The name of the model to use.
|
|
27
25
|
Examples:
|
|
28
|
-
- gemini-2.5-
|
|
26
|
+
- gemini-2.5-flash
|
|
29
27
|
- models/text-bison-001""",
|
|
30
28
|
)
|
|
31
29
|
"""Model name to use."""
|
|
@@ -34,28 +32,38 @@ Examples:
|
|
|
34
32
|
)
|
|
35
33
|
"""Google AI API key.
|
|
36
34
|
If not specified will be read from env var ``GOOGLE_API_KEY``."""
|
|
35
|
+
|
|
37
36
|
credentials: Any = None
|
|
38
37
|
"The default custom credentials (google.auth.credentials.Credentials) to use "
|
|
39
38
|
"when making API calls. If not provided, credentials will be ascertained from "
|
|
40
39
|
"the GOOGLE_API_KEY envvar"
|
|
40
|
+
|
|
41
41
|
temperature: float = 0.7
|
|
42
|
-
"""Run inference with this temperature. Must be within ``[0.0, 2.0]``.
|
|
42
|
+
"""Run inference with this temperature. Must be within ``[0.0, 2.0]``. If unset,
|
|
43
|
+
will default to ``0.7``."""
|
|
44
|
+
|
|
43
45
|
top_p: Optional[float] = None
|
|
44
46
|
"""Decode using nucleus sampling: consider the smallest set of tokens whose
|
|
45
|
-
|
|
47
|
+
probability sum is at least ``top_p``. Must be within ``[0.0, 1.0]``."""
|
|
48
|
+
|
|
46
49
|
top_k: Optional[int] = None
|
|
47
50
|
"""Decode using top-k sampling: consider the set of ``top_k`` most probable tokens.
|
|
48
|
-
|
|
51
|
+
Must be positive."""
|
|
52
|
+
|
|
49
53
|
max_output_tokens: Optional[int] = Field(default=None, alias="max_tokens")
|
|
50
54
|
"""Maximum number of tokens to include in a candidate. Must be greater than zero.
|
|
51
|
-
|
|
55
|
+
If unset, will use the model's default value, which varies by model.
|
|
56
|
+
See https://ai.google.dev/gemini-api/docs/models for model-specific limits."""
|
|
57
|
+
|
|
52
58
|
n: int = 1
|
|
53
59
|
"""Number of chat completions to generate for each prompt. Note that the API may
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
not return the full ``n`` completions if duplicates are generated."""
|
|
61
|
+
|
|
62
|
+
max_retries: int = Field(default=6, alias="retries")
|
|
63
|
+
"""The maximum number of retries to make when generating. If unset, will default
|
|
64
|
+
to ``6``."""
|
|
57
65
|
|
|
58
|
-
timeout: Optional[float] = None
|
|
66
|
+
timeout: Optional[float] = Field(default=None, alias="request_timeout")
|
|
59
67
|
"""The maximum number of seconds to wait for a response."""
|
|
60
68
|
|
|
61
69
|
client_options: Optional[Dict] = Field(
|
|
@@ -68,6 +76,7 @@ Examples:
|
|
|
68
76
|
transport: Optional[str] = Field(
|
|
69
77
|
default=None,
|
|
70
78
|
description="A string, one of: [`rest`, `grpc`, `grpc_asyncio`].",
|
|
79
|
+
alias="api_transport",
|
|
71
80
|
)
|
|
72
81
|
additional_headers: Optional[Dict[str, str]] = Field(
|
|
73
82
|
default=None,
|
|
@@ -89,9 +98,9 @@ Examples:
|
|
|
89
98
|
)
|
|
90
99
|
|
|
91
100
|
safety_settings: Optional[Dict[HarmCategory, HarmBlockThreshold]] = None
|
|
92
|
-
"""The default safety settings to use for all generations.
|
|
93
|
-
|
|
94
|
-
For example:
|
|
101
|
+
"""The default safety settings to use for all generations.
|
|
102
|
+
|
|
103
|
+
For example:
|
|
95
104
|
|
|
96
105
|
.. code-block:: python
|
|
97
106
|
from google.generativeai.types.safety_types import HarmBlockThreshold, HarmCategory
|
|
@@ -127,6 +136,7 @@ def get_user_agent(module: Optional[str] = None) -> Tuple[str, str]:
|
|
|
127
136
|
Args:
|
|
128
137
|
module (Optional[str]):
|
|
129
138
|
Optional. The module for a custom user agent header.
|
|
139
|
+
|
|
130
140
|
Returns:
|
|
131
141
|
Tuple[str, str]
|
|
132
142
|
"""
|
|
@@ -148,11 +158,13 @@ def get_client_info(module: Optional[str] = None) -> "ClientInfo":
|
|
|
148
158
|
Args:
|
|
149
159
|
module (Optional[str]):
|
|
150
160
|
Optional. The module for a custom user agent header.
|
|
161
|
+
|
|
151
162
|
Returns:
|
|
152
163
|
``google.api_core.gapic_v1.client_info.ClientInfo``
|
|
153
164
|
"""
|
|
154
165
|
client_library_version, user_agent = get_user_agent(module)
|
|
155
|
-
|
|
166
|
+
# TODO: remove ignore once google-auth has types.
|
|
167
|
+
return ClientInfo( # type: ignore[no-untyped-call]
|
|
156
168
|
client_library_version=client_library_version,
|
|
157
169
|
user_agent=user_agent,
|
|
158
170
|
)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Go from v1 content blocks to generativelanguage_v1beta format."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from langchain_core.messages import content as types
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def translate_citations_to_grounding_metadata(
|
|
10
|
+
citations: list[types.Citation], web_search_queries: Optional[list[str]] = None
|
|
11
|
+
) -> dict[str, Any]:
|
|
12
|
+
"""Translate LangChain Citations to Google AI grounding metadata format.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
citations: List of Citation content blocks.
|
|
16
|
+
web_search_queries: Optional list of search queries that generated
|
|
17
|
+
the grounding data.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Google AI grounding metadata dictionary.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
>>> citations = [
|
|
24
|
+
... create_citation(
|
|
25
|
+
... url="https://uefa.com/euro2024",
|
|
26
|
+
... title="UEFA Euro 2024 Results",
|
|
27
|
+
... start_index=0,
|
|
28
|
+
... end_index=47,
|
|
29
|
+
... cited_text="Spain won the UEFA Euro 2024 championship",
|
|
30
|
+
... )
|
|
31
|
+
... ]
|
|
32
|
+
>>> metadata = translate_citations_to_grounding_metadata(citations)
|
|
33
|
+
>>> len(metadata["groundingChunks"])
|
|
34
|
+
1
|
|
35
|
+
>>> metadata["groundingChunks"][0]["web"]["uri"]
|
|
36
|
+
'https://uefa.com/euro2024'
|
|
37
|
+
"""
|
|
38
|
+
if not citations:
|
|
39
|
+
return {}
|
|
40
|
+
|
|
41
|
+
# Group citations by text segment (start_index, end_index, cited_text)
|
|
42
|
+
segment_to_citations: dict[
|
|
43
|
+
tuple[Optional[int], Optional[int], Optional[str]], list[types.Citation]
|
|
44
|
+
] = {}
|
|
45
|
+
|
|
46
|
+
for citation in citations:
|
|
47
|
+
key = (
|
|
48
|
+
citation.get("start_index"),
|
|
49
|
+
citation.get("end_index"),
|
|
50
|
+
citation.get("cited_text"),
|
|
51
|
+
)
|
|
52
|
+
if key not in segment_to_citations:
|
|
53
|
+
segment_to_citations[key] = []
|
|
54
|
+
segment_to_citations[key].append(citation)
|
|
55
|
+
|
|
56
|
+
# Build grounding chunks from unique URLs
|
|
57
|
+
url_to_chunk_index: dict[str, int] = {}
|
|
58
|
+
grounding_chunks: list[dict[str, Any]] = []
|
|
59
|
+
|
|
60
|
+
for citation in citations:
|
|
61
|
+
url = citation.get("url")
|
|
62
|
+
if url and url not in url_to_chunk_index:
|
|
63
|
+
url_to_chunk_index[url] = len(grounding_chunks)
|
|
64
|
+
grounding_chunks.append(
|
|
65
|
+
{"web": {"uri": url, "title": citation.get("title", "")}}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Build grounding supports
|
|
69
|
+
grounding_supports: list[dict[str, Any]] = []
|
|
70
|
+
|
|
71
|
+
for (
|
|
72
|
+
start_index,
|
|
73
|
+
end_index,
|
|
74
|
+
cited_text,
|
|
75
|
+
), citations_group in segment_to_citations.items():
|
|
76
|
+
if start_index is not None and end_index is not None and cited_text:
|
|
77
|
+
chunk_indices = []
|
|
78
|
+
confidence_scores = []
|
|
79
|
+
|
|
80
|
+
for citation in citations_group:
|
|
81
|
+
url = citation.get("url")
|
|
82
|
+
if url and url in url_to_chunk_index:
|
|
83
|
+
chunk_indices.append(url_to_chunk_index[url])
|
|
84
|
+
|
|
85
|
+
# Extract confidence scores from extras if available
|
|
86
|
+
extras = citation.get("extras", {})
|
|
87
|
+
google_metadata = extras.get("google_ai_metadata", {})
|
|
88
|
+
scores = google_metadata.get("confidence_scores", [])
|
|
89
|
+
confidence_scores.extend(scores)
|
|
90
|
+
|
|
91
|
+
support = {
|
|
92
|
+
"segment": {
|
|
93
|
+
"startIndex": start_index,
|
|
94
|
+
"endIndex": end_index,
|
|
95
|
+
"text": cited_text,
|
|
96
|
+
},
|
|
97
|
+
"groundingChunkIndices": chunk_indices,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if confidence_scores:
|
|
101
|
+
support["confidenceScores"] = confidence_scores
|
|
102
|
+
|
|
103
|
+
grounding_supports.append(support)
|
|
104
|
+
|
|
105
|
+
# Extract search queries from extras if not provided
|
|
106
|
+
if web_search_queries is None:
|
|
107
|
+
web_search_queries = []
|
|
108
|
+
for citation in citations:
|
|
109
|
+
extras = citation.get("extras", {})
|
|
110
|
+
google_metadata = extras.get("google_ai_metadata", {})
|
|
111
|
+
queries = google_metadata.get("web_search_queries", [])
|
|
112
|
+
web_search_queries.extend(queries)
|
|
113
|
+
# Remove duplicates while preserving order
|
|
114
|
+
web_search_queries = list(dict.fromkeys(web_search_queries))
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"webSearchQueries": web_search_queries,
|
|
118
|
+
"groundingChunks": grounding_chunks,
|
|
119
|
+
"groundingSupports": grounding_supports,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _convert_from_v1_to_generativelanguage_v1beta(
|
|
124
|
+
content: list[types.ContentBlock], model_provider: str | None
|
|
125
|
+
) -> list[dict[str, Any]]:
|
|
126
|
+
"""Convert v1 content blocks to `google.ai.generativelanguage_v1beta.types.Content`.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
content: List of v1 `ContentBlock` objects.
|
|
130
|
+
model_provider: The model provider name that generated the v1 content.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
List of dictionaries in `google.ai.generativelanguage_v1beta.types.Content`
|
|
134
|
+
format, ready to be sent to the API.
|
|
135
|
+
"""
|
|
136
|
+
new_content: list = []
|
|
137
|
+
for block in content:
|
|
138
|
+
if not isinstance(block, dict) or "type" not in block:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
block_dict = dict(block) # (For typing)
|
|
142
|
+
|
|
143
|
+
# TextContentBlock
|
|
144
|
+
if block_dict["type"] == "text":
|
|
145
|
+
new_block = {"text": block_dict.get("text", "")}
|
|
146
|
+
new_content.append(new_block)
|
|
147
|
+
# Citations are only handled on output. Can't pass them back :/
|
|
148
|
+
|
|
149
|
+
# ReasoningContentBlock -> thinking
|
|
150
|
+
elif block_dict["type"] == "reasoning" and model_provider == "google_genai":
|
|
151
|
+
# Google requires passing back the thought_signature when available.
|
|
152
|
+
# Signatures are only provided when function calling is enabled.
|
|
153
|
+
# If no signature is available, we skip the reasoning block as it cannot
|
|
154
|
+
# be properly serialized back to the API.
|
|
155
|
+
if "extras" in block_dict and isinstance(block_dict["extras"], dict):
|
|
156
|
+
extras = block_dict["extras"]
|
|
157
|
+
if "signature" in extras:
|
|
158
|
+
new_block = {
|
|
159
|
+
"thought": True,
|
|
160
|
+
"text": block_dict.get("reasoning", ""),
|
|
161
|
+
"thought_signature": extras["signature"],
|
|
162
|
+
}
|
|
163
|
+
new_content.append(new_block)
|
|
164
|
+
# else: skip reasoning blocks without signatures
|
|
165
|
+
# TODO: log a warning?
|
|
166
|
+
# else: skip reasoning blocks without extras
|
|
167
|
+
# TODO: log a warning?
|
|
168
|
+
|
|
169
|
+
# ImageContentBlock
|
|
170
|
+
elif block_dict["type"] == "image":
|
|
171
|
+
if base64 := block_dict.get("base64"):
|
|
172
|
+
new_block = {
|
|
173
|
+
"inline_data": {
|
|
174
|
+
"mime_type": block_dict.get("mime_type", "image/jpeg"),
|
|
175
|
+
"data": base64.encode("utf-8")
|
|
176
|
+
if isinstance(base64, str)
|
|
177
|
+
else base64,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
new_content.append(new_block)
|
|
181
|
+
elif url := block_dict.get("url") and model_provider == "google_genai":
|
|
182
|
+
# Google file service
|
|
183
|
+
new_block = {
|
|
184
|
+
"file_data": {
|
|
185
|
+
"mime_type": block_dict.get("mime_type", "image/jpeg"),
|
|
186
|
+
"file_uri": block_dict[str(url)],
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
new_content.append(new_block)
|
|
190
|
+
|
|
191
|
+
# TODO: AudioContentBlock -> audio once models support passing back in
|
|
192
|
+
|
|
193
|
+
# FileContentBlock (documents)
|
|
194
|
+
elif block_dict["type"] == "file":
|
|
195
|
+
if base64 := block_dict.get("base64"):
|
|
196
|
+
new_block = {
|
|
197
|
+
"inline_data": {
|
|
198
|
+
"mime_type": block_dict.get(
|
|
199
|
+
"mime_type", "application/octet-stream"
|
|
200
|
+
),
|
|
201
|
+
"data": base64.encode("utf-8")
|
|
202
|
+
if isinstance(base64, str)
|
|
203
|
+
else base64,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
new_content.append(new_block)
|
|
207
|
+
elif url := block_dict.get("url") and model_provider == "google_genai":
|
|
208
|
+
# Google file service
|
|
209
|
+
new_block = {
|
|
210
|
+
"file_data": {
|
|
211
|
+
"mime_type": block_dict.get(
|
|
212
|
+
"mime_type", "application/octet-stream"
|
|
213
|
+
),
|
|
214
|
+
"file_uri": block_dict[str(url)],
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
new_content.append(new_block)
|
|
218
|
+
|
|
219
|
+
# ToolCall -> FunctionCall
|
|
220
|
+
elif block_dict["type"] == "tool_call":
|
|
221
|
+
function_call = {
|
|
222
|
+
"function_call": {
|
|
223
|
+
"name": block_dict.get("name", ""),
|
|
224
|
+
"args": block_dict.get("args", {}),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
new_content.append(function_call)
|
|
228
|
+
|
|
229
|
+
# ToolCallChunk -> FunctionCall
|
|
230
|
+
elif block_dict["type"] == "tool_call_chunk":
|
|
231
|
+
try:
|
|
232
|
+
args_str = block_dict.get("args") or "{}"
|
|
233
|
+
input_ = json.loads(args_str) if isinstance(args_str, str) else args_str
|
|
234
|
+
except json.JSONDecodeError:
|
|
235
|
+
input_ = {}
|
|
236
|
+
|
|
237
|
+
function_call = {
|
|
238
|
+
"function_call": {
|
|
239
|
+
"name": block_dict.get("name", "no_tool_name_present"),
|
|
240
|
+
"args": input_,
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
new_content.append(function_call)
|
|
244
|
+
|
|
245
|
+
# NonStandardContentBlock
|
|
246
|
+
# TODO: Handle new server tools
|
|
247
|
+
|
|
248
|
+
return new_content
|