@langchain/google-genai 0.2.9 → 0.2.11
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 +15 -11
- package/dist/chat_models.d.ts +7 -8
- package/dist/chat_models.js +16 -12
- package/dist/output_parsers.cjs +3 -2
- package/dist/output_parsers.d.ts +4 -4
- package/dist/output_parsers.js +3 -2
- package/dist/utils/common.cjs +238 -86
- package/dist/utils/common.js +239 -87
- package/dist/utils/zod_to_genai_parameters.cjs +2 -2
- package/dist/utils/zod_to_genai_parameters.d.ts +3 -3
- package/dist/utils/zod_to_genai_parameters.js +3 -3
- package/package.json +6 -7
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, malformed function call, etc.
|
|
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) {
|
|
@@ -755,7 +760,7 @@ class ChatGoogleGenerativeAI extends chat_models_1.BaseChatModel {
|
|
|
755
760
|
if (method === "functionCalling") {
|
|
756
761
|
let functionName = name ?? "extract";
|
|
757
762
|
let tools;
|
|
758
|
-
if ((0, types_1.
|
|
763
|
+
if ((0, types_1.isInteropZodSchema)(schema)) {
|
|
759
764
|
const jsonSchema = (0, zod_to_genai_parameters_js_1.schemaToGenerativeAIParameters)(schema);
|
|
760
765
|
tools = [
|
|
761
766
|
{
|
|
@@ -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
|
@@ -5,7 +5,7 @@ import { ChatGenerationChunk, ChatResult } from "@langchain/core/outputs";
|
|
|
5
5
|
import { BaseChatModel, type BaseChatModelCallOptions, type LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
|
|
6
6
|
import { BaseLanguageModelInput, StructuredOutputMethodOptions } from "@langchain/core/language_models/base";
|
|
7
7
|
import { Runnable } from "@langchain/core/runnables";
|
|
8
|
-
import
|
|
8
|
+
import { InteropZodType } from "@langchain/core/utils/types";
|
|
9
9
|
import { GoogleGenerativeAIToolType } from "./types.js";
|
|
10
10
|
export type BaseMessageExamplePair = {
|
|
11
11
|
input: BaseMessage;
|
|
@@ -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
|
|
@@ -535,8 +534,8 @@ export declare class ChatGoogleGenerativeAI extends BaseChatModel<GoogleGenerati
|
|
|
535
534
|
_generate(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
|
|
536
535
|
_streamResponseChunks(messages: BaseMessage[], options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun): AsyncGenerator<ChatGenerationChunk>;
|
|
537
536
|
completionWithRetry(request: string | GenerateContentRequest | (string | GenerativeAIPart)[], options?: this["ParsedCallOptions"]): Promise<import("@google/generative-ai").GenerateContentResult>;
|
|
538
|
-
withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema:
|
|
539
|
-
withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema:
|
|
537
|
+
withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema: InteropZodType<RunOutput> | Record<string, any>, config?: StructuredOutputMethodOptions<false>): Runnable<BaseLanguageModelInput, RunOutput>;
|
|
538
|
+
withStructuredOutput<RunOutput extends Record<string, any> = Record<string, any>>(outputSchema: InteropZodType<RunOutput> | Record<string, any>, config?: StructuredOutputMethodOptions<true>): Runnable<BaseLanguageModelInput, {
|
|
540
539
|
raw: BaseMessage;
|
|
541
540
|
parsed: RunOutput;
|
|
542
541
|
}>;
|
package/dist/chat_models.js
CHANGED
|
@@ -2,7 +2,7 @@ import { GoogleGenerativeAI as GenerativeAI, } from "@google/generative-ai";
|
|
|
2
2
|
import { getEnvironmentVariable } from "@langchain/core/utils/env";
|
|
3
3
|
import { BaseChatModel, } from "@langchain/core/language_models/chat_models";
|
|
4
4
|
import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables";
|
|
5
|
-
import {
|
|
5
|
+
import { isInteropZodSchema, } from "@langchain/core/utils/types";
|
|
6
6
|
import { JsonOutputParser, } from "@langchain/core/output_parsers";
|
|
7
7
|
import { schemaToGenerativeAIParameters, removeAdditionalProperties, } from "./utils/zod_to_genai_parameters.js";
|
|
8
8
|
import { convertBaseMessagesToContent, convertResponseContentToChatGenerationChunk, mapGenerateContentResultToChatResult, } from "./utils/common.js";
|
|
@@ -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, malformed function call, etc.
|
|
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) {
|
|
@@ -752,7 +757,7 @@ export class ChatGoogleGenerativeAI extends BaseChatModel {
|
|
|
752
757
|
if (method === "functionCalling") {
|
|
753
758
|
let functionName = name ?? "extract";
|
|
754
759
|
let tools;
|
|
755
|
-
if (
|
|
760
|
+
if (isInteropZodSchema(schema)) {
|
|
756
761
|
const jsonSchema = schemaToGenerativeAIParameters(schema);
|
|
757
762
|
tools = [
|
|
758
763
|
{
|
|
@@ -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/output_parsers.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GoogleGenerativeAIToolsOutputParser = void 0;
|
|
4
4
|
const output_parsers_1 = require("@langchain/core/output_parsers");
|
|
5
|
+
const types_1 = require("@langchain/core/utils/types");
|
|
5
6
|
class GoogleGenerativeAIToolsOutputParser extends output_parsers_1.BaseLLMOutputParser {
|
|
6
7
|
static lc_name() {
|
|
7
8
|
return "GoogleGenerativeAIToolsOutputParser";
|
|
@@ -48,12 +49,12 @@ class GoogleGenerativeAIToolsOutputParser extends output_parsers_1.BaseLLMOutput
|
|
|
48
49
|
if (this.zodSchema === undefined) {
|
|
49
50
|
return result;
|
|
50
51
|
}
|
|
51
|
-
const zodParsedResult = await this.zodSchema
|
|
52
|
+
const zodParsedResult = await (0, types_1.interopSafeParseAsync)(this.zodSchema, result);
|
|
52
53
|
if (zodParsedResult.success) {
|
|
53
54
|
return zodParsedResult.data;
|
|
54
55
|
}
|
|
55
56
|
else {
|
|
56
|
-
throw new output_parsers_1.OutputParserException(`Failed to parse. Text: "${JSON.stringify(result, null, 2)}". Error: ${JSON.stringify(zodParsedResult.error.
|
|
57
|
+
throw new output_parsers_1.OutputParserException(`Failed to parse. Text: "${JSON.stringify(result, null, 2)}". Error: ${JSON.stringify(zodParsedResult.error.issues)}`, JSON.stringify(result, null, 2));
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
async parseResult(generations) {
|
package/dist/output_parsers.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
1
|
import { BaseLLMOutputParser } from "@langchain/core/output_parsers";
|
|
3
|
-
import { JsonOutputKeyToolsParserParams } from "@langchain/core/output_parsers/openai_tools";
|
|
4
2
|
import { ChatGeneration } from "@langchain/core/outputs";
|
|
5
|
-
|
|
3
|
+
import { InteropZodType } from "@langchain/core/utils/types";
|
|
4
|
+
import { JsonOutputKeyToolsParserParamsInterop } from "@langchain/core/output_parsers/openai_tools";
|
|
5
|
+
interface GoogleGenerativeAIToolsOutputParserParams<T extends Record<string, any>> extends JsonOutputKeyToolsParserParamsInterop<T> {
|
|
6
6
|
}
|
|
7
7
|
export declare class GoogleGenerativeAIToolsOutputParser<T extends Record<string, any> = Record<string, any>> extends BaseLLMOutputParser<T> {
|
|
8
8
|
static lc_name(): string;
|
|
@@ -12,7 +12,7 @@ export declare class GoogleGenerativeAIToolsOutputParser<T extends Record<string
|
|
|
12
12
|
keyName: string;
|
|
13
13
|
/** Whether to return only the first tool call. */
|
|
14
14
|
returnSingle: boolean;
|
|
15
|
-
zodSchema?:
|
|
15
|
+
zodSchema?: InteropZodType<T>;
|
|
16
16
|
constructor(params: GoogleGenerativeAIToolsOutputParserParams<T>);
|
|
17
17
|
protected _validateResult(result: unknown): Promise<T>;
|
|
18
18
|
parseResult(generations: ChatGeneration[]): Promise<T>;
|
package/dist/output_parsers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseLLMOutputParser, OutputParserException, } from "@langchain/core/output_parsers";
|
|
2
|
+
import { interopSafeParseAsync, } from "@langchain/core/utils/types";
|
|
2
3
|
export class GoogleGenerativeAIToolsOutputParser extends BaseLLMOutputParser {
|
|
3
4
|
static lc_name() {
|
|
4
5
|
return "GoogleGenerativeAIToolsOutputParser";
|
|
@@ -45,12 +46,12 @@ export class GoogleGenerativeAIToolsOutputParser extends BaseLLMOutputParser {
|
|
|
45
46
|
if (this.zodSchema === undefined) {
|
|
46
47
|
return result;
|
|
47
48
|
}
|
|
48
|
-
const zodParsedResult = await this.zodSchema
|
|
49
|
+
const zodParsedResult = await interopSafeParseAsync(this.zodSchema, result);
|
|
49
50
|
if (zodParsedResult.success) {
|
|
50
51
|
return zodParsedResult.data;
|
|
51
52
|
}
|
|
52
53
|
else {
|
|
53
|
-
throw new OutputParserException(`Failed to parse. Text: "${JSON.stringify(result, null, 2)}". Error: ${JSON.stringify(zodParsedResult.error.
|
|
54
|
+
throw new OutputParserException(`Failed to parse. Text: "${JSON.stringify(result, null, 2)}". Error: ${JSON.stringify(zodParsedResult.error.issues)}`, JSON.stringify(result, null, 2));
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
async parseResult(generations) {
|
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) => {
|
|
@@ -251,10 +390,13 @@ function mapGenerateContentResultToChatResult(response, extra) {
|
|
|
251
390
|
const [candidate] = response.candidates;
|
|
252
391
|
const { content: candidateContent, ...generationInfo } = candidate;
|
|
253
392
|
let content;
|
|
254
|
-
if (candidateContent?.parts
|
|
393
|
+
if (Array.isArray(candidateContent?.parts) &&
|
|
394
|
+
candidateContent.parts.length === 1 &&
|
|
395
|
+
candidateContent.parts[0].text) {
|
|
255
396
|
content = candidateContent.parts[0].text;
|
|
256
397
|
}
|
|
257
|
-
else
|
|
398
|
+
else if (Array.isArray(candidateContent?.parts) &&
|
|
399
|
+
candidateContent.parts.length > 0) {
|
|
258
400
|
content = candidateContent.parts.map((p) => {
|
|
259
401
|
if ("text" in p) {
|
|
260
402
|
return {
|
|
@@ -277,17 +419,22 @@ function mapGenerateContentResultToChatResult(response, extra) {
|
|
|
277
419
|
return p;
|
|
278
420
|
});
|
|
279
421
|
}
|
|
422
|
+
else {
|
|
423
|
+
// no content returned - likely due to abnormal stop reason, e.g. malformed function call
|
|
424
|
+
content = [];
|
|
425
|
+
}
|
|
280
426
|
let text = "";
|
|
281
427
|
if (typeof content === "string") {
|
|
282
428
|
text = content;
|
|
283
429
|
}
|
|
284
|
-
else if (
|
|
285
|
-
|
|
430
|
+
else if (Array.isArray(content) && content.length > 0) {
|
|
431
|
+
const block = content.find((b) => "text" in b);
|
|
432
|
+
text = block?.text ?? text;
|
|
286
433
|
}
|
|
287
434
|
const generation = {
|
|
288
435
|
text,
|
|
289
436
|
message: new messages_1.AIMessage({
|
|
290
|
-
content,
|
|
437
|
+
content: content ?? "",
|
|
291
438
|
tool_calls: functionCalls?.map((fc) => {
|
|
292
439
|
return {
|
|
293
440
|
...fc,
|
|
@@ -323,11 +470,11 @@ function convertResponseContentToChatGenerationChunk(response, extra) {
|
|
|
323
470
|
const { content: candidateContent, ...generationInfo } = candidate;
|
|
324
471
|
let content;
|
|
325
472
|
// Checks if some parts do not have text. If false, it means that the content is a string.
|
|
326
|
-
if (candidateContent?.parts &&
|
|
473
|
+
if (Array.isArray(candidateContent?.parts) &&
|
|
327
474
|
candidateContent.parts.every((p) => "text" in p)) {
|
|
328
475
|
content = candidateContent.parts.map((p) => p.text).join("");
|
|
329
476
|
}
|
|
330
|
-
else if (candidateContent
|
|
477
|
+
else if (Array.isArray(candidateContent?.parts)) {
|
|
331
478
|
content = candidateContent.parts.map((p) => {
|
|
332
479
|
if ("text" in p) {
|
|
333
480
|
return {
|
|
@@ -350,12 +497,17 @@ function convertResponseContentToChatGenerationChunk(response, extra) {
|
|
|
350
497
|
return p;
|
|
351
498
|
});
|
|
352
499
|
}
|
|
500
|
+
else {
|
|
501
|
+
// no content returned - likely due to abnormal stop reason, e.g. malformed function call
|
|
502
|
+
content = [];
|
|
503
|
+
}
|
|
353
504
|
let text = "";
|
|
354
505
|
if (content && typeof content === "string") {
|
|
355
506
|
text = content;
|
|
356
507
|
}
|
|
357
|
-
else if (content
|
|
358
|
-
|
|
508
|
+
else if (Array.isArray(content)) {
|
|
509
|
+
const block = content.find((b) => "text" in b);
|
|
510
|
+
text = block?.text ?? "";
|
|
359
511
|
}
|
|
360
512
|
const toolCallChunks = [];
|
|
361
513
|
if (functionCalls) {
|
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) => {
|
|
@@ -244,10 +383,13 @@ export function mapGenerateContentResultToChatResult(response, extra) {
|
|
|
244
383
|
const [candidate] = response.candidates;
|
|
245
384
|
const { content: candidateContent, ...generationInfo } = candidate;
|
|
246
385
|
let content;
|
|
247
|
-
if (candidateContent?.parts
|
|
386
|
+
if (Array.isArray(candidateContent?.parts) &&
|
|
387
|
+
candidateContent.parts.length === 1 &&
|
|
388
|
+
candidateContent.parts[0].text) {
|
|
248
389
|
content = candidateContent.parts[0].text;
|
|
249
390
|
}
|
|
250
|
-
else
|
|
391
|
+
else if (Array.isArray(candidateContent?.parts) &&
|
|
392
|
+
candidateContent.parts.length > 0) {
|
|
251
393
|
content = candidateContent.parts.map((p) => {
|
|
252
394
|
if ("text" in p) {
|
|
253
395
|
return {
|
|
@@ -270,17 +412,22 @@ export function mapGenerateContentResultToChatResult(response, extra) {
|
|
|
270
412
|
return p;
|
|
271
413
|
});
|
|
272
414
|
}
|
|
415
|
+
else {
|
|
416
|
+
// no content returned - likely due to abnormal stop reason, e.g. malformed function call
|
|
417
|
+
content = [];
|
|
418
|
+
}
|
|
273
419
|
let text = "";
|
|
274
420
|
if (typeof content === "string") {
|
|
275
421
|
text = content;
|
|
276
422
|
}
|
|
277
|
-
else if (
|
|
278
|
-
|
|
423
|
+
else if (Array.isArray(content) && content.length > 0) {
|
|
424
|
+
const block = content.find((b) => "text" in b);
|
|
425
|
+
text = block?.text ?? text;
|
|
279
426
|
}
|
|
280
427
|
const generation = {
|
|
281
428
|
text,
|
|
282
429
|
message: new AIMessage({
|
|
283
|
-
content,
|
|
430
|
+
content: content ?? "",
|
|
284
431
|
tool_calls: functionCalls?.map((fc) => {
|
|
285
432
|
return {
|
|
286
433
|
...fc,
|
|
@@ -315,11 +462,11 @@ export function convertResponseContentToChatGenerationChunk(response, extra) {
|
|
|
315
462
|
const { content: candidateContent, ...generationInfo } = candidate;
|
|
316
463
|
let content;
|
|
317
464
|
// Checks if some parts do not have text. If false, it means that the content is a string.
|
|
318
|
-
if (candidateContent?.parts &&
|
|
465
|
+
if (Array.isArray(candidateContent?.parts) &&
|
|
319
466
|
candidateContent.parts.every((p) => "text" in p)) {
|
|
320
467
|
content = candidateContent.parts.map((p) => p.text).join("");
|
|
321
468
|
}
|
|
322
|
-
else if (candidateContent
|
|
469
|
+
else if (Array.isArray(candidateContent?.parts)) {
|
|
323
470
|
content = candidateContent.parts.map((p) => {
|
|
324
471
|
if ("text" in p) {
|
|
325
472
|
return {
|
|
@@ -342,12 +489,17 @@ export function convertResponseContentToChatGenerationChunk(response, extra) {
|
|
|
342
489
|
return p;
|
|
343
490
|
});
|
|
344
491
|
}
|
|
492
|
+
else {
|
|
493
|
+
// no content returned - likely due to abnormal stop reason, e.g. malformed function call
|
|
494
|
+
content = [];
|
|
495
|
+
}
|
|
345
496
|
let text = "";
|
|
346
497
|
if (content && typeof content === "string") {
|
|
347
498
|
text = content;
|
|
348
499
|
}
|
|
349
|
-
else if (content
|
|
350
|
-
|
|
500
|
+
else if (Array.isArray(content)) {
|
|
501
|
+
const block = content.find((b) => "text" in b);
|
|
502
|
+
text = block?.text ?? "";
|
|
351
503
|
}
|
|
352
504
|
const toolCallChunks = [];
|
|
353
505
|
if (functionCalls) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.jsonSchemaToGeminiParameters = exports.schemaToGenerativeAIParameters = exports.removeAdditionalProperties = void 0;
|
|
5
5
|
const types_1 = require("@langchain/core/utils/types");
|
|
6
|
-
const
|
|
6
|
+
const json_schema_1 = require("@langchain/core/utils/json_schema");
|
|
7
7
|
function removeAdditionalProperties(
|
|
8
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
9
|
obj) {
|
|
@@ -36,7 +36,7 @@ exports.removeAdditionalProperties = removeAdditionalProperties;
|
|
|
36
36
|
function schemaToGenerativeAIParameters(schema) {
|
|
37
37
|
// GenerativeAI doesn't accept either the $schema or additionalProperties
|
|
38
38
|
// attributes, so we need to explicitly remove them.
|
|
39
|
-
const jsonSchema = removeAdditionalProperties((0, types_1.
|
|
39
|
+
const jsonSchema = removeAdditionalProperties((0, types_1.isInteropZodSchema)(schema) ? (0, json_schema_1.toJsonSchema)(schema) : schema);
|
|
40
40
|
const { $schema, ...rest } = jsonSchema;
|
|
41
41
|
return rest;
|
|
42
42
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
1
|
import { type FunctionDeclarationSchema as GenerativeAIFunctionDeclarationSchema, type SchemaType as FunctionDeclarationSchemaType } from "@google/generative-ai";
|
|
3
|
-
import {
|
|
2
|
+
import { InteropZodType } from "@langchain/core/utils/types";
|
|
3
|
+
import { type JsonSchema7Type } from "@langchain/core/utils/json_schema";
|
|
4
4
|
export interface GenerativeAIJsonSchema extends Record<string, unknown> {
|
|
5
5
|
properties?: Record<string, GenerativeAIJsonSchema>;
|
|
6
6
|
type: FunctionDeclarationSchemaType;
|
|
@@ -10,5 +10,5 @@ export interface GenerativeAIJsonSchemaDirty extends GenerativeAIJsonSchema {
|
|
|
10
10
|
additionalProperties?: boolean;
|
|
11
11
|
}
|
|
12
12
|
export declare function removeAdditionalProperties(obj: Record<string, any>): GenerativeAIJsonSchema;
|
|
13
|
-
export declare function schemaToGenerativeAIParameters<RunOutput extends Record<string, any> = Record<string, any>>(schema:
|
|
13
|
+
export declare function schemaToGenerativeAIParameters<RunOutput extends Record<string, any> = Record<string, any>>(schema: InteropZodType<RunOutput> | JsonSchema7Type): GenerativeAIFunctionDeclarationSchema;
|
|
14
14
|
export declare function jsonSchemaToGeminiParameters(schema: Record<string, any>): GenerativeAIFunctionDeclarationSchema;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { isInteropZodSchema, } from "@langchain/core/utils/types";
|
|
3
|
+
import { toJsonSchema, } from "@langchain/core/utils/json_schema";
|
|
4
4
|
export function removeAdditionalProperties(
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
6
|
obj) {
|
|
@@ -32,7 +32,7 @@ obj) {
|
|
|
32
32
|
export function schemaToGenerativeAIParameters(schema) {
|
|
33
33
|
// GenerativeAI doesn't accept either the $schema or additionalProperties
|
|
34
34
|
// attributes, so we need to explicitly remove them.
|
|
35
|
-
const jsonSchema = removeAdditionalProperties(
|
|
35
|
+
const jsonSchema = removeAdditionalProperties(isInteropZodSchema(schema) ? toJsonSchema(schema) : schema);
|
|
36
36
|
const { $schema, ...rest } = jsonSchema;
|
|
37
37
|
return rest;
|
|
38
38
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/google-genai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Google Generative AI integration for LangChain.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -36,15 +36,14 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@google/generative-ai": "^0.24.0",
|
|
39
|
-
"uuid": "^11.1.0"
|
|
40
|
-
"zod-to-json-schema": "^3.22.4"
|
|
39
|
+
"uuid": "^11.1.0"
|
|
41
40
|
},
|
|
42
41
|
"peerDependencies": {
|
|
43
|
-
"@langchain/core": ">=0.3.
|
|
42
|
+
"@langchain/core": ">=0.3.58 <0.4.0"
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
45
|
"@jest/globals": "^29.5.0",
|
|
47
|
-
"@langchain/core": "
|
|
46
|
+
"@langchain/core": "workspace:*",
|
|
48
47
|
"@langchain/scripts": ">=0.1.0 <0.2.0",
|
|
49
48
|
"@langchain/standard-tests": "0.0.0",
|
|
50
49
|
"@swc/core": "^1.3.90",
|
|
@@ -68,7 +67,7 @@
|
|
|
68
67
|
"rollup": "^4.5.2",
|
|
69
68
|
"ts-jest": "^29.1.0",
|
|
70
69
|
"typescript": "<5.2.0",
|
|
71
|
-
"zod": "^3.
|
|
70
|
+
"zod": "^3.25.32"
|
|
72
71
|
},
|
|
73
72
|
"publishConfig": {
|
|
74
73
|
"access": "public"
|
|
@@ -92,4 +91,4 @@
|
|
|
92
91
|
"index.d.ts",
|
|
93
92
|
"index.d.cts"
|
|
94
93
|
]
|
|
95
|
-
}
|
|
94
|
+
}
|