@langchain/google-genai 0.2.8 → 0.2.10

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.
package/README.md CHANGED
@@ -50,6 +50,7 @@ Then initialize
50
50
 
51
51
  ```typescript
52
52
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
53
+ import { HumanMessage } from "@langchain/core/messages";
53
54
 
54
55
  const model = new ChatGoogleGenerativeAI({
55
56
  modelName: "gemini-pro",
@@ -27,13 +27,12 @@ const tools_js_1 = require("./utils/tools.cjs");
27
27
  * ## [Runtime args](https://api.js.langchain.com/interfaces/langchain_google_genai.GoogleGenerativeAIChatCallOptions.html)
28
28
  *
29
29
  * Runtime args can be passed as the second argument to any of the base runnable methods `.invoke`. `.stream`, `.batch`, etc.
30
- * They can also be passed via `.bind`, or the second arg in `.bindTools`, like shown in the examples below:
30
+ * They can also be passed via `.withConfig`, or the second arg in `.bindTools`, like shown in the examples below:
31
31
  *
32
32
  * ```typescript
33
- * // When calling `.bind`, call options should be passed via the first argument
34
- * const llmWithArgsBound = llm.bind({
33
+ * // When calling `.withConfig`, call options should be passed via the first argument
34
+ * const llmWithArgsBound = llm.withConfig({
35
35
  * stop: ["\n"],
36
- * tools: [...],
37
36
  * });
38
37
  *
39
38
  * // When calling `.bindTools`, call options should be passed via the second argument
@@ -598,7 +597,10 @@ class ChatGoogleGenerativeAI extends chat_models_1.BaseChatModel {
598
597
  return "googlegenerativeai";
599
598
  }
600
599
  bindTools(tools, kwargs) {
601
- return this.bind({ tools: (0, tools_js_1.convertToolsToGenAI)(tools)?.tools, ...kwargs });
600
+ return this.withConfig({
601
+ tools: (0, tools_js_1.convertToolsToGenAI)(tools)?.tools,
602
+ ...kwargs,
603
+ });
602
604
  }
603
605
  invocationParams(options) {
604
606
  const toolsAndConfig = options?.tools?.length
@@ -668,7 +670,10 @@ class ChatGoogleGenerativeAI extends chat_models_1.BaseChatModel {
668
670
  const generationResult = (0, common_js_1.mapGenerateContentResultToChatResult)(res.response, {
669
671
  usageMetadata,
670
672
  });
671
- await runManager?.handleLLMNewToken(generationResult.generations[0].text ?? "");
673
+ // may not have generations in output if there was a refusal for safety reasons
674
+ if (generationResult.generations?.length > 0) {
675
+ await runManager?.handleLLMNewToken(generationResult.generations[0]?.text ?? "");
676
+ }
672
677
  return generationResult;
673
678
  }
674
679
  async *_streamResponseChunks(messages, options, runManager) {
@@ -800,14 +805,13 @@ class ChatGoogleGenerativeAI extends chat_models_1.BaseChatModel {
800
805
  keyName: functionName,
801
806
  });
802
807
  }
803
- llm = this.bind({
804
- tools,
805
- tool_choice: functionName,
808
+ llm = this.bindTools(tools).withConfig({
809
+ allowedFunctionNames: [functionName],
806
810
  });
807
811
  }
808
812
  else {
809
813
  const jsonSchema = (0, zod_to_genai_parameters_js_1.schemaToGenerativeAIParameters)(schema);
810
- llm = this.bind({
814
+ llm = this.withConfig({
811
815
  responseSchema: jsonSchema,
812
816
  });
813
817
  outputParser = new output_parsers_1.JsonOutputParser();
@@ -71,7 +71,7 @@ export interface GoogleGenerativeAIChatInput extends BaseChatModelParams, Pick<G
71
71
  * Top-k changes how the model selects tokens for output.
72
72
  *
73
73
  * A top-k of 1 means the selected token is the most probable among
74
- * all tokens in the models vocabulary (also called greedy decoding),
74
+ * all tokens in the model's vocabulary (also called greedy decoding),
75
75
  * while a top-k of 3 means that the next token is selected from
76
76
  * among the 3 most probable tokens (using temperature).
77
77
  *
@@ -139,13 +139,12 @@ export interface GoogleGenerativeAIChatInput extends BaseChatModelParams, Pick<G
139
139
  * ## [Runtime args](https://api.js.langchain.com/interfaces/langchain_google_genai.GoogleGenerativeAIChatCallOptions.html)
140
140
  *
141
141
  * Runtime args can be passed as the second argument to any of the base runnable methods `.invoke`. `.stream`, `.batch`, etc.
142
- * They can also be passed via `.bind`, or the second arg in `.bindTools`, like shown in the examples below:
142
+ * They can also be passed via `.withConfig`, or the second arg in `.bindTools`, like shown in the examples below:
143
143
  *
144
144
  * ```typescript
145
- * // When calling `.bind`, call options should be passed via the first argument
146
- * const llmWithArgsBound = llm.bind({
145
+ * // When calling `.withConfig`, call options should be passed via the first argument
146
+ * const llmWithArgsBound = llm.withConfig({
147
147
  * stop: ["\n"],
148
- * tools: [...],
149
148
  * });
150
149
  *
151
150
  * // When calling `.bindTools`, call options should be passed via the second argument
@@ -24,13 +24,12 @@ import { convertToolsToGenAI } from "./utils/tools.js";
24
24
  * ## [Runtime args](https://api.js.langchain.com/interfaces/langchain_google_genai.GoogleGenerativeAIChatCallOptions.html)
25
25
  *
26
26
  * Runtime args can be passed as the second argument to any of the base runnable methods `.invoke`. `.stream`, `.batch`, etc.
27
- * They can also be passed via `.bind`, or the second arg in `.bindTools`, like shown in the examples below:
27
+ * They can also be passed via `.withConfig`, or the second arg in `.bindTools`, like shown in the examples below:
28
28
  *
29
29
  * ```typescript
30
- * // When calling `.bind`, call options should be passed via the first argument
31
- * const llmWithArgsBound = llm.bind({
30
+ * // When calling `.withConfig`, call options should be passed via the first argument
31
+ * const llmWithArgsBound = llm.withConfig({
32
32
  * stop: ["\n"],
33
- * tools: [...],
34
33
  * });
35
34
  *
36
35
  * // When calling `.bindTools`, call options should be passed via the second argument
@@ -595,7 +594,10 @@ export class ChatGoogleGenerativeAI extends BaseChatModel {
595
594
  return "googlegenerativeai";
596
595
  }
597
596
  bindTools(tools, kwargs) {
598
- return this.bind({ tools: convertToolsToGenAI(tools)?.tools, ...kwargs });
597
+ return this.withConfig({
598
+ tools: convertToolsToGenAI(tools)?.tools,
599
+ ...kwargs,
600
+ });
599
601
  }
600
602
  invocationParams(options) {
601
603
  const toolsAndConfig = options?.tools?.length
@@ -665,7 +667,10 @@ export class ChatGoogleGenerativeAI extends BaseChatModel {
665
667
  const generationResult = mapGenerateContentResultToChatResult(res.response, {
666
668
  usageMetadata,
667
669
  });
668
- await runManager?.handleLLMNewToken(generationResult.generations[0].text ?? "");
670
+ // may not have generations in output if there was a refusal for safety reasons
671
+ if (generationResult.generations?.length > 0) {
672
+ await runManager?.handleLLMNewToken(generationResult.generations[0]?.text ?? "");
673
+ }
669
674
  return generationResult;
670
675
  }
671
676
  async *_streamResponseChunks(messages, options, runManager) {
@@ -797,14 +802,13 @@ export class ChatGoogleGenerativeAI extends BaseChatModel {
797
802
  keyName: functionName,
798
803
  });
799
804
  }
800
- llm = this.bind({
801
- tools,
802
- tool_choice: functionName,
805
+ llm = this.bindTools(tools).withConfig({
806
+ allowedFunctionNames: [functionName],
803
807
  });
804
808
  }
805
809
  else {
806
810
  const jsonSchema = schemaToGenerativeAIParameters(schema);
807
- llm = this.bind({
811
+ llm = this.withConfig({
808
812
  responseSchema: jsonSchema,
809
813
  });
810
814
  outputParser = new JsonOutputParser();
@@ -78,115 +78,255 @@ function inferToolNameFromPreviousMessages(message, previousMessages) {
78
78
  return toolCall.id === message.tool_call_id;
79
79
  })?.name;
80
80
  }
81
- function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
82
- if (typeof message.content === "string" &&
83
- message.content !== "" &&
84
- !(0, messages_1.isToolMessage)(message)) {
85
- return [{ text: message.content }];
86
- }
87
- let functionCalls = [];
88
- let functionResponses = [];
89
- let messageParts = [];
90
- if ("tool_calls" in message &&
91
- Array.isArray(message.tool_calls) &&
92
- message.tool_calls.length > 0) {
93
- functionCalls = message.tool_calls.map((tc) => ({
94
- functionCall: {
95
- name: tc.name,
96
- args: tc.args,
97
- },
98
- }));
99
- }
100
- else if ((0, messages_1.isToolMessage)(message) && message.content) {
101
- const messageName = message.name ??
102
- inferToolNameFromPreviousMessages(message, previousMessages);
103
- if (messageName === undefined) {
104
- throw new Error(`Google requires a tool name for each tool call response, and we could not infer a called tool name for ToolMessage "${message.id}" from your passed messages. Please populate a "name" field on that ToolMessage explicitly.`);
105
- }
106
- functionResponses = [
107
- {
108
- functionResponse: {
109
- name: messageName,
110
- response: typeof message.content === "string"
111
- ? { result: message.content }
112
- : message.content,
113
- },
114
- },
115
- ];
116
- }
117
- else if (Array.isArray(message.content)) {
118
- messageParts = message.content.map((c) => {
119
- if (c.type === "text") {
120
- return {
121
- text: c.text,
122
- };
81
+ function _getStandardContentBlockConverter(isMultimodalModel) {
82
+ const standardContentBlockConverter = {
83
+ providerName: "Google Gemini",
84
+ fromStandardTextBlock(block) {
85
+ return {
86
+ text: block.text,
87
+ };
88
+ },
89
+ fromStandardImageBlock(block) {
90
+ if (!isMultimodalModel) {
91
+ throw new Error("This model does not support images");
123
92
  }
124
- else if (c.type === "executableCode") {
125
- return {
126
- executableCode: c.executableCode,
127
- };
93
+ if (block.source_type === "url") {
94
+ const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
95
+ if (data) {
96
+ return {
97
+ inlineData: {
98
+ mimeType: data.mime_type,
99
+ data: data.data,
100
+ },
101
+ };
102
+ }
103
+ else {
104
+ return {
105
+ fileData: {
106
+ mimeType: block.mime_type ?? "",
107
+ fileUri: block.url,
108
+ },
109
+ };
110
+ }
128
111
  }
129
- else if (c.type === "codeExecutionResult") {
112
+ if (block.source_type === "base64") {
130
113
  return {
131
- codeExecutionResult: c.codeExecutionResult,
114
+ inlineData: {
115
+ mimeType: block.mime_type ?? "",
116
+ data: block.data,
117
+ },
132
118
  };
133
119
  }
134
- if (c.type === "image_url") {
135
- if (!isMultimodalModel) {
136
- throw new Error(`This model does not support images`);
137
- }
138
- let source;
139
- if (typeof c.image_url === "string") {
140
- source = c.image_url;
141
- }
142
- else if (typeof c.image_url === "object" && "url" in c.image_url) {
143
- source = c.image_url.url;
120
+ throw new Error(`Unsupported source type: ${block.source_type}`);
121
+ },
122
+ fromStandardAudioBlock(block) {
123
+ if (!isMultimodalModel) {
124
+ throw new Error("This model does not support audio");
125
+ }
126
+ if (block.source_type === "url") {
127
+ const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
128
+ if (data) {
129
+ return {
130
+ inlineData: {
131
+ mimeType: data.mime_type,
132
+ data: data.data,
133
+ },
134
+ };
144
135
  }
145
136
  else {
146
- throw new Error("Please provide image as base64 encoded data URL");
147
- }
148
- const [dm, data] = source.split(",");
149
- if (!dm.startsWith("data:")) {
150
- throw new Error("Please provide image as base64 encoded data URL");
151
- }
152
- const [mimeType, encoding] = dm.replace(/^data:/, "").split(";");
153
- if (encoding !== "base64") {
154
- throw new Error("Please provide image as base64 encoded data URL");
137
+ return {
138
+ fileData: {
139
+ mimeType: block.mime_type ?? "",
140
+ fileUri: block.url,
141
+ },
142
+ };
155
143
  }
144
+ }
145
+ if (block.source_type === "base64") {
156
146
  return {
157
147
  inlineData: {
158
- data,
159
- mimeType,
148
+ mimeType: block.mime_type ?? "",
149
+ data: block.data,
160
150
  },
161
151
  };
162
152
  }
163
- else if (c.type === "media") {
164
- return messageContentMedia(c);
153
+ throw new Error(`Unsupported source type: ${block.source_type}`);
154
+ },
155
+ fromStandardFileBlock(block) {
156
+ if (!isMultimodalModel) {
157
+ throw new Error("This model does not support files");
165
158
  }
166
- else if (c.type === "tool_use") {
159
+ if (block.source_type === "text") {
167
160
  return {
168
- functionCall: {
169
- name: c.name,
170
- args: c.input,
171
- },
161
+ text: block.text,
172
162
  };
173
163
  }
174
- else if (c.type?.includes("/") &&
175
- // Ensure it's a single slash.
176
- c.type.split("/").length === 2 &&
177
- "data" in c &&
178
- typeof c.data === "string") {
164
+ if (block.source_type === "url") {
165
+ const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
166
+ if (data) {
167
+ return {
168
+ inlineData: {
169
+ mimeType: data.mime_type,
170
+ data: data.data,
171
+ },
172
+ };
173
+ }
174
+ else {
175
+ return {
176
+ fileData: {
177
+ mimeType: block.mime_type ?? "",
178
+ fileUri: block.url,
179
+ },
180
+ };
181
+ }
182
+ }
183
+ if (block.source_type === "base64") {
179
184
  return {
180
185
  inlineData: {
181
- mimeType: c.type,
182
- data: c.data,
186
+ mimeType: block.mime_type ?? "",
187
+ data: block.data,
183
188
  },
184
189
  };
185
190
  }
186
- throw new Error(`Unknown content type ${c.type}`);
191
+ throw new Error(`Unsupported source type: ${block.source_type}`);
192
+ },
193
+ };
194
+ return standardContentBlockConverter;
195
+ }
196
+ function _convertLangChainContentToPart(content, isMultimodalModel) {
197
+ if ((0, messages_1.isDataContentBlock)(content)) {
198
+ return (0, messages_1.convertToProviderContentBlock)(content, _getStandardContentBlockConverter(isMultimodalModel));
199
+ }
200
+ if (content.type === "text") {
201
+ return { text: content.text };
202
+ }
203
+ else if (content.type === "executableCode") {
204
+ return { executableCode: content.executableCode };
205
+ }
206
+ else if (content.type === "codeExecutionResult") {
207
+ return { codeExecutionResult: content.codeExecutionResult };
208
+ }
209
+ else if (content.type === "image_url") {
210
+ if (!isMultimodalModel) {
211
+ throw new Error(`This model does not support images`);
212
+ }
213
+ let source;
214
+ if (typeof content.image_url === "string") {
215
+ source = content.image_url;
216
+ }
217
+ else if (typeof content.image_url === "object" &&
218
+ "url" in content.image_url) {
219
+ source = content.image_url.url;
220
+ }
221
+ else {
222
+ throw new Error("Please provide image as base64 encoded data URL");
223
+ }
224
+ const [dm, data] = source.split(",");
225
+ if (!dm.startsWith("data:")) {
226
+ throw new Error("Please provide image as base64 encoded data URL");
227
+ }
228
+ const [mimeType, encoding] = dm.replace(/^data:/, "").split(";");
229
+ if (encoding !== "base64") {
230
+ throw new Error("Please provide image as base64 encoded data URL");
231
+ }
232
+ return {
233
+ inlineData: {
234
+ data,
235
+ mimeType,
236
+ },
237
+ };
238
+ }
239
+ else if (content.type === "media") {
240
+ return messageContentMedia(content);
241
+ }
242
+ else if (content.type === "tool_use") {
243
+ return {
244
+ functionCall: {
245
+ name: content.name,
246
+ args: content.input,
247
+ },
248
+ };
249
+ }
250
+ else if (content.type?.includes("/") &&
251
+ // Ensure it's a single slash.
252
+ content.type.split("/").length === 2 &&
253
+ "data" in content &&
254
+ typeof content.data === "string") {
255
+ return {
256
+ inlineData: {
257
+ mimeType: content.type,
258
+ data: content.data,
259
+ },
260
+ };
261
+ }
262
+ else if ("functionCall" in content) {
263
+ // No action needed here — function calls will be added later from message.tool_calls
264
+ return undefined;
265
+ }
266
+ else {
267
+ if ("type" in content) {
268
+ throw new Error(`Unknown content type ${content.type}`);
269
+ }
270
+ else {
271
+ throw new Error(`Unknown content ${JSON.stringify(content)}`);
272
+ }
273
+ }
274
+ }
275
+ function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
276
+ if ((0, messages_1.isToolMessage)(message)) {
277
+ const messageName = message.name ??
278
+ inferToolNameFromPreviousMessages(message, previousMessages);
279
+ if (messageName === undefined) {
280
+ throw new Error(`Google requires a tool name for each tool call response, and we could not infer a called tool name for ToolMessage "${message.id}" from your passed messages. Please populate a "name" field on that ToolMessage explicitly.`);
281
+ }
282
+ const result = Array.isArray(message.content)
283
+ ? message.content
284
+ .map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
285
+ .filter((p) => p !== undefined)
286
+ : message.content;
287
+ if (message.status === "error") {
288
+ return [
289
+ {
290
+ functionResponse: {
291
+ name: messageName,
292
+ // The API expects an object with an `error` field if the function call fails.
293
+ // `error` must be a valid object (not a string or array), so we wrap `message.content` here
294
+ response: { error: { details: result } },
295
+ },
296
+ },
297
+ ];
298
+ }
299
+ return [
300
+ {
301
+ functionResponse: {
302
+ name: messageName,
303
+ // again, can't have a string or array value for `response`, so we wrap it as an object here
304
+ response: { result },
305
+ },
306
+ },
307
+ ];
308
+ }
309
+ let functionCalls = [];
310
+ const messageParts = [];
311
+ if (typeof message.content === "string" && message.content) {
312
+ messageParts.push({ text: message.content });
313
+ }
314
+ if (Array.isArray(message.content)) {
315
+ messageParts.push(...message.content
316
+ .map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
317
+ .filter((p) => p !== undefined));
318
+ }
319
+ if ((0, messages_1.isAIMessage)(message) && message.tool_calls?.length) {
320
+ functionCalls = message.tool_calls.map((tc) => {
321
+ return {
322
+ functionCall: {
323
+ name: tc.name,
324
+ args: tc.args,
325
+ },
326
+ };
187
327
  });
188
328
  }
189
- return [...messageParts, ...functionCalls, ...functionResponses];
329
+ return [...messageParts, ...functionCalls];
190
330
  }
191
331
  exports.convertMessageContentToParts = convertMessageContentToParts;
192
332
  function convertBaseMessagesToContent(messages, isMultimodalModel, convertSystemMessageToHumanContent = false) {
@@ -1,4 +1,4 @@
1
- import { AIMessage, AIMessageChunk, ChatMessage, isAIMessage, isBaseMessage, isToolMessage, } from "@langchain/core/messages";
1
+ import { AIMessage, AIMessageChunk, ChatMessage, isAIMessage, isBaseMessage, isToolMessage, parseBase64DataUrl, convertToProviderContentBlock, isDataContentBlock, } from "@langchain/core/messages";
2
2
  import { ChatGenerationChunk, } from "@langchain/core/outputs";
3
3
  import { isLangChainTool } from "@langchain/core/utils/function_calling";
4
4
  import { isOpenAITool } from "@langchain/core/language_models/base";
@@ -73,115 +73,255 @@ function inferToolNameFromPreviousMessages(message, previousMessages) {
73
73
  return toolCall.id === message.tool_call_id;
74
74
  })?.name;
75
75
  }
76
- export function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
77
- if (typeof message.content === "string" &&
78
- message.content !== "" &&
79
- !isToolMessage(message)) {
80
- return [{ text: message.content }];
81
- }
82
- let functionCalls = [];
83
- let functionResponses = [];
84
- let messageParts = [];
85
- if ("tool_calls" in message &&
86
- Array.isArray(message.tool_calls) &&
87
- message.tool_calls.length > 0) {
88
- functionCalls = message.tool_calls.map((tc) => ({
89
- functionCall: {
90
- name: tc.name,
91
- args: tc.args,
92
- },
93
- }));
94
- }
95
- else if (isToolMessage(message) && message.content) {
96
- const messageName = message.name ??
97
- inferToolNameFromPreviousMessages(message, previousMessages);
98
- if (messageName === undefined) {
99
- throw new Error(`Google requires a tool name for each tool call response, and we could not infer a called tool name for ToolMessage "${message.id}" from your passed messages. Please populate a "name" field on that ToolMessage explicitly.`);
100
- }
101
- functionResponses = [
102
- {
103
- functionResponse: {
104
- name: messageName,
105
- response: typeof message.content === "string"
106
- ? { result: message.content }
107
- : message.content,
108
- },
109
- },
110
- ];
111
- }
112
- else if (Array.isArray(message.content)) {
113
- messageParts = message.content.map((c) => {
114
- if (c.type === "text") {
115
- return {
116
- text: c.text,
117
- };
76
+ function _getStandardContentBlockConverter(isMultimodalModel) {
77
+ const standardContentBlockConverter = {
78
+ providerName: "Google Gemini",
79
+ fromStandardTextBlock(block) {
80
+ return {
81
+ text: block.text,
82
+ };
83
+ },
84
+ fromStandardImageBlock(block) {
85
+ if (!isMultimodalModel) {
86
+ throw new Error("This model does not support images");
118
87
  }
119
- else if (c.type === "executableCode") {
120
- return {
121
- executableCode: c.executableCode,
122
- };
88
+ if (block.source_type === "url") {
89
+ const data = parseBase64DataUrl({ dataUrl: block.url });
90
+ if (data) {
91
+ return {
92
+ inlineData: {
93
+ mimeType: data.mime_type,
94
+ data: data.data,
95
+ },
96
+ };
97
+ }
98
+ else {
99
+ return {
100
+ fileData: {
101
+ mimeType: block.mime_type ?? "",
102
+ fileUri: block.url,
103
+ },
104
+ };
105
+ }
123
106
  }
124
- else if (c.type === "codeExecutionResult") {
107
+ if (block.source_type === "base64") {
125
108
  return {
126
- codeExecutionResult: c.codeExecutionResult,
109
+ inlineData: {
110
+ mimeType: block.mime_type ?? "",
111
+ data: block.data,
112
+ },
127
113
  };
128
114
  }
129
- if (c.type === "image_url") {
130
- if (!isMultimodalModel) {
131
- throw new Error(`This model does not support images`);
132
- }
133
- let source;
134
- if (typeof c.image_url === "string") {
135
- source = c.image_url;
136
- }
137
- else if (typeof c.image_url === "object" && "url" in c.image_url) {
138
- source = c.image_url.url;
115
+ throw new Error(`Unsupported source type: ${block.source_type}`);
116
+ },
117
+ fromStandardAudioBlock(block) {
118
+ if (!isMultimodalModel) {
119
+ throw new Error("This model does not support audio");
120
+ }
121
+ if (block.source_type === "url") {
122
+ const data = parseBase64DataUrl({ dataUrl: block.url });
123
+ if (data) {
124
+ return {
125
+ inlineData: {
126
+ mimeType: data.mime_type,
127
+ data: data.data,
128
+ },
129
+ };
139
130
  }
140
131
  else {
141
- throw new Error("Please provide image as base64 encoded data URL");
142
- }
143
- const [dm, data] = source.split(",");
144
- if (!dm.startsWith("data:")) {
145
- throw new Error("Please provide image as base64 encoded data URL");
146
- }
147
- const [mimeType, encoding] = dm.replace(/^data:/, "").split(";");
148
- if (encoding !== "base64") {
149
- throw new Error("Please provide image as base64 encoded data URL");
132
+ return {
133
+ fileData: {
134
+ mimeType: block.mime_type ?? "",
135
+ fileUri: block.url,
136
+ },
137
+ };
150
138
  }
139
+ }
140
+ if (block.source_type === "base64") {
151
141
  return {
152
142
  inlineData: {
153
- data,
154
- mimeType,
143
+ mimeType: block.mime_type ?? "",
144
+ data: block.data,
155
145
  },
156
146
  };
157
147
  }
158
- else if (c.type === "media") {
159
- return messageContentMedia(c);
148
+ throw new Error(`Unsupported source type: ${block.source_type}`);
149
+ },
150
+ fromStandardFileBlock(block) {
151
+ if (!isMultimodalModel) {
152
+ throw new Error("This model does not support files");
160
153
  }
161
- else if (c.type === "tool_use") {
154
+ if (block.source_type === "text") {
162
155
  return {
163
- functionCall: {
164
- name: c.name,
165
- args: c.input,
166
- },
156
+ text: block.text,
167
157
  };
168
158
  }
169
- else if (c.type?.includes("/") &&
170
- // Ensure it's a single slash.
171
- c.type.split("/").length === 2 &&
172
- "data" in c &&
173
- typeof c.data === "string") {
159
+ if (block.source_type === "url") {
160
+ const data = parseBase64DataUrl({ dataUrl: block.url });
161
+ if (data) {
162
+ return {
163
+ inlineData: {
164
+ mimeType: data.mime_type,
165
+ data: data.data,
166
+ },
167
+ };
168
+ }
169
+ else {
170
+ return {
171
+ fileData: {
172
+ mimeType: block.mime_type ?? "",
173
+ fileUri: block.url,
174
+ },
175
+ };
176
+ }
177
+ }
178
+ if (block.source_type === "base64") {
174
179
  return {
175
180
  inlineData: {
176
- mimeType: c.type,
177
- data: c.data,
181
+ mimeType: block.mime_type ?? "",
182
+ data: block.data,
178
183
  },
179
184
  };
180
185
  }
181
- throw new Error(`Unknown content type ${c.type}`);
186
+ throw new Error(`Unsupported source type: ${block.source_type}`);
187
+ },
188
+ };
189
+ return standardContentBlockConverter;
190
+ }
191
+ function _convertLangChainContentToPart(content, isMultimodalModel) {
192
+ if (isDataContentBlock(content)) {
193
+ return convertToProviderContentBlock(content, _getStandardContentBlockConverter(isMultimodalModel));
194
+ }
195
+ if (content.type === "text") {
196
+ return { text: content.text };
197
+ }
198
+ else if (content.type === "executableCode") {
199
+ return { executableCode: content.executableCode };
200
+ }
201
+ else if (content.type === "codeExecutionResult") {
202
+ return { codeExecutionResult: content.codeExecutionResult };
203
+ }
204
+ else if (content.type === "image_url") {
205
+ if (!isMultimodalModel) {
206
+ throw new Error(`This model does not support images`);
207
+ }
208
+ let source;
209
+ if (typeof content.image_url === "string") {
210
+ source = content.image_url;
211
+ }
212
+ else if (typeof content.image_url === "object" &&
213
+ "url" in content.image_url) {
214
+ source = content.image_url.url;
215
+ }
216
+ else {
217
+ throw new Error("Please provide image as base64 encoded data URL");
218
+ }
219
+ const [dm, data] = source.split(",");
220
+ if (!dm.startsWith("data:")) {
221
+ throw new Error("Please provide image as base64 encoded data URL");
222
+ }
223
+ const [mimeType, encoding] = dm.replace(/^data:/, "").split(";");
224
+ if (encoding !== "base64") {
225
+ throw new Error("Please provide image as base64 encoded data URL");
226
+ }
227
+ return {
228
+ inlineData: {
229
+ data,
230
+ mimeType,
231
+ },
232
+ };
233
+ }
234
+ else if (content.type === "media") {
235
+ return messageContentMedia(content);
236
+ }
237
+ else if (content.type === "tool_use") {
238
+ return {
239
+ functionCall: {
240
+ name: content.name,
241
+ args: content.input,
242
+ },
243
+ };
244
+ }
245
+ else if (content.type?.includes("/") &&
246
+ // Ensure it's a single slash.
247
+ content.type.split("/").length === 2 &&
248
+ "data" in content &&
249
+ typeof content.data === "string") {
250
+ return {
251
+ inlineData: {
252
+ mimeType: content.type,
253
+ data: content.data,
254
+ },
255
+ };
256
+ }
257
+ else if ("functionCall" in content) {
258
+ // No action needed here — function calls will be added later from message.tool_calls
259
+ return undefined;
260
+ }
261
+ else {
262
+ if ("type" in content) {
263
+ throw new Error(`Unknown content type ${content.type}`);
264
+ }
265
+ else {
266
+ throw new Error(`Unknown content ${JSON.stringify(content)}`);
267
+ }
268
+ }
269
+ }
270
+ export function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
271
+ if (isToolMessage(message)) {
272
+ const messageName = message.name ??
273
+ inferToolNameFromPreviousMessages(message, previousMessages);
274
+ if (messageName === undefined) {
275
+ throw new Error(`Google requires a tool name for each tool call response, and we could not infer a called tool name for ToolMessage "${message.id}" from your passed messages. Please populate a "name" field on that ToolMessage explicitly.`);
276
+ }
277
+ const result = Array.isArray(message.content)
278
+ ? message.content
279
+ .map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
280
+ .filter((p) => p !== undefined)
281
+ : message.content;
282
+ if (message.status === "error") {
283
+ return [
284
+ {
285
+ functionResponse: {
286
+ name: messageName,
287
+ // The API expects an object with an `error` field if the function call fails.
288
+ // `error` must be a valid object (not a string or array), so we wrap `message.content` here
289
+ response: { error: { details: result } },
290
+ },
291
+ },
292
+ ];
293
+ }
294
+ return [
295
+ {
296
+ functionResponse: {
297
+ name: messageName,
298
+ // again, can't have a string or array value for `response`, so we wrap it as an object here
299
+ response: { result },
300
+ },
301
+ },
302
+ ];
303
+ }
304
+ let functionCalls = [];
305
+ const messageParts = [];
306
+ if (typeof message.content === "string" && message.content) {
307
+ messageParts.push({ text: message.content });
308
+ }
309
+ if (Array.isArray(message.content)) {
310
+ messageParts.push(...message.content
311
+ .map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
312
+ .filter((p) => p !== undefined));
313
+ }
314
+ if (isAIMessage(message) && message.tool_calls?.length) {
315
+ functionCalls = message.tool_calls.map((tc) => {
316
+ return {
317
+ functionCall: {
318
+ name: tc.name,
319
+ args: tc.args,
320
+ },
321
+ };
182
322
  });
183
323
  }
184
- return [...messageParts, ...functionCalls, ...functionResponses];
324
+ return [...messageParts, ...functionCalls];
185
325
  }
186
326
  export function convertBaseMessagesToContent(messages, isMultimodalModel, convertSystemMessageToHumanContent = false) {
187
327
  return messages.reduce((acc, message, index) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/google-genai",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Google Generative AI integration for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@jest/globals": "^29.5.0",
47
- "@langchain/core": "0.3.55",
47
+ "@langchain/core": "0.3.57",
48
48
  "@langchain/scripts": ">=0.1.0 <0.2.0",
49
49
  "@langchain/standard-tests": "0.0.0",
50
50
  "@swc/core": "^1.3.90",