@langchain/google-genai 0.2.9 → 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 +1 -0
- package/dist/chat_models.cjs +14 -10
- package/dist/chat_models.d.ts +4 -5
- package/dist/chat_models.js +14 -10
- package/dist/utils/common.cjs +216 -77
- package/dist/utils/common.js +217 -78
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/chat_models.cjs
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
34
|
-
* const llmWithArgsBound = llm.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
804
|
-
|
|
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.
|
|
814
|
+
llm = this.withConfig({
|
|
811
815
|
responseSchema: jsonSchema,
|
|
812
816
|
});
|
|
813
817
|
outputParser = new output_parsers_1.JsonOutputParser();
|
package/dist/chat_models.d.ts
CHANGED
|
@@ -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 model
|
|
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 `.
|
|
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 `.
|
|
146
|
-
* const llmWithArgsBound = llm.
|
|
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
|
package/dist/chat_models.js
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
31
|
-
* const llmWithArgsBound = llm.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
801
|
-
|
|
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.
|
|
811
|
+
llm = this.withConfig({
|
|
808
812
|
responseSchema: jsonSchema,
|
|
809
813
|
});
|
|
810
814
|
outputParser = new JsonOutputParser();
|
package/dist/utils/common.cjs
CHANGED
|
@@ -78,6 +78,200 @@ function inferToolNameFromPreviousMessages(message, previousMessages) {
|
|
|
78
78
|
return toolCall.id === message.tool_call_id;
|
|
79
79
|
})?.name;
|
|
80
80
|
}
|
|
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");
|
|
92
|
+
}
|
|
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
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (block.source_type === "base64") {
|
|
113
|
+
return {
|
|
114
|
+
inlineData: {
|
|
115
|
+
mimeType: block.mime_type ?? "",
|
|
116
|
+
data: block.data,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
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
|
+
};
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return {
|
|
138
|
+
fileData: {
|
|
139
|
+
mimeType: block.mime_type ?? "",
|
|
140
|
+
fileUri: block.url,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (block.source_type === "base64") {
|
|
146
|
+
return {
|
|
147
|
+
inlineData: {
|
|
148
|
+
mimeType: block.mime_type ?? "",
|
|
149
|
+
data: block.data,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
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");
|
|
158
|
+
}
|
|
159
|
+
if (block.source_type === "text") {
|
|
160
|
+
return {
|
|
161
|
+
text: block.text,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
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") {
|
|
184
|
+
return {
|
|
185
|
+
inlineData: {
|
|
186
|
+
mimeType: block.mime_type ?? "",
|
|
187
|
+
data: block.data,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
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
|
+
}
|
|
81
275
|
function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
|
|
82
276
|
if ((0, messages_1.isToolMessage)(message)) {
|
|
83
277
|
const messageName = message.name ??
|
|
@@ -85,13 +279,29 @@ function convertMessageContentToParts(message, isMultimodalModel, previousMessag
|
|
|
85
279
|
if (messageName === undefined) {
|
|
86
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.`);
|
|
87
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
|
+
}
|
|
88
299
|
return [
|
|
89
300
|
{
|
|
90
301
|
functionResponse: {
|
|
91
302
|
name: messageName,
|
|
92
|
-
response
|
|
93
|
-
|
|
94
|
-
: message.content,
|
|
303
|
+
// again, can't have a string or array value for `response`, so we wrap it as an object here
|
|
304
|
+
response: { result },
|
|
95
305
|
},
|
|
96
306
|
},
|
|
97
307
|
];
|
|
@@ -102,80 +312,9 @@ function convertMessageContentToParts(message, isMultimodalModel, previousMessag
|
|
|
102
312
|
messageParts.push({ text: message.content });
|
|
103
313
|
}
|
|
104
314
|
if (Array.isArray(message.content)) {
|
|
105
|
-
message.content
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
else if (c.type === "executableCode") {
|
|
110
|
-
messageParts.push({ executableCode: c.executableCode });
|
|
111
|
-
}
|
|
112
|
-
else if (c.type === "codeExecutionResult") {
|
|
113
|
-
messageParts.push({ codeExecutionResult: c.codeExecutionResult });
|
|
114
|
-
}
|
|
115
|
-
else if (c.type === "image_url") {
|
|
116
|
-
if (!isMultimodalModel) {
|
|
117
|
-
throw new Error(`This model does not support images`);
|
|
118
|
-
}
|
|
119
|
-
let source;
|
|
120
|
-
if (typeof c.image_url === "string") {
|
|
121
|
-
source = c.image_url;
|
|
122
|
-
}
|
|
123
|
-
else if (typeof c.image_url === "object" && "url" in c.image_url) {
|
|
124
|
-
source = c.image_url.url;
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
throw new Error("Please provide image as base64 encoded data URL");
|
|
128
|
-
}
|
|
129
|
-
const [dm, data] = source.split(",");
|
|
130
|
-
if (!dm.startsWith("data:")) {
|
|
131
|
-
throw new Error("Please provide image as base64 encoded data URL");
|
|
132
|
-
}
|
|
133
|
-
const [mimeType, encoding] = dm.replace(/^data:/, "").split(";");
|
|
134
|
-
if (encoding !== "base64") {
|
|
135
|
-
throw new Error("Please provide image as base64 encoded data URL");
|
|
136
|
-
}
|
|
137
|
-
messageParts.push({
|
|
138
|
-
inlineData: {
|
|
139
|
-
data,
|
|
140
|
-
mimeType,
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
else if (c.type === "media") {
|
|
145
|
-
messageParts.push(messageContentMedia(c));
|
|
146
|
-
}
|
|
147
|
-
else if (c.type === "tool_use") {
|
|
148
|
-
functionCalls.push({
|
|
149
|
-
functionCall: {
|
|
150
|
-
name: c.name,
|
|
151
|
-
args: c.input,
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
else if (c.type?.includes("/") &&
|
|
156
|
-
// Ensure it's a single slash.
|
|
157
|
-
c.type.split("/").length === 2 &&
|
|
158
|
-
"data" in c &&
|
|
159
|
-
typeof c.data === "string") {
|
|
160
|
-
messageParts.push({
|
|
161
|
-
inlineData: {
|
|
162
|
-
mimeType: c.type,
|
|
163
|
-
data: c.data,
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
else if ("functionCall" in c) {
|
|
168
|
-
// No action needed here — function calls will be added later from message.tool_calls
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
if ("type" in c) {
|
|
172
|
-
throw new Error(`Unknown content type ${c.type}`);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
throw new Error(`Unknown content ${JSON.stringify(c)}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
});
|
|
315
|
+
messageParts.push(...message.content
|
|
316
|
+
.map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
|
|
317
|
+
.filter((p) => p !== undefined));
|
|
179
318
|
}
|
|
180
319
|
if ((0, messages_1.isAIMessage)(message) && message.tool_calls?.length) {
|
|
181
320
|
functionCalls = message.tool_calls.map((tc) => {
|
package/dist/utils/common.js
CHANGED
|
@@ -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,6 +73,200 @@ function inferToolNameFromPreviousMessages(message, previousMessages) {
|
|
|
73
73
|
return toolCall.id === message.tool_call_id;
|
|
74
74
|
})?.name;
|
|
75
75
|
}
|
|
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");
|
|
87
|
+
}
|
|
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
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (block.source_type === "base64") {
|
|
108
|
+
return {
|
|
109
|
+
inlineData: {
|
|
110
|
+
mimeType: block.mime_type ?? "",
|
|
111
|
+
data: block.data,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
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
|
+
};
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
return {
|
|
133
|
+
fileData: {
|
|
134
|
+
mimeType: block.mime_type ?? "",
|
|
135
|
+
fileUri: block.url,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (block.source_type === "base64") {
|
|
141
|
+
return {
|
|
142
|
+
inlineData: {
|
|
143
|
+
mimeType: block.mime_type ?? "",
|
|
144
|
+
data: block.data,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
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");
|
|
153
|
+
}
|
|
154
|
+
if (block.source_type === "text") {
|
|
155
|
+
return {
|
|
156
|
+
text: block.text,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
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") {
|
|
179
|
+
return {
|
|
180
|
+
inlineData: {
|
|
181
|
+
mimeType: block.mime_type ?? "",
|
|
182
|
+
data: block.data,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
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
|
+
}
|
|
76
270
|
export function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
|
|
77
271
|
if (isToolMessage(message)) {
|
|
78
272
|
const messageName = message.name ??
|
|
@@ -80,13 +274,29 @@ export function convertMessageContentToParts(message, isMultimodalModel, previou
|
|
|
80
274
|
if (messageName === undefined) {
|
|
81
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.`);
|
|
82
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
|
+
}
|
|
83
294
|
return [
|
|
84
295
|
{
|
|
85
296
|
functionResponse: {
|
|
86
297
|
name: messageName,
|
|
87
|
-
response
|
|
88
|
-
|
|
89
|
-
: message.content,
|
|
298
|
+
// again, can't have a string or array value for `response`, so we wrap it as an object here
|
|
299
|
+
response: { result },
|
|
90
300
|
},
|
|
91
301
|
},
|
|
92
302
|
];
|
|
@@ -97,80 +307,9 @@ export function convertMessageContentToParts(message, isMultimodalModel, previou
|
|
|
97
307
|
messageParts.push({ text: message.content });
|
|
98
308
|
}
|
|
99
309
|
if (Array.isArray(message.content)) {
|
|
100
|
-
message.content
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
else if (c.type === "executableCode") {
|
|
105
|
-
messageParts.push({ executableCode: c.executableCode });
|
|
106
|
-
}
|
|
107
|
-
else if (c.type === "codeExecutionResult") {
|
|
108
|
-
messageParts.push({ codeExecutionResult: c.codeExecutionResult });
|
|
109
|
-
}
|
|
110
|
-
else if (c.type === "image_url") {
|
|
111
|
-
if (!isMultimodalModel) {
|
|
112
|
-
throw new Error(`This model does not support images`);
|
|
113
|
-
}
|
|
114
|
-
let source;
|
|
115
|
-
if (typeof c.image_url === "string") {
|
|
116
|
-
source = c.image_url;
|
|
117
|
-
}
|
|
118
|
-
else if (typeof c.image_url === "object" && "url" in c.image_url) {
|
|
119
|
-
source = c.image_url.url;
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
throw new Error("Please provide image as base64 encoded data URL");
|
|
123
|
-
}
|
|
124
|
-
const [dm, data] = source.split(",");
|
|
125
|
-
if (!dm.startsWith("data:")) {
|
|
126
|
-
throw new Error("Please provide image as base64 encoded data URL");
|
|
127
|
-
}
|
|
128
|
-
const [mimeType, encoding] = dm.replace(/^data:/, "").split(";");
|
|
129
|
-
if (encoding !== "base64") {
|
|
130
|
-
throw new Error("Please provide image as base64 encoded data URL");
|
|
131
|
-
}
|
|
132
|
-
messageParts.push({
|
|
133
|
-
inlineData: {
|
|
134
|
-
data,
|
|
135
|
-
mimeType,
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
else if (c.type === "media") {
|
|
140
|
-
messageParts.push(messageContentMedia(c));
|
|
141
|
-
}
|
|
142
|
-
else if (c.type === "tool_use") {
|
|
143
|
-
functionCalls.push({
|
|
144
|
-
functionCall: {
|
|
145
|
-
name: c.name,
|
|
146
|
-
args: c.input,
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
else if (c.type?.includes("/") &&
|
|
151
|
-
// Ensure it's a single slash.
|
|
152
|
-
c.type.split("/").length === 2 &&
|
|
153
|
-
"data" in c &&
|
|
154
|
-
typeof c.data === "string") {
|
|
155
|
-
messageParts.push({
|
|
156
|
-
inlineData: {
|
|
157
|
-
mimeType: c.type,
|
|
158
|
-
data: c.data,
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
else if ("functionCall" in c) {
|
|
163
|
-
// No action needed here — function calls will be added later from message.tool_calls
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
if ("type" in c) {
|
|
167
|
-
throw new Error(`Unknown content type ${c.type}`);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
throw new Error(`Unknown content ${JSON.stringify(c)}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
});
|
|
310
|
+
messageParts.push(...message.content
|
|
311
|
+
.map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
|
|
312
|
+
.filter((p) => p !== undefined));
|
|
174
313
|
}
|
|
175
314
|
if (isAIMessage(message) && message.tool_calls?.length) {
|
|
176
315
|
functionCalls = message.tool_calls.map((tc) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/google-genai",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
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",
|