@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 +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 +225 -85
- package/dist/utils/common.js +226 -86
- 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,115 +78,255 @@ function inferToolNameFromPreviousMessages(message, previousMessages) {
|
|
|
78
78
|
return toolCall.id === message.tool_call_id;
|
|
79
79
|
})?.name;
|
|
80
80
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
112
|
+
if (block.source_type === "base64") {
|
|
130
113
|
return {
|
|
131
|
-
|
|
114
|
+
inlineData: {
|
|
115
|
+
mimeType: block.mime_type ?? "",
|
|
116
|
+
data: block.data,
|
|
117
|
+
},
|
|
132
118
|
};
|
|
133
119
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
159
|
-
|
|
148
|
+
mimeType: block.mime_type ?? "",
|
|
149
|
+
data: block.data,
|
|
160
150
|
},
|
|
161
151
|
};
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
159
|
+
if (block.source_type === "text") {
|
|
167
160
|
return {
|
|
168
|
-
|
|
169
|
-
name: c.name,
|
|
170
|
-
args: c.input,
|
|
171
|
-
},
|
|
161
|
+
text: block.text,
|
|
172
162
|
};
|
|
173
163
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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:
|
|
182
|
-
data:
|
|
186
|
+
mimeType: block.mime_type ?? "",
|
|
187
|
+
data: block.data,
|
|
183
188
|
},
|
|
184
189
|
};
|
|
185
190
|
}
|
|
186
|
-
throw new Error(`
|
|
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
|
|
329
|
+
return [...messageParts, ...functionCalls];
|
|
190
330
|
}
|
|
191
331
|
exports.convertMessageContentToParts = convertMessageContentToParts;
|
|
192
332
|
function convertBaseMessagesToContent(messages, isMultimodalModel, convertSystemMessageToHumanContent = false) {
|
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,115 +73,255 @@ function inferToolNameFromPreviousMessages(message, previousMessages) {
|
|
|
73
73
|
return toolCall.id === message.tool_call_id;
|
|
74
74
|
})?.name;
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
107
|
+
if (block.source_type === "base64") {
|
|
125
108
|
return {
|
|
126
|
-
|
|
109
|
+
inlineData: {
|
|
110
|
+
mimeType: block.mime_type ?? "",
|
|
111
|
+
data: block.data,
|
|
112
|
+
},
|
|
127
113
|
};
|
|
128
114
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
154
|
-
|
|
143
|
+
mimeType: block.mime_type ?? "",
|
|
144
|
+
data: block.data,
|
|
155
145
|
},
|
|
156
146
|
};
|
|
157
147
|
}
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
154
|
+
if (block.source_type === "text") {
|
|
162
155
|
return {
|
|
163
|
-
|
|
164
|
-
name: c.name,
|
|
165
|
-
args: c.input,
|
|
166
|
-
},
|
|
156
|
+
text: block.text,
|
|
167
157
|
};
|
|
168
158
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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:
|
|
177
|
-
data:
|
|
181
|
+
mimeType: block.mime_type ?? "",
|
|
182
|
+
data: block.data,
|
|
178
183
|
},
|
|
179
184
|
};
|
|
180
185
|
}
|
|
181
|
-
throw new Error(`
|
|
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
|
|
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.
|
|
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",
|