@aigne/gemini 0.14.16 → 1.74.0-beta
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 +11 -11
- package/dist/gemini-chat-model.cjs +435 -0
- package/dist/gemini-chat-model.d.cts +123 -0
- package/dist/gemini-chat-model.d.cts.map +1 -0
- package/dist/gemini-chat-model.d.mts +123 -0
- package/dist/gemini-chat-model.d.mts.map +1 -0
- package/dist/gemini-chat-model.mjs +436 -0
- package/dist/gemini-chat-model.mjs.map +1 -0
- package/dist/gemini-image-model.cjs +169 -0
- package/dist/gemini-image-model.d.cts +37 -0
- package/dist/gemini-image-model.d.cts.map +1 -0
- package/dist/gemini-image-model.d.mts +37 -0
- package/dist/gemini-image-model.d.mts.map +1 -0
- package/dist/gemini-image-model.mjs +170 -0
- package/dist/gemini-image-model.mjs.map +1 -0
- package/dist/gemini-video-model.cjs +148 -0
- package/dist/gemini-video-model.d.cts +117 -0
- package/dist/gemini-video-model.d.cts.map +1 -0
- package/dist/gemini-video-model.d.mts +117 -0
- package/dist/gemini-video-model.d.mts.map +1 -0
- package/dist/gemini-video-model.mjs +149 -0
- package/dist/gemini-video-model.mjs.map +1 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +5 -0
- package/dist/utils.cjs +34 -0
- package/dist/utils.mjs +35 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +29 -30
- package/CHANGELOG.md +0 -2672
- package/lib/cjs/gemini-chat-model.d.ts +0 -117
- package/lib/cjs/gemini-chat-model.js +0 -564
- package/lib/cjs/gemini-image-model.d.ts +0 -34
- package/lib/cjs/gemini-image-model.js +0 -171
- package/lib/cjs/gemini-video-model.d.ts +0 -114
- package/lib/cjs/gemini-video-model.js +0 -164
- package/lib/cjs/index.d.ts +0 -3
- package/lib/cjs/index.js +0 -19
- package/lib/cjs/package.json +0 -3
- package/lib/cjs/utils.d.ts +0 -15
- package/lib/cjs/utils.js +0 -37
- package/lib/dts/gemini-chat-model.d.ts +0 -117
- package/lib/dts/gemini-image-model.d.ts +0 -34
- package/lib/dts/gemini-video-model.d.ts +0 -114
- package/lib/dts/index.d.ts +0 -3
- package/lib/dts/utils.d.ts +0 -15
- package/lib/esm/gemini-chat-model.d.ts +0 -117
- package/lib/esm/gemini-chat-model.js +0 -560
- package/lib/esm/gemini-image-model.d.ts +0 -34
- package/lib/esm/gemini-image-model.js +0 -167
- package/lib/esm/gemini-video-model.d.ts +0 -114
- package/lib/esm/gemini-video-model.js +0 -160
- package/lib/esm/index.d.ts +0 -3
- package/lib/esm/index.js +0 -3
- package/lib/esm/package.json +0 -3
- package/lib/esm/utils.d.ts +0 -15
- package/lib/esm/utils.js +0 -34
package/README.md
CHANGED
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<picture>
|
|
5
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
6
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
7
|
-
<img src="https://raw.githubusercontent.com/
|
|
5
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo-dark.svg" media="(prefers-color-scheme: dark)">
|
|
6
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo.svg" media="(prefers-color-scheme: light)">
|
|
7
|
+
<img src="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/logo.svg" alt="AIGNE Logo" width="400" />
|
|
8
8
|
</picture>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
[](https://star-history.com/#ArcBlock/aigne-framework)
|
|
12
|
+
[](https://github.com/ArcBlock/aigne-framework/issues)
|
|
13
|
+
[](https://codecov.io/gh/ArcBlock/aigne-framework)
|
|
14
14
|
[](https://www.npmjs.com/package/@aigne/gemini)
|
|
15
|
-
[](https://github.com/
|
|
15
|
+
[](https://github.com/ArcBlock/aigne-framework/blob/main/LICENSE.md)
|
|
16
16
|
|
|
17
|
-
AIGNE Gemini SDK for integrating with Google's Gemini AI models within the [AIGNE Framework](https://github.com/
|
|
17
|
+
AIGNE Gemini SDK for integrating with Google's Gemini AI models within the [AIGNE Framework](https://github.com/ArcBlock/aigne-framework).
|
|
18
18
|
|
|
19
19
|
## Introduction
|
|
20
20
|
|
|
21
21
|
`@aigne/gemini` provides a seamless integration between the AIGNE Framework and Google's Gemini language models and API. This package enables developers to easily leverage Gemini's advanced AI capabilities in their AIGNE applications, providing a consistent interface across the framework while taking advantage of Google's state-of-the-art multimodal models.
|
|
22
22
|
|
|
23
23
|
<picture>
|
|
24
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
25
|
-
<source srcset="https://raw.githubusercontent.com/
|
|
26
|
-
<img src="https://raw.githubusercontent.com/
|
|
24
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-gemini-dark.png" media="(prefers-color-scheme: dark)">
|
|
25
|
+
<source srcset="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-gemini.png" media="(prefers-color-scheme: light)">
|
|
26
|
+
<img src="https://raw.githubusercontent.com/ArcBlock/aigne-framework/main/assets/aigne-gemini.png" alt="AIGNE Arch" />
|
|
27
27
|
</picture>
|
|
28
28
|
|
|
29
29
|
## Features
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
let _aigne_core = require("@aigne/core");
|
|
2
|
+
let _aigne_core_utils_logger = require("@aigne/core/utils/logger");
|
|
3
|
+
let _aigne_core_utils_model_utils = require("@aigne/core/utils/model-utils");
|
|
4
|
+
let _aigne_core_utils_type_utils = require("@aigne/core/utils/type-utils");
|
|
5
|
+
let _aigne_utils_nodejs = require("@aigne/utils/nodejs");
|
|
6
|
+
let _aigne_uuid = require("@aigne/uuid");
|
|
7
|
+
let _google_genai = require("@google/genai");
|
|
8
|
+
let yaml = require("yaml");
|
|
9
|
+
let zod = require("zod");
|
|
10
|
+
let zod_to_json_schema = require("zod-to-json-schema");
|
|
11
|
+
|
|
12
|
+
//#region src/gemini-chat-model.ts
|
|
13
|
+
const GEMINI_DEFAULT_CHAT_MODEL = "gemini-2.0-flash";
|
|
14
|
+
const OUTPUT_FUNCTION_NAME = "output";
|
|
15
|
+
const NEED_UPLOAD_MAX_FILE_SIZE_MB = 20;
|
|
16
|
+
/**
|
|
17
|
+
* Implementation of the ChatModel interface for Google's Gemini API
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* Here's how to create and use a Gemini chat model:
|
|
21
|
+
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* Here's an example with streaming response:
|
|
25
|
+
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
|
|
26
|
+
*/
|
|
27
|
+
var GeminiChatModel = class extends _aigne_core.ChatModel {
|
|
28
|
+
constructor(options) {
|
|
29
|
+
super({
|
|
30
|
+
...options,
|
|
31
|
+
model: options?.model || GEMINI_DEFAULT_CHAT_MODEL
|
|
32
|
+
});
|
|
33
|
+
this.options = options;
|
|
34
|
+
}
|
|
35
|
+
apiKeyEnvName = "GEMINI_API_KEY";
|
|
36
|
+
_googleClient;
|
|
37
|
+
get googleClient() {
|
|
38
|
+
if (this._googleClient) return this._googleClient;
|
|
39
|
+
const { apiKey } = this.credential;
|
|
40
|
+
if (!apiKey) throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
|
|
41
|
+
this._googleClient ??= new _google_genai.GoogleGenAI({
|
|
42
|
+
apiKey,
|
|
43
|
+
...this.options?.clientOptions
|
|
44
|
+
});
|
|
45
|
+
return this._googleClient;
|
|
46
|
+
}
|
|
47
|
+
get credential() {
|
|
48
|
+
return {
|
|
49
|
+
apiKey: this.options?.apiKey || process.env[this.apiKeyEnvName] || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY,
|
|
50
|
+
model: this.options?.model || GEMINI_DEFAULT_CHAT_MODEL
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
get modelOptions() {
|
|
54
|
+
return this.options?.modelOptions;
|
|
55
|
+
}
|
|
56
|
+
async countTokens(input) {
|
|
57
|
+
const { model, ...request } = await this.getParameters(input);
|
|
58
|
+
const contents = [];
|
|
59
|
+
const { systemInstruction, tools } = request.config ?? {};
|
|
60
|
+
if (systemInstruction) contents.push(this.contentUnionToContent(systemInstruction));
|
|
61
|
+
if (tools?.length) contents.push({
|
|
62
|
+
role: "system",
|
|
63
|
+
parts: [{ text: JSON.stringify(tools) }]
|
|
64
|
+
});
|
|
65
|
+
contents.push(...[request.contents].flat().map(this.contentUnionToContent));
|
|
66
|
+
const tokens = (await this.googleClient.models.countTokens({
|
|
67
|
+
model,
|
|
68
|
+
contents
|
|
69
|
+
})).totalTokens;
|
|
70
|
+
if (!(0, _aigne_core_utils_type_utils.isNil)(tokens)) return tokens;
|
|
71
|
+
return super.countTokens(input);
|
|
72
|
+
}
|
|
73
|
+
contentUnionToContent(content) {
|
|
74
|
+
if (typeof content === "object" && "parts" in content) return {
|
|
75
|
+
role: "system",
|
|
76
|
+
parts: content.parts
|
|
77
|
+
};
|
|
78
|
+
else if (typeof content === "string") return {
|
|
79
|
+
role: "system",
|
|
80
|
+
parts: [{ text: content }]
|
|
81
|
+
};
|
|
82
|
+
else if (Array.isArray(content)) return {
|
|
83
|
+
role: "system",
|
|
84
|
+
parts: content.map((i) => typeof i === "string" ? { text: i } : i)
|
|
85
|
+
};
|
|
86
|
+
else return {
|
|
87
|
+
role: "system",
|
|
88
|
+
parts: [content]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
process(input, options) {
|
|
92
|
+
return this.processInput(input, options);
|
|
93
|
+
}
|
|
94
|
+
thinkingBudgetModelMap = [
|
|
95
|
+
{
|
|
96
|
+
pattern: /gemini-2.5-flash-image-preview/,
|
|
97
|
+
support: false
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
pattern: /gemini-3(?!.*-image-)/,
|
|
101
|
+
support: true,
|
|
102
|
+
type: "level"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
pattern: /gemini-2.5-pro/,
|
|
106
|
+
support: true,
|
|
107
|
+
min: 128,
|
|
108
|
+
max: 32768
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
pattern: /gemini-2.5-flash/,
|
|
112
|
+
support: true,
|
|
113
|
+
min: 0,
|
|
114
|
+
max: 24576
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
pattern: /2.5-flash-lite/,
|
|
118
|
+
support: true,
|
|
119
|
+
min: 512,
|
|
120
|
+
max: 24576
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
pattern: /.*/,
|
|
124
|
+
support: false
|
|
125
|
+
}
|
|
126
|
+
];
|
|
127
|
+
thinkingBudgetLevelMap = {
|
|
128
|
+
high: 1e5,
|
|
129
|
+
medium: 1e4,
|
|
130
|
+
low: 5e3,
|
|
131
|
+
minimal: 200
|
|
132
|
+
};
|
|
133
|
+
thinkingLevelMap = {
|
|
134
|
+
high: _google_genai.ThinkingLevel.HIGH,
|
|
135
|
+
medium: _google_genai.ThinkingLevel.HIGH,
|
|
136
|
+
low: _google_genai.ThinkingLevel.LOW,
|
|
137
|
+
minimal: _google_genai.ThinkingLevel.LOW
|
|
138
|
+
};
|
|
139
|
+
getThinkingBudget(model, effort) {
|
|
140
|
+
const m = this.thinkingBudgetModelMap.find((i) => i.pattern.test(model));
|
|
141
|
+
if (!m?.support) return { support: false };
|
|
142
|
+
if (m.type === "level") {
|
|
143
|
+
let level = _google_genai.ThinkingLevel.THINKING_LEVEL_UNSPECIFIED;
|
|
144
|
+
if (typeof effort === "string") level = this.thinkingLevelMap[effort];
|
|
145
|
+
else if (typeof effort === "number") level = effort >= this.thinkingBudgetLevelMap["medium"] ? _google_genai.ThinkingLevel.HIGH : _google_genai.ThinkingLevel.LOW;
|
|
146
|
+
return {
|
|
147
|
+
support: true,
|
|
148
|
+
level
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
let budget = typeof effort === "string" ? this.thinkingBudgetLevelMap[effort] || void 0 : effort;
|
|
152
|
+
if (typeof budget === "undefined") return { support: true };
|
|
153
|
+
if (typeof m.min === "number") budget = Math.max(m.min, budget);
|
|
154
|
+
if (typeof m.max === "number") budget = Math.min(m.max, budget);
|
|
155
|
+
return {
|
|
156
|
+
support: true,
|
|
157
|
+
budget
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async getParameters(input) {
|
|
161
|
+
const { modelOptions = {} } = input;
|
|
162
|
+
const model = modelOptions.model || this.credential.model;
|
|
163
|
+
const { contents, config } = await this.buildContents(input);
|
|
164
|
+
const thinkingBudget = this.getThinkingBudget(model, modelOptions.reasoningEffort);
|
|
165
|
+
return {
|
|
166
|
+
model,
|
|
167
|
+
contents,
|
|
168
|
+
config: {
|
|
169
|
+
thinkingConfig: thinkingBudget.support ? {
|
|
170
|
+
includeThoughts: true,
|
|
171
|
+
thinkingBudget: thinkingBudget.budget,
|
|
172
|
+
thinkingLevel: thinkingBudget.level
|
|
173
|
+
} : void 0,
|
|
174
|
+
responseModalities: modelOptions.modalities,
|
|
175
|
+
temperature: modelOptions.temperature,
|
|
176
|
+
topP: modelOptions.topP,
|
|
177
|
+
frequencyPenalty: modelOptions.frequencyPenalty,
|
|
178
|
+
presencePenalty: modelOptions.presencePenalty,
|
|
179
|
+
...config,
|
|
180
|
+
...await this.buildConfig(input)
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async *processInput(input, options) {
|
|
185
|
+
const parameters = await this.getParameters(input);
|
|
186
|
+
const response = await this.googleClient.models.generateContentStream(parameters);
|
|
187
|
+
let usage = {
|
|
188
|
+
inputTokens: 0,
|
|
189
|
+
outputTokens: 0
|
|
190
|
+
};
|
|
191
|
+
let responseModel;
|
|
192
|
+
const files = [];
|
|
193
|
+
const toolCalls = [];
|
|
194
|
+
let text = "";
|
|
195
|
+
let json;
|
|
196
|
+
for await (const chunk of response) {
|
|
197
|
+
if (!responseModel && chunk.modelVersion) {
|
|
198
|
+
responseModel = chunk.modelVersion;
|
|
199
|
+
yield { delta: { json: { model: responseModel } } };
|
|
200
|
+
}
|
|
201
|
+
for (const { content } of chunk.candidates ?? []) if (content?.parts) for (const part of content.parts) {
|
|
202
|
+
if (part.text) if (part.thought) yield { delta: { text: { thoughts: part.text } } };
|
|
203
|
+
else {
|
|
204
|
+
text += part.text;
|
|
205
|
+
if (input.responseFormat?.type !== "json_schema") yield { delta: { text: { text: part.text } } };
|
|
206
|
+
}
|
|
207
|
+
if (part.inlineData?.data) files.push({
|
|
208
|
+
type: "file",
|
|
209
|
+
data: part.inlineData.data,
|
|
210
|
+
filename: part.inlineData.displayName,
|
|
211
|
+
mimeType: part.inlineData.mimeType
|
|
212
|
+
});
|
|
213
|
+
if (part.functionCall?.name) if (part.functionCall.name === OUTPUT_FUNCTION_NAME) json = part.functionCall.args;
|
|
214
|
+
else {
|
|
215
|
+
const toolCall = {
|
|
216
|
+
id: part.functionCall.id || (0, _aigne_uuid.v7)(),
|
|
217
|
+
type: "function",
|
|
218
|
+
function: {
|
|
219
|
+
name: part.functionCall.name,
|
|
220
|
+
arguments: part.functionCall.args || {}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
if (part.thoughtSignature && parameters.model.includes("gemini-3")) toolCall.metadata = { thoughtSignature: part.thoughtSignature };
|
|
224
|
+
toolCalls.push(toolCall);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (chunk.usageMetadata) {
|
|
228
|
+
if (chunk.usageMetadata.promptTokenCount) usage.inputTokens = chunk.usageMetadata.promptTokenCount;
|
|
229
|
+
if (chunk.usageMetadata.candidatesTokenCount || chunk.usageMetadata.thoughtsTokenCount) usage.outputTokens = (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0);
|
|
230
|
+
if (chunk.usageMetadata.cachedContentTokenCount) usage.cacheReadInputTokens = chunk.usageMetadata.cachedContentTokenCount;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (toolCalls.length) yield { delta: { json: { toolCalls } } };
|
|
234
|
+
if (input.responseFormat?.type === "json_schema") {
|
|
235
|
+
if (json) yield { delta: { json: { json } } };
|
|
236
|
+
else if (text) yield { delta: { json: { json: (0, _aigne_core.safeParseJSON)(text) } } };
|
|
237
|
+
else if (!toolCalls.length) throw new _aigne_core.StructuredOutputError("No JSON response from the model");
|
|
238
|
+
} else if (!toolCalls.length) {
|
|
239
|
+
if (!text && !files.length) {
|
|
240
|
+
_aigne_core_utils_logger.logger.warn("Empty response from Gemini, retrying with structured output mode");
|
|
241
|
+
try {
|
|
242
|
+
const outputSchema = zod.z.object({ output: zod.z.string().describe("The final answer from the model") });
|
|
243
|
+
const result = await (0, _aigne_core.agentProcessResultToObject)(await this.process({
|
|
244
|
+
...input,
|
|
245
|
+
responseFormat: {
|
|
246
|
+
type: "json_schema",
|
|
247
|
+
jsonSchema: {
|
|
248
|
+
name: "output",
|
|
249
|
+
schema: (0, zod_to_json_schema.zodToJsonSchema)(outputSchema)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}, options));
|
|
253
|
+
usage = (0, _aigne_core_utils_model_utils.mergeUsage)(usage, result.usage);
|
|
254
|
+
if (result.toolCalls?.length) {
|
|
255
|
+
toolCalls.push(...result.toolCalls);
|
|
256
|
+
yield { delta: { json: { toolCalls } } };
|
|
257
|
+
} else {
|
|
258
|
+
if (!result.json) throw new Error("Retrying with structured output mode got no json response");
|
|
259
|
+
const parsed = outputSchema.safeParse(result.json);
|
|
260
|
+
if (!parsed.success) throw new Error("Retrying with structured output mode got invalid json response");
|
|
261
|
+
text = parsed.data.output;
|
|
262
|
+
yield { delta: { text: { text } } };
|
|
263
|
+
_aigne_core_utils_logger.logger.warn("Empty response from Gemini, retried with structured output mode successfully");
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
_aigne_core_utils_logger.logger.error("Empty response from Gemini, retrying with structured output mode failed", error);
|
|
267
|
+
throw new _aigne_core.StructuredOutputError("No response from the model");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
yield { delta: { json: {
|
|
272
|
+
usage,
|
|
273
|
+
files: files.length ? files : void 0,
|
|
274
|
+
modelOptions: { reasoningEffort: parameters.config?.thinkingConfig?.thinkingLevel || parameters.config?.thinkingConfig?.thinkingBudget }
|
|
275
|
+
} } };
|
|
276
|
+
}
|
|
277
|
+
async buildConfig(input) {
|
|
278
|
+
const config = {};
|
|
279
|
+
const { tools, toolConfig } = await this.buildTools(input);
|
|
280
|
+
config.tools = tools;
|
|
281
|
+
config.toolConfig = toolConfig;
|
|
282
|
+
if (input.responseFormat?.type === "json_schema") if (config.tools?.length) {
|
|
283
|
+
config.tools.push({ functionDeclarations: [{
|
|
284
|
+
name: OUTPUT_FUNCTION_NAME,
|
|
285
|
+
description: "Output the final response",
|
|
286
|
+
parametersJsonSchema: input.responseFormat.jsonSchema.schema
|
|
287
|
+
}] });
|
|
288
|
+
config.toolConfig = {
|
|
289
|
+
...config.toolConfig,
|
|
290
|
+
functionCallingConfig: { mode: _google_genai.FunctionCallingConfigMode.ANY }
|
|
291
|
+
};
|
|
292
|
+
} else {
|
|
293
|
+
config.responseJsonSchema = input.responseFormat.jsonSchema.schema;
|
|
294
|
+
config.responseMimeType = "application/json";
|
|
295
|
+
}
|
|
296
|
+
return config;
|
|
297
|
+
}
|
|
298
|
+
async buildTools(input) {
|
|
299
|
+
const tools = [];
|
|
300
|
+
for (const tool of input.tools ?? []) tools.push({ functionDeclarations: [{
|
|
301
|
+
name: tool.function.name,
|
|
302
|
+
description: tool.function.description,
|
|
303
|
+
parametersJsonSchema: tool.function.parameters
|
|
304
|
+
}] });
|
|
305
|
+
return {
|
|
306
|
+
tools,
|
|
307
|
+
toolConfig: { functionCallingConfig: !input.toolChoice ? void 0 : input.toolChoice === "auto" ? { mode: _google_genai.FunctionCallingConfigMode.AUTO } : input.toolChoice === "none" ? { mode: _google_genai.FunctionCallingConfigMode.NONE } : input.toolChoice === "required" ? { mode: _google_genai.FunctionCallingConfigMode.ANY } : {
|
|
308
|
+
mode: _google_genai.FunctionCallingConfigMode.ANY,
|
|
309
|
+
allowedFunctionNames: [input.toolChoice.function.name]
|
|
310
|
+
} }
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async buildVideoContentParts(media) {
|
|
314
|
+
const { path: filePath, mimeType: fileMimeType } = await this.transformFileType("local", media);
|
|
315
|
+
if (filePath) {
|
|
316
|
+
if ((await _aigne_utils_nodejs.nodejs.fs.stat(filePath)).size / (1024 * 1024) > NEED_UPLOAD_MAX_FILE_SIZE_MB) {
|
|
317
|
+
let file = await this.googleClient.files.upload({
|
|
318
|
+
file: filePath,
|
|
319
|
+
config: { mimeType: fileMimeType }
|
|
320
|
+
});
|
|
321
|
+
while (file.state === "PROCESSING") {
|
|
322
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
323
|
+
if (file.name) file = await this.googleClient.files.get({ name: file.name });
|
|
324
|
+
}
|
|
325
|
+
if (file.state !== "ACTIVE") throw new Error(`File ${file.name} failed to process: ${file.state}`);
|
|
326
|
+
if (file.uri && file.mimeType) {
|
|
327
|
+
const part = (0, _google_genai.createUserContent)([(0, _google_genai.createPartFromUri)(file.uri, file.mimeType), ""]).parts?.find((x) => x.fileData);
|
|
328
|
+
if (part) {
|
|
329
|
+
await _aigne_utils_nodejs.nodejs.fs.rm(filePath);
|
|
330
|
+
return part;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async buildContents(input) {
|
|
337
|
+
const result = { contents: [] };
|
|
338
|
+
const systemParts = [];
|
|
339
|
+
result.contents = (await Promise.all(input.messages.map(async (msg) => {
|
|
340
|
+
if (msg.role === "system") {
|
|
341
|
+
if (typeof msg.content === "string") systemParts.push({ text: msg.content });
|
|
342
|
+
else if (Array.isArray(msg.content)) systemParts.push(...msg.content.map((item) => {
|
|
343
|
+
if (item.type === "text") return { text: item.text };
|
|
344
|
+
throw new Error(`Unsupported content type: ${item.type}`);
|
|
345
|
+
}));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const content = { role: msg.role === "agent" ? "model" : msg.role === "user" ? "user" : void 0 };
|
|
349
|
+
if (msg.toolCalls) content.parts = msg.toolCalls.map((call) => {
|
|
350
|
+
const part = { functionCall: {
|
|
351
|
+
id: call.id,
|
|
352
|
+
name: call.function.name,
|
|
353
|
+
args: call.function.arguments
|
|
354
|
+
} };
|
|
355
|
+
if (call.metadata?.thoughtSignature) part.thoughtSignature = call.metadata.thoughtSignature;
|
|
356
|
+
return part;
|
|
357
|
+
});
|
|
358
|
+
else if (msg.toolCallId) {
|
|
359
|
+
const call = input.messages.flatMap((i) => i.toolCalls).find((c) => c?.id === msg.toolCallId);
|
|
360
|
+
if (!call) throw new Error(`Tool call not found: ${msg.toolCallId}`);
|
|
361
|
+
if (!msg.content) throw new Error("Tool call must have content");
|
|
362
|
+
let toolResult;
|
|
363
|
+
{
|
|
364
|
+
let text;
|
|
365
|
+
if (typeof msg.content === "string") text = msg.content;
|
|
366
|
+
else if (msg.content?.length === 1) {
|
|
367
|
+
const first = msg.content[0];
|
|
368
|
+
if (first?.type === "text") text = first.text;
|
|
369
|
+
}
|
|
370
|
+
if (text) {
|
|
371
|
+
try {
|
|
372
|
+
const obj = (0, yaml.parse)(text);
|
|
373
|
+
if ((0, _aigne_core_utils_type_utils.isRecord)(obj)) toolResult = obj;
|
|
374
|
+
} catch {}
|
|
375
|
+
if (!toolResult) toolResult = { result: text };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const functionResponse = {
|
|
379
|
+
id: msg.toolCallId,
|
|
380
|
+
name: call.function.name
|
|
381
|
+
};
|
|
382
|
+
if (toolResult) functionResponse.response = toolResult;
|
|
383
|
+
else functionResponse.parts = await this.contentToParts(msg.content);
|
|
384
|
+
content.parts = [{ functionResponse }];
|
|
385
|
+
} else if (msg.content) content.parts = await this.contentToParts(msg.content);
|
|
386
|
+
return content;
|
|
387
|
+
}))).filter(_aigne_core_utils_type_utils.isNonNullable);
|
|
388
|
+
this.ensureMessagesHasUserMessage(systemParts, result.contents);
|
|
389
|
+
if (systemParts.length) {
|
|
390
|
+
result.config ??= {};
|
|
391
|
+
result.config.systemInstruction = systemParts;
|
|
392
|
+
}
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
async contentToParts(content) {
|
|
396
|
+
if (typeof content === "string") return [{ text: content }];
|
|
397
|
+
return Promise.all(content.map(async (item) => {
|
|
398
|
+
switch (item.type) {
|
|
399
|
+
case "text": return { text: item.text };
|
|
400
|
+
case "url": return { fileData: {
|
|
401
|
+
fileUri: item.url,
|
|
402
|
+
mimeType: item.mimeType
|
|
403
|
+
} };
|
|
404
|
+
case "file": {
|
|
405
|
+
const part = await this.buildVideoContentParts(item);
|
|
406
|
+
if (part) return part;
|
|
407
|
+
return { inlineData: {
|
|
408
|
+
data: item.data,
|
|
409
|
+
mimeType: item.mimeType
|
|
410
|
+
} };
|
|
411
|
+
}
|
|
412
|
+
case "local": throw new Error(`Unsupported local file: ${item.path}, it should be converted to base64 at ChatModel`);
|
|
413
|
+
}
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
ensureMessagesHasUserMessage(systems, contents) {
|
|
417
|
+
if (!contents.length && systems.length) {
|
|
418
|
+
const system = systems.pop();
|
|
419
|
+
if (system) contents.push({
|
|
420
|
+
role: "user",
|
|
421
|
+
parts: [system]
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (contents[0]?.role === "model") {
|
|
425
|
+
const system = systems.pop();
|
|
426
|
+
if (system) contents.unshift({
|
|
427
|
+
role: "user",
|
|
428
|
+
parts: [system]
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
//#endregion
|
|
435
|
+
exports.GeminiChatModel = GeminiChatModel;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as _aigne_core0 from "@aigne/core";
|
|
2
|
+
import { AgentInvokeOptions, AgentProcessResult, ChatModel, ChatModelInput, ChatModelInputOptions, ChatModelOptions, ChatModelOutput } from "@aigne/core";
|
|
3
|
+
import { PromiseOrValue } from "@aigne/core/utils/type-utils";
|
|
4
|
+
import { GoogleGenAI, GoogleGenAIOptions, ThinkingLevel } from "@google/genai";
|
|
5
|
+
|
|
6
|
+
//#region src/gemini-chat-model.d.ts
|
|
7
|
+
interface GeminiChatModelOptions extends ChatModelOptions {
|
|
8
|
+
/**
|
|
9
|
+
* API key for Gemini API
|
|
10
|
+
*
|
|
11
|
+
* If not provided, will look for GEMINI_API_KEY or GOOGLE_API_KEY in environment variables
|
|
12
|
+
*/
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional client options for the Gemini SDK
|
|
16
|
+
*/
|
|
17
|
+
clientOptions?: Partial<GoogleGenAIOptions>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Implementation of the ChatModel interface for Google's Gemini API
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* Here's how to create and use a Gemini chat model:
|
|
24
|
+
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* Here's an example with streaming response:
|
|
28
|
+
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
|
|
29
|
+
*/
|
|
30
|
+
declare class GeminiChatModel extends ChatModel {
|
|
31
|
+
options?: GeminiChatModelOptions | undefined;
|
|
32
|
+
constructor(options?: GeminiChatModelOptions | undefined);
|
|
33
|
+
protected apiKeyEnvName: string;
|
|
34
|
+
protected _googleClient?: GoogleGenAI;
|
|
35
|
+
get googleClient(): GoogleGenAI;
|
|
36
|
+
get credential(): {
|
|
37
|
+
apiKey: string | undefined;
|
|
38
|
+
model: string;
|
|
39
|
+
};
|
|
40
|
+
get modelOptions(): Partial<{
|
|
41
|
+
[x: string]: unknown;
|
|
42
|
+
model?: string | {
|
|
43
|
+
$get: string;
|
|
44
|
+
} | undefined;
|
|
45
|
+
temperature?: number | {
|
|
46
|
+
$get: string;
|
|
47
|
+
} | undefined;
|
|
48
|
+
topP?: number | {
|
|
49
|
+
$get: string;
|
|
50
|
+
} | undefined;
|
|
51
|
+
frequencyPenalty?: number | {
|
|
52
|
+
$get: string;
|
|
53
|
+
} | undefined;
|
|
54
|
+
presencePenalty?: number | {
|
|
55
|
+
$get: string;
|
|
56
|
+
} | undefined;
|
|
57
|
+
parallelToolCalls?: boolean | {
|
|
58
|
+
$get: string;
|
|
59
|
+
} | undefined;
|
|
60
|
+
modalities?: {
|
|
61
|
+
$get: string;
|
|
62
|
+
} | _aigne_core0.Modality[] | undefined;
|
|
63
|
+
preferInputFileType?: "url" | {
|
|
64
|
+
$get: string;
|
|
65
|
+
} | "file" | undefined;
|
|
66
|
+
reasoningEffort?: number | {
|
|
67
|
+
$get: string;
|
|
68
|
+
} | "minimal" | "low" | "medium" | "high" | undefined;
|
|
69
|
+
cacheConfig?: {
|
|
70
|
+
$get: string;
|
|
71
|
+
} | _aigne_core0.CacheConfig | undefined;
|
|
72
|
+
}> | undefined;
|
|
73
|
+
countTokens(input: ChatModelInput): Promise<number>;
|
|
74
|
+
private contentUnionToContent;
|
|
75
|
+
process(input: ChatModelInput, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
76
|
+
protected thinkingBudgetModelMap: ({
|
|
77
|
+
pattern: RegExp;
|
|
78
|
+
support: boolean;
|
|
79
|
+
type?: undefined;
|
|
80
|
+
min?: undefined;
|
|
81
|
+
max?: undefined;
|
|
82
|
+
} | {
|
|
83
|
+
pattern: RegExp;
|
|
84
|
+
support: boolean;
|
|
85
|
+
type: string;
|
|
86
|
+
min?: undefined;
|
|
87
|
+
max?: undefined;
|
|
88
|
+
} | {
|
|
89
|
+
pattern: RegExp;
|
|
90
|
+
support: boolean;
|
|
91
|
+
min: number;
|
|
92
|
+
max: number;
|
|
93
|
+
type?: undefined;
|
|
94
|
+
})[];
|
|
95
|
+
protected thinkingBudgetLevelMap: {
|
|
96
|
+
high: number;
|
|
97
|
+
medium: number;
|
|
98
|
+
low: number;
|
|
99
|
+
minimal: number;
|
|
100
|
+
};
|
|
101
|
+
protected thinkingLevelMap: {
|
|
102
|
+
high: ThinkingLevel;
|
|
103
|
+
medium: ThinkingLevel;
|
|
104
|
+
low: ThinkingLevel;
|
|
105
|
+
minimal: ThinkingLevel;
|
|
106
|
+
};
|
|
107
|
+
protected getThinkingBudget(model: string, effort: ChatModelInputOptions["reasoningEffort"]): {
|
|
108
|
+
support: boolean;
|
|
109
|
+
budget?: number;
|
|
110
|
+
level?: ThinkingLevel;
|
|
111
|
+
};
|
|
112
|
+
private getParameters;
|
|
113
|
+
private processInput;
|
|
114
|
+
private buildConfig;
|
|
115
|
+
private buildTools;
|
|
116
|
+
private buildVideoContentParts;
|
|
117
|
+
private buildContents;
|
|
118
|
+
private contentToParts;
|
|
119
|
+
private ensureMessagesHasUserMessage;
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { GeminiChatModel, GeminiChatModelOptions };
|
|
123
|
+
//# sourceMappingURL=gemini-chat-model.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-chat-model.d.cts","names":[],"sources":["../src/gemini-chat-model.ts"],"mappings":";;;;;;UA+CiB,sBAAA,SAA+B,gBAAA;EAAA;;AAyBhD;;;EAzBgD,MAAA;EAAA;;AAyBhD;EAzBgD,aAAA,GAW9B,OAAA,CAAQ,kBAAA;AAAA;AAAA;;;;AAc1B;;;;;;;AAd0B,cAcb,eAAA,SAAwB,SAAA;EAAA,OAAA,GACG,sBAAA;EAAA,YAAA,OAAA,GAAA,sBAAA;EAAA,UAAA,aAAA;EAAA,UAAA,aAAA,GASZ,WAAA;EAAA,IAAA,aAAA,GAEV,WAAA;EAAA,IAAA,WAAA;IAAA,MAAA;IAAA,KAAA;EAAA;EAAA,IAAA,aAAA,GA+BA,OAAA;IAAA,CAAA,CAAA;IAAA,KAAA;MAAA,IAAA;IAAA;IAAA,WAAA;MAAA,IAAA;IAAA;IAAA,IAAA;MAAA,IAAA;IAAA;IAAA,gBAAA;MAAA,IAAA;IAAA;IAAA,eAAA;MAAA,IAAA;IAAA;IAAA,iBAAA;MAAA,IAAA;IAAA;IAAA,UAAA;MAAA,IAAA;IAAA,IA/BA,YAAA,CAAA,QAAA;IAAA,mBAAA;MAAA,IAAA;IAAA;IAAA,eAAA;MAAA,IAAA;IAAA;IAAA,WAAA;MAAA,IAAA;IAAA;;qBAmCkB,cAAA,GAAiB,OAAA;EAAA,QAAA,qBAAA;EAAA,QAAA,KAAA,EAwC1C,cAAA,EAAA,OAAA,EACE,kBAAA,GACR,cAAA,CAAe,kBAAA,CAAmB,eAAA;EAAA,UAAA,sBAAA;IAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qDAwD3B,qBAAA;IAAA,OAAA;IAAA,MAAA;IAAA,KAAA,GACsC,aAAA;EAAA;EAAA,QAAA,aAAA;EAAA,QAAA,YAAA;EAAA,QAAA,WAAA;EAAA,QAAA,UAAA;EAAA,QAAA,sBAAA;EAAA,QAAA,aAAA;EAAA,QAAA,cAAA;EAAA,QAAA,4BAAA;AAAA"}
|