langchain-google-genai 2.1.11__py3-none-any.whl → 3.0.0__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.

@@ -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-pro
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
- probability sum is at least ``top_p``. Must be within ``[0.0, 1.0]``."""
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
- Must be positive."""
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
- If unset, will default to ``64``."""
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
- not return the full ``n`` completions if duplicates are generated."""
55
- max_retries: int = 6
56
- """The maximum number of retries to make when generating."""
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
- return ClientInfo(
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,286 @@
1
+ """Go from v1 content blocks to generativelanguage_v1beta format."""
2
+
3
+ import json
4
+ from typing import Any, Optional, cast
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 "extras" in block_dict and isinstance(block_dict["extras"], dict):
154
+ extras = block_dict["extras"]
155
+ if "signature" in extras:
156
+ new_block = {
157
+ "thought": True,
158
+ "text": block_dict.get("reasoning", ""),
159
+ "thought_signature": extras["signature"],
160
+ }
161
+ new_content.append(new_block)
162
+ # else: skip reasoning blocks without signatures
163
+ # TODO: log a warning?
164
+ # else: skip reasoning blocks without extras
165
+ # TODO: log a warning?
166
+
167
+ # ImageContentBlock
168
+ elif block_dict["type"] == "image":
169
+ if base64 := block_dict.get("base64"):
170
+ new_block = {
171
+ "inline_data": {
172
+ "mime_type": block_dict.get("mime_type", "image/jpeg"),
173
+ "data": base64.encode("utf-8")
174
+ if isinstance(base64, str)
175
+ else base64,
176
+ }
177
+ }
178
+ new_content.append(new_block)
179
+ elif url := block_dict.get("url") and model_provider == "google_genai":
180
+ # Google file service
181
+ new_block = {
182
+ "file_data": {
183
+ "mime_type": block_dict.get("mime_type", "image/jpeg"),
184
+ "file_uri": block_dict[str(url)],
185
+ }
186
+ }
187
+ new_content.append(new_block)
188
+
189
+ # TODO: AudioContentBlock -> audio once models support passing back in
190
+
191
+ # FileContentBlock (documents)
192
+ elif block_dict["type"] == "file":
193
+ if base64 := block_dict.get("base64"):
194
+ new_block = {
195
+ "inline_data": {
196
+ "mime_type": block_dict.get(
197
+ "mime_type", "application/octet-stream"
198
+ ),
199
+ "data": base64.encode("utf-8")
200
+ if isinstance(base64, str)
201
+ else base64,
202
+ }
203
+ }
204
+ new_content.append(new_block)
205
+ elif url := block_dict.get("url") and model_provider == "google_genai":
206
+ # Google file service
207
+ new_block = {
208
+ "file_data": {
209
+ "mime_type": block_dict.get(
210
+ "mime_type", "application/octet-stream"
211
+ ),
212
+ "file_uri": block_dict[str(url)],
213
+ }
214
+ }
215
+ new_content.append(new_block)
216
+
217
+ # ToolCall -> FunctionCall
218
+ elif block_dict["type"] == "tool_call":
219
+ function_call = {
220
+ "function_call": {
221
+ "name": block_dict.get("name", ""),
222
+ "args": block_dict.get("args", {}),
223
+ }
224
+ }
225
+ new_content.append(function_call)
226
+
227
+ # ToolCallChunk -> FunctionCall
228
+ elif block_dict["type"] == "tool_call_chunk":
229
+ try:
230
+ args_str = block_dict.get("args") or "{}"
231
+ input_ = json.loads(args_str) if isinstance(args_str, str) else args_str
232
+ except json.JSONDecodeError:
233
+ input_ = {}
234
+
235
+ function_call = {
236
+ "function_call": {
237
+ "name": block_dict.get("name", "no_tool_name_present"),
238
+ "args": input_,
239
+ }
240
+ }
241
+ new_content.append(function_call)
242
+
243
+ elif block_dict["type"] == "server_tool_call":
244
+ if block_dict.get("name") == "code_interpreter":
245
+ # LangChain v0 format
246
+ args = cast(dict, block_dict.get("args", {}))
247
+ executable_code = {
248
+ "type": "executable_code",
249
+ "executable_code": args.get("code", ""),
250
+ "language": args.get("language", ""),
251
+ "id": block_dict.get("id", ""),
252
+ }
253
+ # Google generativelanguage format
254
+ new_content.append(
255
+ {
256
+ "executable_code": {
257
+ "language": executable_code["language"],
258
+ "code": executable_code["executable_code"],
259
+ }
260
+ }
261
+ )
262
+
263
+ elif block_dict["type"] == "server_tool_result":
264
+ extras = cast(dict, block_dict.get("extras", {}))
265
+ if extras.get("block_type") == "code_execution_result":
266
+ # LangChain v0 format
267
+ code_execution_result = {
268
+ "type": "code_execution_result",
269
+ "code_execution_result": block_dict.get("output", ""),
270
+ "outcome": extras.get("outcome", ""),
271
+ "tool_call_id": block_dict.get("tool_call_id", ""),
272
+ }
273
+ # Google generativelanguage format
274
+ new_content.append(
275
+ {
276
+ "code_execution_result": {
277
+ "outcome": code_execution_result["outcome"],
278
+ "output": code_execution_result["code_execution_result"],
279
+ }
280
+ }
281
+ )
282
+
283
+ elif block_dict["type"] == "non_standard":
284
+ new_content.append(block_dict["value"])
285
+
286
+ return new_content