@aigne/gemini 0.14.2-beta.8 → 0.14.2-beta.9
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/CHANGELOG.md +17 -0
- package/lib/cjs/gemini-chat-model.d.ts +24 -15
- package/lib/cjs/gemini-chat-model.js +108 -48
- package/lib/dts/gemini-chat-model.d.ts +24 -15
- package/lib/esm/gemini-chat-model.d.ts +24 -15
- package/lib/esm/gemini-chat-model.js +109 -49
- package/package.json +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.14.2-beta.9](https://github.com/AIGNE-io/aigne-framework/compare/gemini-v0.14.2-beta.8...gemini-v0.14.2-beta.9) (2025-10-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **models:** auto retry when got emtpy response from gemini ([#636](https://github.com/AIGNE-io/aigne-framework/issues/636)) ([9367cef](https://github.com/AIGNE-io/aigne-framework/commit/9367cef49ea4c0c87b8a36b454deb2efaee6886f))
|
|
9
|
+
* **models:** enhance gemini model tool use with status fields ([#634](https://github.com/AIGNE-io/aigne-framework/issues/634)) ([067b175](https://github.com/AIGNE-io/aigne-framework/commit/067b175c8e31bb5b1a6d0fc5a5cfb2d070d8d709))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Dependencies
|
|
13
|
+
|
|
14
|
+
* The following workspace dependencies were updated
|
|
15
|
+
* dependencies
|
|
16
|
+
* @aigne/core bumped to 1.63.0-beta.9
|
|
17
|
+
* devDependencies
|
|
18
|
+
* @aigne/test-utils bumped to 0.5.55-beta.9
|
|
19
|
+
|
|
3
20
|
## [0.14.2-beta.8](https://github.com/AIGNE-io/aigne-framework/compare/gemini-v0.14.2-beta.7...gemini-v0.14.2-beta.8) (2025-10-16)
|
|
4
21
|
|
|
5
22
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type AgentProcessResult, ChatModel, type ChatModelInput, type ChatModelOptions, type ChatModelOutput } from "@aigne/core";
|
|
2
2
|
import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { GoogleGenAI, type GoogleGenAIOptions } from "@google/genai";
|
|
4
|
+
export interface GeminiChatModelOptions extends ChatModelOptions {
|
|
5
|
+
/**
|
|
6
|
+
* API key for Gemini API
|
|
7
|
+
*
|
|
8
|
+
* If not provided, will look for GEMINI_API_KEY or GOOGLE_API_KEY in environment variables
|
|
9
|
+
*/
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional client options for the Gemini SDK
|
|
13
|
+
*/
|
|
14
|
+
clientOptions?: Partial<GoogleGenAIOptions>;
|
|
15
|
+
}
|
|
5
16
|
/**
|
|
6
17
|
* Implementation of the ChatModel interface for Google's Gemini API
|
|
7
18
|
*
|
|
8
|
-
* This model uses OpenAI-compatible API format to interact with Google's Gemini models,
|
|
9
|
-
* providing access to models like Gemini 1.5 and Gemini 2.0.
|
|
10
|
-
*
|
|
11
19
|
* @example
|
|
12
20
|
* Here's how to create and use a Gemini chat model:
|
|
13
21
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
|
|
@@ -16,19 +24,20 @@ import { GoogleGenAI } from "@google/genai";
|
|
|
16
24
|
* Here's an example with streaming response:
|
|
17
25
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
|
|
18
26
|
*/
|
|
19
|
-
export declare class GeminiChatModel extends
|
|
20
|
-
|
|
27
|
+
export declare class GeminiChatModel extends ChatModel {
|
|
28
|
+
options?: GeminiChatModelOptions | undefined;
|
|
29
|
+
constructor(options?: GeminiChatModelOptions | undefined);
|
|
21
30
|
protected apiKeyEnvName: string;
|
|
22
|
-
protected supportsToolsUseWithJsonSchema: boolean;
|
|
23
|
-
protected supportsParallelToolCalls: boolean;
|
|
24
|
-
protected supportsToolStreaming: boolean;
|
|
25
|
-
protected optionalFieldMode: "optional";
|
|
26
31
|
protected _googleClient?: GoogleGenAI;
|
|
27
32
|
get googleClient(): GoogleGenAI;
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
get credential(): {
|
|
34
|
+
apiKey: string | undefined;
|
|
35
|
+
model: string;
|
|
36
|
+
};
|
|
37
|
+
get modelOptions(): Omit<import("@aigne/core").ChatModelInputOptions, "model"> | undefined;
|
|
38
|
+
process(input: ChatModelInput): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
39
|
+
private processInput;
|
|
30
40
|
private buildConfig;
|
|
31
41
|
private buildTools;
|
|
32
42
|
private buildContents;
|
|
33
|
-
getRunMessages(input: ChatModelInput): ReturnType<OpenAIChatModel["getRunMessages"]>;
|
|
34
43
|
}
|
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GeminiChatModel = void 0;
|
|
4
4
|
const core_1 = require("@aigne/core");
|
|
5
|
+
const logger_js_1 = require("@aigne/core/utils/logger.js");
|
|
5
6
|
const type_utils_js_1 = require("@aigne/core/utils/type-utils.js");
|
|
6
|
-
const openai_1 = require("@aigne/openai");
|
|
7
7
|
const uuid_1 = require("@aigne/uuid");
|
|
8
8
|
const genai_1 = require("@google/genai");
|
|
9
|
-
const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai";
|
|
10
9
|
const GEMINI_DEFAULT_CHAT_MODEL = "gemini-2.0-flash";
|
|
10
|
+
const OUTPUT_JSON_FUNCTION_NAME = "output_json";
|
|
11
11
|
/**
|
|
12
12
|
* Implementation of the ChatModel interface for Google's Gemini API
|
|
13
13
|
*
|
|
14
|
-
* This model uses OpenAI-compatible API format to interact with Google's Gemini models,
|
|
15
|
-
* providing access to models like Gemini 1.5 and Gemini 2.0.
|
|
16
|
-
*
|
|
17
14
|
* @example
|
|
18
15
|
* Here's how to create and use a Gemini chat model:
|
|
19
16
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
|
|
@@ -22,19 +19,16 @@ const GEMINI_DEFAULT_CHAT_MODEL = "gemini-2.0-flash";
|
|
|
22
19
|
* Here's an example with streaming response:
|
|
23
20
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
|
|
24
21
|
*/
|
|
25
|
-
class GeminiChatModel extends
|
|
22
|
+
class GeminiChatModel extends core_1.ChatModel {
|
|
23
|
+
options;
|
|
26
24
|
constructor(options) {
|
|
27
25
|
super({
|
|
28
26
|
...options,
|
|
29
27
|
model: options?.model || GEMINI_DEFAULT_CHAT_MODEL,
|
|
30
|
-
baseURL: options?.baseURL || GEMINI_BASE_URL,
|
|
31
28
|
});
|
|
29
|
+
this.options = options;
|
|
32
30
|
}
|
|
33
31
|
apiKeyEnvName = "GEMINI_API_KEY";
|
|
34
|
-
supportsToolsUseWithJsonSchema = false;
|
|
35
|
-
supportsParallelToolCalls = false;
|
|
36
|
-
supportsToolStreaming = false;
|
|
37
|
-
optionalFieldMode = "optional";
|
|
38
32
|
_googleClient;
|
|
39
33
|
get googleClient() {
|
|
40
34
|
if (this._googleClient)
|
|
@@ -42,20 +36,33 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
42
36
|
const { apiKey } = this.credential;
|
|
43
37
|
if (!apiKey)
|
|
44
38
|
throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
|
|
45
|
-
this._googleClient ??= new genai_1.GoogleGenAI({
|
|
39
|
+
this._googleClient ??= new genai_1.GoogleGenAI({
|
|
40
|
+
apiKey,
|
|
41
|
+
...this.options?.clientOptions,
|
|
42
|
+
});
|
|
46
43
|
return this._googleClient;
|
|
47
44
|
}
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
get credential() {
|
|
46
|
+
const apiKey = this.options?.apiKey ||
|
|
47
|
+
process.env[this.apiKeyEnvName] ||
|
|
48
|
+
process.env.GEMINI_API_KEY ||
|
|
49
|
+
process.env.GOOGLE_API_KEY;
|
|
50
|
+
return {
|
|
51
|
+
apiKey,
|
|
52
|
+
model: this.options?.model || GEMINI_DEFAULT_CHAT_MODEL,
|
|
53
|
+
};
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
+
get modelOptions() {
|
|
56
|
+
return this.options?.modelOptions;
|
|
57
|
+
}
|
|
58
|
+
process(input) {
|
|
59
|
+
return this.processInput(input);
|
|
60
|
+
}
|
|
61
|
+
async *processInput(input) {
|
|
55
62
|
const model = input.modelOptions?.model || this.credential.model;
|
|
56
63
|
const { contents, config } = await this.buildContents(input);
|
|
57
64
|
const parameters = {
|
|
58
|
-
model
|
|
65
|
+
model,
|
|
59
66
|
contents,
|
|
60
67
|
config: {
|
|
61
68
|
responseModalities: input.modelOptions?.modalities,
|
|
@@ -64,7 +71,6 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
64
71
|
frequencyPenalty: input.modelOptions?.frequencyPenalty || this.modelOptions?.frequencyPenalty,
|
|
65
72
|
presencePenalty: input.modelOptions?.presencePenalty || this.modelOptions?.presencePenalty,
|
|
66
73
|
...config,
|
|
67
|
-
...(await this.buildTools(input)),
|
|
68
74
|
...(await this.buildConfig(input)),
|
|
69
75
|
},
|
|
70
76
|
};
|
|
@@ -77,6 +83,7 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
77
83
|
const files = [];
|
|
78
84
|
const toolCalls = [];
|
|
79
85
|
let text = "";
|
|
86
|
+
let json;
|
|
80
87
|
for await (const chunk of response) {
|
|
81
88
|
if (!responseModel && chunk.modelVersion) {
|
|
82
89
|
responseModel = chunk.modelVersion;
|
|
@@ -100,15 +107,20 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
100
107
|
});
|
|
101
108
|
}
|
|
102
109
|
if (part.functionCall?.name) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
if (part.functionCall.name === OUTPUT_JSON_FUNCTION_NAME) {
|
|
111
|
+
json = part.functionCall.args;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
toolCalls.push({
|
|
115
|
+
id: part.functionCall.id || (0, uuid_1.v7)(),
|
|
116
|
+
type: "function",
|
|
117
|
+
function: {
|
|
118
|
+
name: part.functionCall.name,
|
|
119
|
+
arguments: part.functionCall.args || {},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
yield { delta: { json: { toolCalls } } };
|
|
123
|
+
}
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
}
|
|
@@ -119,15 +131,51 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
119
131
|
}
|
|
120
132
|
}
|
|
121
133
|
if (input.responseFormat?.type === "json_schema") {
|
|
122
|
-
|
|
134
|
+
if (json) {
|
|
135
|
+
yield { delta: { json: { json } } };
|
|
136
|
+
}
|
|
137
|
+
else if (text) {
|
|
138
|
+
yield { delta: { json: { json: (0, core_1.safeParseJSON)(text) } } };
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// NOTE: Trigger retry of chat model
|
|
142
|
+
throw new core_1.StructuredOutputError("No JSON response from the model");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (!toolCalls.length) {
|
|
146
|
+
if (!text) {
|
|
147
|
+
logger_js_1.logger.error("No text response from the model", parameters);
|
|
148
|
+
// NOTE: Trigger retry of chat model
|
|
149
|
+
throw new core_1.StructuredOutputError("No text response from the model");
|
|
150
|
+
}
|
|
123
151
|
}
|
|
124
|
-
yield { delta: { json: { usage, files } } };
|
|
152
|
+
yield { delta: { json: { usage, files: files.length ? files : undefined } } };
|
|
125
153
|
}
|
|
126
154
|
async buildConfig(input) {
|
|
127
155
|
const config = {};
|
|
156
|
+
const { tools, toolConfig } = await this.buildTools(input);
|
|
157
|
+
config.tools = tools;
|
|
158
|
+
config.toolConfig = toolConfig;
|
|
128
159
|
if (input.responseFormat?.type === "json_schema") {
|
|
129
|
-
config.
|
|
130
|
-
|
|
160
|
+
if (config.tools?.length) {
|
|
161
|
+
config.tools.push({
|
|
162
|
+
functionDeclarations: [
|
|
163
|
+
{
|
|
164
|
+
name: OUTPUT_JSON_FUNCTION_NAME,
|
|
165
|
+
description: "Output the final response in JSON format",
|
|
166
|
+
parametersJsonSchema: input.responseFormat.jsonSchema.schema,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
config.toolConfig = {
|
|
171
|
+
...config.toolConfig,
|
|
172
|
+
functionCallingConfig: { mode: genai_1.FunctionCallingConfigMode.ANY },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
config.responseJsonSchema = input.responseFormat.jsonSchema.schema;
|
|
177
|
+
config.responseMimeType = "application/json";
|
|
178
|
+
}
|
|
131
179
|
}
|
|
132
180
|
return config;
|
|
133
181
|
}
|
|
@@ -178,7 +226,7 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
178
226
|
return;
|
|
179
227
|
}
|
|
180
228
|
const content = {
|
|
181
|
-
role: msg.role === "agent" ? "model" : "user",
|
|
229
|
+
role: msg.role === "agent" ? "model" : msg.role === "user" ? "user" : undefined,
|
|
182
230
|
};
|
|
183
231
|
if (msg.toolCalls) {
|
|
184
232
|
content.parts = msg.toolCalls.map((call) => ({
|
|
@@ -195,12 +243,31 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
195
243
|
.find((c) => c?.id === msg.toolCallId);
|
|
196
244
|
if (!call)
|
|
197
245
|
throw new Error(`Tool call not found: ${msg.toolCallId}`);
|
|
246
|
+
const output = JSON.parse(msg.content);
|
|
247
|
+
const isError = "error" in output && Boolean(input.error);
|
|
248
|
+
const response = {
|
|
249
|
+
tool: call.function.name,
|
|
250
|
+
};
|
|
251
|
+
// NOTE: base on the documentation of gemini api, the content should include `output` field for successful result or `error` field for failed result,
|
|
252
|
+
// and base on the actual test, add a tool field presenting the tool name can improve the LLM understanding that which tool is called.
|
|
253
|
+
if (isError) {
|
|
254
|
+
Object.assign(response, { status: "error" }, output);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
Object.assign(response, { status: "success" });
|
|
258
|
+
if ("output" in output) {
|
|
259
|
+
Object.assign(response, output);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
Object.assign(response, { output });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
198
265
|
content.parts = [
|
|
199
266
|
{
|
|
200
267
|
functionResponse: {
|
|
201
268
|
id: msg.toolCallId,
|
|
202
269
|
name: call.function.name,
|
|
203
|
-
response
|
|
270
|
+
response,
|
|
204
271
|
},
|
|
205
272
|
},
|
|
206
273
|
];
|
|
@@ -224,24 +291,17 @@ class GeminiChatModel extends openai_1.OpenAIChatModel {
|
|
|
224
291
|
}
|
|
225
292
|
return content;
|
|
226
293
|
}))).filter(type_utils_js_1.isNonNullable);
|
|
294
|
+
if (!result.contents.length && systemParts.length) {
|
|
295
|
+
const system = systemParts.pop();
|
|
296
|
+
if (system) {
|
|
297
|
+
result.contents.push({ role: "user", parts: [system] });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
227
300
|
if (systemParts.length) {
|
|
228
301
|
result.config ??= {};
|
|
229
302
|
result.config.systemInstruction = systemParts;
|
|
230
303
|
}
|
|
231
304
|
return result;
|
|
232
305
|
}
|
|
233
|
-
async getRunMessages(input) {
|
|
234
|
-
const messages = await super.getRunMessages(input);
|
|
235
|
-
if (!messages.some((i) => i.role === "user")) {
|
|
236
|
-
for (const msg of messages) {
|
|
237
|
-
if (msg.role === "system") {
|
|
238
|
-
// Ensure the last message is from the user
|
|
239
|
-
msg.role = "user";
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return messages;
|
|
245
|
-
}
|
|
246
306
|
}
|
|
247
307
|
exports.GeminiChatModel = GeminiChatModel;
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type AgentProcessResult, ChatModel, type ChatModelInput, type ChatModelOptions, type ChatModelOutput } from "@aigne/core";
|
|
2
2
|
import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { GoogleGenAI, type GoogleGenAIOptions } from "@google/genai";
|
|
4
|
+
export interface GeminiChatModelOptions extends ChatModelOptions {
|
|
5
|
+
/**
|
|
6
|
+
* API key for Gemini API
|
|
7
|
+
*
|
|
8
|
+
* If not provided, will look for GEMINI_API_KEY or GOOGLE_API_KEY in environment variables
|
|
9
|
+
*/
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional client options for the Gemini SDK
|
|
13
|
+
*/
|
|
14
|
+
clientOptions?: Partial<GoogleGenAIOptions>;
|
|
15
|
+
}
|
|
5
16
|
/**
|
|
6
17
|
* Implementation of the ChatModel interface for Google's Gemini API
|
|
7
18
|
*
|
|
8
|
-
* This model uses OpenAI-compatible API format to interact with Google's Gemini models,
|
|
9
|
-
* providing access to models like Gemini 1.5 and Gemini 2.0.
|
|
10
|
-
*
|
|
11
19
|
* @example
|
|
12
20
|
* Here's how to create and use a Gemini chat model:
|
|
13
21
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
|
|
@@ -16,19 +24,20 @@ import { GoogleGenAI } from "@google/genai";
|
|
|
16
24
|
* Here's an example with streaming response:
|
|
17
25
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
|
|
18
26
|
*/
|
|
19
|
-
export declare class GeminiChatModel extends
|
|
20
|
-
|
|
27
|
+
export declare class GeminiChatModel extends ChatModel {
|
|
28
|
+
options?: GeminiChatModelOptions | undefined;
|
|
29
|
+
constructor(options?: GeminiChatModelOptions | undefined);
|
|
21
30
|
protected apiKeyEnvName: string;
|
|
22
|
-
protected supportsToolsUseWithJsonSchema: boolean;
|
|
23
|
-
protected supportsParallelToolCalls: boolean;
|
|
24
|
-
protected supportsToolStreaming: boolean;
|
|
25
|
-
protected optionalFieldMode: "optional";
|
|
26
31
|
protected _googleClient?: GoogleGenAI;
|
|
27
32
|
get googleClient(): GoogleGenAI;
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
get credential(): {
|
|
34
|
+
apiKey: string | undefined;
|
|
35
|
+
model: string;
|
|
36
|
+
};
|
|
37
|
+
get modelOptions(): Omit<import("@aigne/core").ChatModelInputOptions, "model"> | undefined;
|
|
38
|
+
process(input: ChatModelInput): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
39
|
+
private processInput;
|
|
30
40
|
private buildConfig;
|
|
31
41
|
private buildTools;
|
|
32
42
|
private buildContents;
|
|
33
|
-
getRunMessages(input: ChatModelInput): ReturnType<OpenAIChatModel["getRunMessages"]>;
|
|
34
43
|
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type AgentProcessResult, ChatModel, type ChatModelInput, type ChatModelOptions, type ChatModelOutput } from "@aigne/core";
|
|
2
2
|
import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { GoogleGenAI, type GoogleGenAIOptions } from "@google/genai";
|
|
4
|
+
export interface GeminiChatModelOptions extends ChatModelOptions {
|
|
5
|
+
/**
|
|
6
|
+
* API key for Gemini API
|
|
7
|
+
*
|
|
8
|
+
* If not provided, will look for GEMINI_API_KEY or GOOGLE_API_KEY in environment variables
|
|
9
|
+
*/
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional client options for the Gemini SDK
|
|
13
|
+
*/
|
|
14
|
+
clientOptions?: Partial<GoogleGenAIOptions>;
|
|
15
|
+
}
|
|
5
16
|
/**
|
|
6
17
|
* Implementation of the ChatModel interface for Google's Gemini API
|
|
7
18
|
*
|
|
8
|
-
* This model uses OpenAI-compatible API format to interact with Google's Gemini models,
|
|
9
|
-
* providing access to models like Gemini 1.5 and Gemini 2.0.
|
|
10
|
-
*
|
|
11
19
|
* @example
|
|
12
20
|
* Here's how to create and use a Gemini chat model:
|
|
13
21
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
|
|
@@ -16,19 +24,20 @@ import { GoogleGenAI } from "@google/genai";
|
|
|
16
24
|
* Here's an example with streaming response:
|
|
17
25
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
|
|
18
26
|
*/
|
|
19
|
-
export declare class GeminiChatModel extends
|
|
20
|
-
|
|
27
|
+
export declare class GeminiChatModel extends ChatModel {
|
|
28
|
+
options?: GeminiChatModelOptions | undefined;
|
|
29
|
+
constructor(options?: GeminiChatModelOptions | undefined);
|
|
21
30
|
protected apiKeyEnvName: string;
|
|
22
|
-
protected supportsToolsUseWithJsonSchema: boolean;
|
|
23
|
-
protected supportsParallelToolCalls: boolean;
|
|
24
|
-
protected supportsToolStreaming: boolean;
|
|
25
|
-
protected optionalFieldMode: "optional";
|
|
26
31
|
protected _googleClient?: GoogleGenAI;
|
|
27
32
|
get googleClient(): GoogleGenAI;
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
get credential(): {
|
|
34
|
+
apiKey: string | undefined;
|
|
35
|
+
model: string;
|
|
36
|
+
};
|
|
37
|
+
get modelOptions(): Omit<import("@aigne/core").ChatModelInputOptions, "model"> | undefined;
|
|
38
|
+
process(input: ChatModelInput): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
|
|
39
|
+
private processInput;
|
|
30
40
|
private buildConfig;
|
|
31
41
|
private buildTools;
|
|
32
42
|
private buildContents;
|
|
33
|
-
getRunMessages(input: ChatModelInput): ReturnType<OpenAIChatModel["getRunMessages"]>;
|
|
34
43
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { safeParseJSON, } from "@aigne/core";
|
|
1
|
+
import { ChatModel, StructuredOutputError, safeParseJSON, } from "@aigne/core";
|
|
2
|
+
import { logger } from "@aigne/core/utils/logger.js";
|
|
2
3
|
import { isNonNullable } from "@aigne/core/utils/type-utils.js";
|
|
3
|
-
import { OpenAIChatModel } from "@aigne/openai";
|
|
4
4
|
import { v7 } from "@aigne/uuid";
|
|
5
5
|
import { FunctionCallingConfigMode, GoogleGenAI, } from "@google/genai";
|
|
6
|
-
const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai";
|
|
7
6
|
const GEMINI_DEFAULT_CHAT_MODEL = "gemini-2.0-flash";
|
|
7
|
+
const OUTPUT_JSON_FUNCTION_NAME = "output_json";
|
|
8
8
|
/**
|
|
9
9
|
* Implementation of the ChatModel interface for Google's Gemini API
|
|
10
10
|
*
|
|
11
|
-
* This model uses OpenAI-compatible API format to interact with Google's Gemini models,
|
|
12
|
-
* providing access to models like Gemini 1.5 and Gemini 2.0.
|
|
13
|
-
*
|
|
14
11
|
* @example
|
|
15
12
|
* Here's how to create and use a Gemini chat model:
|
|
16
13
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model}
|
|
@@ -19,19 +16,16 @@ const GEMINI_DEFAULT_CHAT_MODEL = "gemini-2.0-flash";
|
|
|
19
16
|
* Here's an example with streaming response:
|
|
20
17
|
* {@includeCode ../test/gemini-chat-model.test.ts#example-gemini-chat-model-streaming}
|
|
21
18
|
*/
|
|
22
|
-
export class GeminiChatModel extends
|
|
19
|
+
export class GeminiChatModel extends ChatModel {
|
|
20
|
+
options;
|
|
23
21
|
constructor(options) {
|
|
24
22
|
super({
|
|
25
23
|
...options,
|
|
26
24
|
model: options?.model || GEMINI_DEFAULT_CHAT_MODEL,
|
|
27
|
-
baseURL: options?.baseURL || GEMINI_BASE_URL,
|
|
28
25
|
});
|
|
26
|
+
this.options = options;
|
|
29
27
|
}
|
|
30
28
|
apiKeyEnvName = "GEMINI_API_KEY";
|
|
31
|
-
supportsToolsUseWithJsonSchema = false;
|
|
32
|
-
supportsParallelToolCalls = false;
|
|
33
|
-
supportsToolStreaming = false;
|
|
34
|
-
optionalFieldMode = "optional";
|
|
35
29
|
_googleClient;
|
|
36
30
|
get googleClient() {
|
|
37
31
|
if (this._googleClient)
|
|
@@ -39,20 +33,33 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
39
33
|
const { apiKey } = this.credential;
|
|
40
34
|
if (!apiKey)
|
|
41
35
|
throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
|
|
42
|
-
this._googleClient ??= new GoogleGenAI({
|
|
36
|
+
this._googleClient ??= new GoogleGenAI({
|
|
37
|
+
apiKey,
|
|
38
|
+
...this.options?.clientOptions,
|
|
39
|
+
});
|
|
43
40
|
return this._googleClient;
|
|
44
41
|
}
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
get credential() {
|
|
43
|
+
const apiKey = this.options?.apiKey ||
|
|
44
|
+
process.env[this.apiKeyEnvName] ||
|
|
45
|
+
process.env.GEMINI_API_KEY ||
|
|
46
|
+
process.env.GOOGLE_API_KEY;
|
|
47
|
+
return {
|
|
48
|
+
apiKey,
|
|
49
|
+
model: this.options?.model || GEMINI_DEFAULT_CHAT_MODEL,
|
|
50
|
+
};
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
get modelOptions() {
|
|
53
|
+
return this.options?.modelOptions;
|
|
54
|
+
}
|
|
55
|
+
process(input) {
|
|
56
|
+
return this.processInput(input);
|
|
57
|
+
}
|
|
58
|
+
async *processInput(input) {
|
|
52
59
|
const model = input.modelOptions?.model || this.credential.model;
|
|
53
60
|
const { contents, config } = await this.buildContents(input);
|
|
54
61
|
const parameters = {
|
|
55
|
-
model
|
|
62
|
+
model,
|
|
56
63
|
contents,
|
|
57
64
|
config: {
|
|
58
65
|
responseModalities: input.modelOptions?.modalities,
|
|
@@ -61,7 +68,6 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
61
68
|
frequencyPenalty: input.modelOptions?.frequencyPenalty || this.modelOptions?.frequencyPenalty,
|
|
62
69
|
presencePenalty: input.modelOptions?.presencePenalty || this.modelOptions?.presencePenalty,
|
|
63
70
|
...config,
|
|
64
|
-
...(await this.buildTools(input)),
|
|
65
71
|
...(await this.buildConfig(input)),
|
|
66
72
|
},
|
|
67
73
|
};
|
|
@@ -74,6 +80,7 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
74
80
|
const files = [];
|
|
75
81
|
const toolCalls = [];
|
|
76
82
|
let text = "";
|
|
83
|
+
let json;
|
|
77
84
|
for await (const chunk of response) {
|
|
78
85
|
if (!responseModel && chunk.modelVersion) {
|
|
79
86
|
responseModel = chunk.modelVersion;
|
|
@@ -97,15 +104,20 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
97
104
|
});
|
|
98
105
|
}
|
|
99
106
|
if (part.functionCall?.name) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
if (part.functionCall.name === OUTPUT_JSON_FUNCTION_NAME) {
|
|
108
|
+
json = part.functionCall.args;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
toolCalls.push({
|
|
112
|
+
id: part.functionCall.id || v7(),
|
|
113
|
+
type: "function",
|
|
114
|
+
function: {
|
|
115
|
+
name: part.functionCall.name,
|
|
116
|
+
arguments: part.functionCall.args || {},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
yield { delta: { json: { toolCalls } } };
|
|
120
|
+
}
|
|
109
121
|
}
|
|
110
122
|
}
|
|
111
123
|
}
|
|
@@ -116,15 +128,51 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
116
128
|
}
|
|
117
129
|
}
|
|
118
130
|
if (input.responseFormat?.type === "json_schema") {
|
|
119
|
-
|
|
131
|
+
if (json) {
|
|
132
|
+
yield { delta: { json: { json } } };
|
|
133
|
+
}
|
|
134
|
+
else if (text) {
|
|
135
|
+
yield { delta: { json: { json: safeParseJSON(text) } } };
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// NOTE: Trigger retry of chat model
|
|
139
|
+
throw new StructuredOutputError("No JSON response from the model");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (!toolCalls.length) {
|
|
143
|
+
if (!text) {
|
|
144
|
+
logger.error("No text response from the model", parameters);
|
|
145
|
+
// NOTE: Trigger retry of chat model
|
|
146
|
+
throw new StructuredOutputError("No text response from the model");
|
|
147
|
+
}
|
|
120
148
|
}
|
|
121
|
-
yield { delta: { json: { usage, files } } };
|
|
149
|
+
yield { delta: { json: { usage, files: files.length ? files : undefined } } };
|
|
122
150
|
}
|
|
123
151
|
async buildConfig(input) {
|
|
124
152
|
const config = {};
|
|
153
|
+
const { tools, toolConfig } = await this.buildTools(input);
|
|
154
|
+
config.tools = tools;
|
|
155
|
+
config.toolConfig = toolConfig;
|
|
125
156
|
if (input.responseFormat?.type === "json_schema") {
|
|
126
|
-
config.
|
|
127
|
-
|
|
157
|
+
if (config.tools?.length) {
|
|
158
|
+
config.tools.push({
|
|
159
|
+
functionDeclarations: [
|
|
160
|
+
{
|
|
161
|
+
name: OUTPUT_JSON_FUNCTION_NAME,
|
|
162
|
+
description: "Output the final response in JSON format",
|
|
163
|
+
parametersJsonSchema: input.responseFormat.jsonSchema.schema,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
config.toolConfig = {
|
|
168
|
+
...config.toolConfig,
|
|
169
|
+
functionCallingConfig: { mode: FunctionCallingConfigMode.ANY },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
config.responseJsonSchema = input.responseFormat.jsonSchema.schema;
|
|
174
|
+
config.responseMimeType = "application/json";
|
|
175
|
+
}
|
|
128
176
|
}
|
|
129
177
|
return config;
|
|
130
178
|
}
|
|
@@ -175,7 +223,7 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
175
223
|
return;
|
|
176
224
|
}
|
|
177
225
|
const content = {
|
|
178
|
-
role: msg.role === "agent" ? "model" : "user",
|
|
226
|
+
role: msg.role === "agent" ? "model" : msg.role === "user" ? "user" : undefined,
|
|
179
227
|
};
|
|
180
228
|
if (msg.toolCalls) {
|
|
181
229
|
content.parts = msg.toolCalls.map((call) => ({
|
|
@@ -192,12 +240,31 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
192
240
|
.find((c) => c?.id === msg.toolCallId);
|
|
193
241
|
if (!call)
|
|
194
242
|
throw new Error(`Tool call not found: ${msg.toolCallId}`);
|
|
243
|
+
const output = JSON.parse(msg.content);
|
|
244
|
+
const isError = "error" in output && Boolean(input.error);
|
|
245
|
+
const response = {
|
|
246
|
+
tool: call.function.name,
|
|
247
|
+
};
|
|
248
|
+
// NOTE: base on the documentation of gemini api, the content should include `output` field for successful result or `error` field for failed result,
|
|
249
|
+
// and base on the actual test, add a tool field presenting the tool name can improve the LLM understanding that which tool is called.
|
|
250
|
+
if (isError) {
|
|
251
|
+
Object.assign(response, { status: "error" }, output);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
Object.assign(response, { status: "success" });
|
|
255
|
+
if ("output" in output) {
|
|
256
|
+
Object.assign(response, output);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
Object.assign(response, { output });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
195
262
|
content.parts = [
|
|
196
263
|
{
|
|
197
264
|
functionResponse: {
|
|
198
265
|
id: msg.toolCallId,
|
|
199
266
|
name: call.function.name,
|
|
200
|
-
response
|
|
267
|
+
response,
|
|
201
268
|
},
|
|
202
269
|
},
|
|
203
270
|
];
|
|
@@ -221,23 +288,16 @@ export class GeminiChatModel extends OpenAIChatModel {
|
|
|
221
288
|
}
|
|
222
289
|
return content;
|
|
223
290
|
}))).filter(isNonNullable);
|
|
291
|
+
if (!result.contents.length && systemParts.length) {
|
|
292
|
+
const system = systemParts.pop();
|
|
293
|
+
if (system) {
|
|
294
|
+
result.contents.push({ role: "user", parts: [system] });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
224
297
|
if (systemParts.length) {
|
|
225
298
|
result.config ??= {};
|
|
226
299
|
result.config.systemInstruction = systemParts;
|
|
227
300
|
}
|
|
228
301
|
return result;
|
|
229
302
|
}
|
|
230
|
-
async getRunMessages(input) {
|
|
231
|
-
const messages = await super.getRunMessages(input);
|
|
232
|
-
if (!messages.some((i) => i.role === "user")) {
|
|
233
|
-
for (const msg of messages) {
|
|
234
|
-
if (msg.role === "system") {
|
|
235
|
-
// Ensure the last message is from the user
|
|
236
|
-
msg.role = "user";
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return messages;
|
|
242
|
-
}
|
|
243
303
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/gemini",
|
|
3
|
-
"version": "0.14.2-beta.
|
|
3
|
+
"version": "0.14.2-beta.9",
|
|
4
4
|
"description": "AIGNE Gemini SDK for integrating with Google's Gemini AI models",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@google/genai": "^1.24.0",
|
|
40
40
|
"zod": "^3.25.67",
|
|
41
41
|
"@aigne/platform-helpers": "^0.6.3",
|
|
42
|
-
"@aigne/
|
|
42
|
+
"@aigne/core": "^1.63.0-beta.9"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/bun": "^1.2.22",
|
|
@@ -47,8 +47,7 @@
|
|
|
47
47
|
"npm-run-all": "^4.1.5",
|
|
48
48
|
"rimraf": "^6.0.1",
|
|
49
49
|
"typescript": "^5.9.2",
|
|
50
|
-
"@aigne/
|
|
51
|
-
"@aigne/test-utils": "^0.5.55-beta.8"
|
|
50
|
+
"@aigne/test-utils": "^0.5.55-beta.9"
|
|
52
51
|
},
|
|
53
52
|
"scripts": {
|
|
54
53
|
"lint": "tsc --noEmit",
|