koishi-plugin-chatluna-google-gemini-adapter 1.3.0-alpha.9 → 1.3.1
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 +7 -7
- package/lib/index.cjs +95 -34
- package/lib/index.d.ts +2 -2
- package/lib/index.mjs +93 -32
- package/lib/types.d.ts +11 -1
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
## chatluna-google-gemini-adapter
|
|
2
|
-
|
|
3
|
-
## [](https://www.npmjs.com/package/koishi-plugin-chatluna-google-gemini) [](https://www.npmjs.com/package//koishi-plugin-chatluna-google-gemini-adapter)
|
|
4
|
-
|
|
5
|
-
> 为 ChatLuna 提供 Google Gemini 支持的适配器
|
|
6
|
-
|
|
7
|
-
[Gemini 适配器文档](https://chatluna.chat/guide/configure-model-platform/google-gemini.html)
|
|
1
|
+
## chatluna-google-gemini-adapter
|
|
2
|
+
|
|
3
|
+
## [](https://www.npmjs.com/package/koishi-plugin-chatluna-google-gemini) [](https://www.npmjs.com/package//koishi-plugin-chatluna-google-gemini-adapter)
|
|
4
|
+
|
|
5
|
+
> 为 ChatLuna 提供 Google Gemini 支持的适配器
|
|
6
|
+
|
|
7
|
+
[Gemini 适配器文档](https://chatluna.chat/guide/configure-model-platform/google-gemini.html)
|
package/lib/index.cjs
CHANGED
|
@@ -23,14 +23,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
23
23
|
// src/locales/zh-CN.schema.yml
|
|
24
24
|
var require_zh_CN_schema = __commonJS({
|
|
25
25
|
"src/locales/zh-CN.schema.yml"(exports2, module2) {
|
|
26
|
-
module2.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini
|
|
26
|
+
module2.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini API Key", "Gemini API 请求地址", "是否启用此配置"], $desc: "Gemini 的 API Key 和请求地址列表。" } }, { $desc: "模型配置", maxContextRatio: "最大上下文使用比例(0~1),控制可用的模型上下文窗口大小的最大百分比。例如 0.35 表示最多使用模型上下文的 35%。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-2.0-flash-exp` 和 `gemini-2.5-flash-image-preview` 模型。", searchThreshold: "搜索的[置信度阈值](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval),范围:0~1,设置的数值越低,则越倾向于使用谷歌搜索。(仅支持 `gemini-1.5` 系列模型。gemini 2.0 模型起使用动态的工具调用)", includeThoughts: "是否获取模型的思考内容。", codeExecution: "为模型启用代码执行工具。", urlContext: "为模型启用 URL 内容获取工具。", nonStreaming: "强制不启用流式返回。开启后,将总是以非流式发起请求,即便配置了 stream 参数。" }] };
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
// src/locales/en-US.schema.yml
|
|
31
31
|
var require_en_US_schema = __commonJS({
|
|
32
32
|
"src/locales/en-US.schema.yml"(exports2, module2) {
|
|
33
|
-
module2.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters",
|
|
33
|
+
module2.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)", "Enabled"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters", maxContextRatio: "Maximum context usage ratio (0-1). Controls the maximum percentage of model context window available for use. For example, 0.35 means at most 35% of the model context can be used.", temperature: "Sampling temperature (0-2). Higher: more random, Lower: more deterministic", googleSearch: "Enable Google search", thinkingBudget: "Thinking budget (-1-24576). (0: dynamic thinking) Higher: more tokens spent on thinking. Currently only supports `gemini-2.5` series models.", groundingContentDisplay: "Enable display of search results", imageGeneration: "Enable image generation (only for `gemini-2.0-flash-exp` and `gemini-2.5-flash-image-preview` model)", searchThreshold: "Search confidence [threshold](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval) (0-1). Lower: more likely to use Google search", includeThoughts: "Enable retrieval of model thoughts", codeExecution: "Enable code execution tool", urlContext: "Enable URL context retrieval tool", nonStreaming: "Force disable streaming response. When enabled, requests will always be made in non-streaming mode, even if the stream parameter is configured." }] };
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -63,10 +63,10 @@ var import_sse = require("koishi-plugin-chatluna/utils/sse");
|
|
|
63
63
|
var import_stream = require("koishi-plugin-chatluna/utils/stream");
|
|
64
64
|
|
|
65
65
|
// src/utils.ts
|
|
66
|
-
var import_zod_to_json_schema = require("zod-to-json-schema");
|
|
67
66
|
var import_v1_shared_adapter = require("@chatluna/v1-shared-adapter");
|
|
68
67
|
var import_string = require("koishi-plugin-chatluna/utils/string");
|
|
69
68
|
var import_types = require("@langchain/core/utils/types");
|
|
69
|
+
var import_zod_openapi = require("@anatine/zod-openapi");
|
|
70
70
|
async function langchainMessageToGeminiMessage(messages, plugin, model) {
|
|
71
71
|
return Promise.all(
|
|
72
72
|
messages.map(async (message) => {
|
|
@@ -79,7 +79,12 @@ async function langchainMessageToGeminiMessage(messages, plugin, model) {
|
|
|
79
79
|
role,
|
|
80
80
|
parts: []
|
|
81
81
|
};
|
|
82
|
-
|
|
82
|
+
const thoughtData = message.additional_kwargs["thought_data"] ?? {};
|
|
83
|
+
result.parts = typeof message.content === "string" ? [{ text: message.content, ...thoughtData }] : await processGeminiContentParts(
|
|
84
|
+
plugin,
|
|
85
|
+
message.content,
|
|
86
|
+
thoughtData
|
|
87
|
+
);
|
|
83
88
|
const images = message.additional_kwargs.images;
|
|
84
89
|
if (images) {
|
|
85
90
|
processImageParts(result, images, model);
|
|
@@ -129,6 +134,7 @@ function parseJsonArgs(args) {
|
|
|
129
134
|
}
|
|
130
135
|
__name(parseJsonArgs, "parseJsonArgs");
|
|
131
136
|
function processFunctionMessage(message) {
|
|
137
|
+
const thoughtData = message.additional_kwargs["thought_data"] ?? {};
|
|
132
138
|
if (message["tool_calls"]) {
|
|
133
139
|
message = message;
|
|
134
140
|
const toolCalls = message.tool_calls;
|
|
@@ -140,7 +146,8 @@ function processFunctionMessage(message) {
|
|
|
140
146
|
name: toolCall.name,
|
|
141
147
|
args: toolCall.args,
|
|
142
148
|
id: toolCall.id
|
|
143
|
-
}
|
|
149
|
+
},
|
|
150
|
+
...thoughtData
|
|
144
151
|
};
|
|
145
152
|
})
|
|
146
153
|
};
|
|
@@ -195,11 +202,11 @@ async function processGeminiImageContent(plugin, part) {
|
|
|
195
202
|
};
|
|
196
203
|
}
|
|
197
204
|
__name(processGeminiImageContent, "processGeminiImageContent");
|
|
198
|
-
async function processGeminiContentParts(plugin, content) {
|
|
205
|
+
async function processGeminiContentParts(plugin, content, thoughtData) {
|
|
199
206
|
return Promise.all(
|
|
200
207
|
content.map(async (part) => {
|
|
201
208
|
if ((0, import_string.isMessageContentText)(part)) {
|
|
202
|
-
return { text: part.text };
|
|
209
|
+
return { text: part.text, ...thoughtData };
|
|
203
210
|
}
|
|
204
211
|
if ((0, import_string.isMessageContentImageUrl)(part)) {
|
|
205
212
|
return await processGeminiImageContent(plugin, part);
|
|
@@ -287,9 +294,7 @@ function formatToolsToGeminiAITools(tools, config, model) {
|
|
|
287
294
|
__name(formatToolsToGeminiAITools, "formatToolsToGeminiAITools");
|
|
288
295
|
function formatToolToGeminiAITool(tool) {
|
|
289
296
|
const parameters = (0, import_v1_shared_adapter.removeAdditionalProperties)(
|
|
290
|
-
(0, import_types.isZodSchemaV3)(tool.schema) ? (0,
|
|
291
|
-
allowedAdditionalProperties: void 0
|
|
292
|
-
}) : tool.schema
|
|
297
|
+
(0, import_types.isZodSchemaV3)(tool.schema) ? (0, import_zod_openapi.generateSchema)(tool.schema, true, "3.0") : tool.schema
|
|
293
298
|
);
|
|
294
299
|
return {
|
|
295
300
|
name: tool.name,
|
|
@@ -405,6 +410,7 @@ __name(isChatResponse, "isChatResponse");
|
|
|
405
410
|
|
|
406
411
|
// src/requester.ts
|
|
407
412
|
var import_string2 = require("koishi-plugin-chatluna/utils/string");
|
|
413
|
+
var import_logger = require("koishi-plugin-chatluna/utils/logger");
|
|
408
414
|
var GeminiRequester = class extends import_api.ModelRequester {
|
|
409
415
|
constructor(ctx, _configPool, _pluginConfig, _plugin) {
|
|
410
416
|
super(ctx, _configPool, _pluginConfig, _plugin);
|
|
@@ -448,6 +454,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
|
|
|
448
454
|
await (0, import_sse.checkResponse)(response);
|
|
449
455
|
yield* this._processResponseStream(response);
|
|
450
456
|
} catch (e) {
|
|
457
|
+
if (this.ctx.chatluna.config.isLog) {
|
|
458
|
+
await (0, import_logger.trackLogToLocal)(
|
|
459
|
+
"Request",
|
|
460
|
+
JSON.stringify(chatGenerationParams),
|
|
461
|
+
logger
|
|
462
|
+
);
|
|
463
|
+
}
|
|
451
464
|
if (e instanceof import_error.ChatLunaError) {
|
|
452
465
|
throw e;
|
|
453
466
|
} else {
|
|
@@ -457,15 +470,16 @@ var GeminiRequester = class extends import_api.ModelRequester {
|
|
|
457
470
|
}
|
|
458
471
|
async completionInternal(params) {
|
|
459
472
|
const modelConfig = prepareModelConfig(params, this._pluginConfig);
|
|
473
|
+
const chatGenerationParams = await createChatGenerationParams(
|
|
474
|
+
params,
|
|
475
|
+
this._plugin,
|
|
476
|
+
modelConfig,
|
|
477
|
+
this._pluginConfig
|
|
478
|
+
);
|
|
460
479
|
try {
|
|
461
480
|
const response = await this._post(
|
|
462
481
|
`models/${modelConfig.model}:generateContent`,
|
|
463
|
-
|
|
464
|
-
params,
|
|
465
|
-
this._plugin,
|
|
466
|
-
modelConfig,
|
|
467
|
-
this._pluginConfig
|
|
468
|
-
),
|
|
482
|
+
chatGenerationParams,
|
|
469
483
|
{
|
|
470
484
|
signal: params.signal
|
|
471
485
|
}
|
|
@@ -473,6 +487,13 @@ var GeminiRequester = class extends import_api.ModelRequester {
|
|
|
473
487
|
await (0, import_sse.checkResponse)(response);
|
|
474
488
|
return await this._processResponse(response);
|
|
475
489
|
} catch (e) {
|
|
490
|
+
if (this.ctx.chatluna.config.isLog) {
|
|
491
|
+
await (0, import_logger.trackLogToLocal)(
|
|
492
|
+
"Request",
|
|
493
|
+
JSON.stringify(chatGenerationParams),
|
|
494
|
+
logger
|
|
495
|
+
);
|
|
496
|
+
}
|
|
476
497
|
if (e instanceof import_error.ChatLunaError) {
|
|
477
498
|
throw e;
|
|
478
499
|
} else {
|
|
@@ -669,6 +690,18 @@ var GeminiRequester = class extends import_api.ModelRequester {
|
|
|
669
690
|
return;
|
|
670
691
|
}
|
|
671
692
|
const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
|
|
693
|
+
if (transformValue.usageMetadata) {
|
|
694
|
+
const promptTokens = transformValue.usageMetadata.promptTokenCount;
|
|
695
|
+
const totalTokens = transformValue.usageMetadata.totalTokenCount;
|
|
696
|
+
const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
|
|
697
|
+
controller.enqueue({
|
|
698
|
+
usage: {
|
|
699
|
+
promptTokens,
|
|
700
|
+
completionTokens,
|
|
701
|
+
totalTokens
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
672
705
|
if (!transformValue?.candidates) {
|
|
673
706
|
return;
|
|
674
707
|
}
|
|
@@ -707,6 +740,22 @@ var GeminiRequester = class extends import_api.ModelRequester {
|
|
|
707
740
|
let errorCount = 0;
|
|
708
741
|
let functionIndex = 0;
|
|
709
742
|
for await (const chunk of iterable) {
|
|
743
|
+
let parsedChunk;
|
|
744
|
+
if (parsedChunk = partAsTypeCheck(
|
|
745
|
+
chunk,
|
|
746
|
+
(chunk2) => chunk2["usage"] != null
|
|
747
|
+
)) {
|
|
748
|
+
const generationChunk = new import_outputs.ChatGenerationChunk({
|
|
749
|
+
message: new import_messages.AIMessageChunk({
|
|
750
|
+
content: "",
|
|
751
|
+
response_metadata: {
|
|
752
|
+
tokenUsage: parsedChunk.usage
|
|
753
|
+
}
|
|
754
|
+
}),
|
|
755
|
+
text: ""
|
|
756
|
+
});
|
|
757
|
+
yield { type: "generation", generation: generationChunk };
|
|
758
|
+
}
|
|
710
759
|
try {
|
|
711
760
|
const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
|
|
712
761
|
chunk,
|
|
@@ -722,10 +771,7 @@ var GeminiRequester = class extends import_api.ModelRequester {
|
|
|
722
771
|
const messageChunk = this._createMessageChunk(
|
|
723
772
|
updatedContent,
|
|
724
773
|
updatedToolCalling,
|
|
725
|
-
|
|
726
|
-
chunk,
|
|
727
|
-
(part) => part["inlineData"] != null
|
|
728
|
-
)
|
|
774
|
+
chunk
|
|
729
775
|
);
|
|
730
776
|
const generationChunk = new import_outputs.ChatGenerationChunk({
|
|
731
777
|
message: messageChunk,
|
|
@@ -826,7 +872,11 @@ ${groundingContent}`
|
|
|
826
872
|
}
|
|
827
873
|
}
|
|
828
874
|
}
|
|
829
|
-
_createMessageChunk(content, functionCall,
|
|
875
|
+
_createMessageChunk(content, functionCall, chunk) {
|
|
876
|
+
const imagePart = this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
|
|
877
|
+
chunk,
|
|
878
|
+
(part) => part["inlineData"] != null
|
|
879
|
+
);
|
|
830
880
|
const messageChunk = new import_messages.AIMessageChunk({
|
|
831
881
|
content: content ?? "",
|
|
832
882
|
tool_call_chunks: [functionCall].filter(Boolean)
|
|
@@ -834,7 +884,10 @@ ${groundingContent}`
|
|
|
834
884
|
messageChunk.additional_kwargs = {
|
|
835
885
|
images: imagePart ? [
|
|
836
886
|
`data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
|
|
837
|
-
] : void 0
|
|
887
|
+
] : void 0,
|
|
888
|
+
thought_data: {
|
|
889
|
+
thoughtSignature: chunk["thoughtSignature"]
|
|
890
|
+
}
|
|
838
891
|
};
|
|
839
892
|
return messageChunk;
|
|
840
893
|
}
|
|
@@ -927,7 +980,10 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
|
|
|
927
980
|
import_types2.ModelCapabilities.ToolCall
|
|
928
981
|
]
|
|
929
982
|
};
|
|
930
|
-
|
|
983
|
+
const thinkingModel = ["gemini-2.5-pro", "gemini-3.0-pro"];
|
|
984
|
+
if (thinkingModel.some(
|
|
985
|
+
(name2) => name2.includes(model.name.toLowerCase())
|
|
986
|
+
)) {
|
|
931
987
|
if (!model.name.includes("-thinking")) {
|
|
932
988
|
models.push(
|
|
933
989
|
{ ...info, name: model.name + "-non-thinking" },
|
|
@@ -960,7 +1016,9 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
|
|
|
960
1016
|
requester: this._requester,
|
|
961
1017
|
model,
|
|
962
1018
|
modelMaxContextSize: info.maxTokens,
|
|
963
|
-
maxTokenLimit:
|
|
1019
|
+
maxTokenLimit: Math.floor(
|
|
1020
|
+
(info.maxTokens || 1e5) * this._config.maxContextRatio
|
|
1021
|
+
),
|
|
964
1022
|
timeout: this._config.timeout,
|
|
965
1023
|
temperature: this._config.temperature,
|
|
966
1024
|
maxRetries: this._config.maxRetries,
|
|
@@ -976,15 +1034,17 @@ var GeminiClient = class extends import_client.PlatformModelAndEmbeddingsClient
|
|
|
976
1034
|
};
|
|
977
1035
|
|
|
978
1036
|
// src/index.ts
|
|
979
|
-
var
|
|
1037
|
+
var import_logger2 = require("koishi-plugin-chatluna/utils/logger");
|
|
980
1038
|
var logger;
|
|
981
1039
|
var reusable = true;
|
|
982
1040
|
function apply(ctx, config) {
|
|
983
|
-
|
|
984
|
-
logger = (0, import_logger.createLogger)(ctx, "chatluna-gemini-adapter");
|
|
1041
|
+
logger = (0, import_logger2.createLogger)(ctx, "chatluna-gemini-adapter");
|
|
985
1042
|
ctx.on("ready", async () => {
|
|
1043
|
+
const plugin = new import_chat.ChatLunaPlugin(ctx, config, config.platform);
|
|
986
1044
|
plugin.parseConfig((config2) => {
|
|
987
|
-
return config2.apiKeys.
|
|
1045
|
+
return config2.apiKeys.filter(([apiKey, _, enabled]) => {
|
|
1046
|
+
return apiKey.length > 0 && enabled;
|
|
1047
|
+
}).map(([apiKey, apiEndpoint]) => {
|
|
988
1048
|
return {
|
|
989
1049
|
apiKey,
|
|
990
1050
|
apiEndpoint,
|
|
@@ -996,8 +1056,8 @@ function apply(ctx, config) {
|
|
|
996
1056
|
};
|
|
997
1057
|
});
|
|
998
1058
|
});
|
|
999
|
-
plugin.registerClient((
|
|
1000
|
-
await plugin.
|
|
1059
|
+
plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
|
|
1060
|
+
await plugin.initClient();
|
|
1001
1061
|
});
|
|
1002
1062
|
}
|
|
1003
1063
|
__name(apply, "apply");
|
|
@@ -1007,15 +1067,16 @@ var Config4 = import_koishi.Schema.intersect([
|
|
|
1007
1067
|
platform: import_koishi.Schema.string().default("gemini"),
|
|
1008
1068
|
apiKeys: import_koishi.Schema.array(
|
|
1009
1069
|
import_koishi.Schema.tuple([
|
|
1010
|
-
import_koishi.Schema.string().role("secret"),
|
|
1070
|
+
import_koishi.Schema.string().role("secret").default(""),
|
|
1011
1071
|
import_koishi.Schema.string().default(
|
|
1012
1072
|
"https://generativelanguage.googleapis.com/v1beta"
|
|
1013
|
-
)
|
|
1073
|
+
),
|
|
1074
|
+
import_koishi.Schema.boolean().default(true)
|
|
1014
1075
|
])
|
|
1015
|
-
).default([[
|
|
1076
|
+
).default([[]]).role("table")
|
|
1016
1077
|
}),
|
|
1017
1078
|
import_koishi.Schema.object({
|
|
1018
|
-
|
|
1079
|
+
maxContextRatio: import_koishi.Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
|
|
1019
1080
|
temperature: import_koishi.Schema.percent().min(0).max(2).step(0.1).default(1),
|
|
1020
1081
|
googleSearch: import_koishi.Schema.boolean().default(false),
|
|
1021
1082
|
codeExecution: import_koishi.Schema.boolean().default(false),
|
package/lib/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ export declare let logger: Logger;
|
|
|
4
4
|
export declare const reusable = true;
|
|
5
5
|
export declare function apply(ctx: Context, config: Config): void;
|
|
6
6
|
export interface Config extends ChatLunaPlugin.Config {
|
|
7
|
-
apiKeys: [string, string][];
|
|
8
|
-
|
|
7
|
+
apiKeys: [string, string, boolean][];
|
|
8
|
+
maxContextRatio: number;
|
|
9
9
|
platform: string;
|
|
10
10
|
temperature: number;
|
|
11
11
|
googleSearch: boolean;
|
package/lib/index.mjs
CHANGED
|
@@ -8,14 +8,14 @@ var __commonJS = (cb, mod) => function __require() {
|
|
|
8
8
|
// src/locales/zh-CN.schema.yml
|
|
9
9
|
var require_zh_CN_schema = __commonJS({
|
|
10
10
|
"src/locales/zh-CN.schema.yml"(exports, module) {
|
|
11
|
-
module.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini
|
|
11
|
+
module.exports = { $inner: [{}, { $desc: "请求选项", platform: "适配器的平台名。(不懂请不要修改)", apiKeys: { $inner: ["Gemini API Key", "Gemini API 请求地址", "是否启用此配置"], $desc: "Gemini 的 API Key 和请求地址列表。" } }, { $desc: "模型配置", maxContextRatio: "最大上下文使用比例(0~1),控制可用的模型上下文窗口大小的最大百分比。例如 0.35 表示最多使用模型上下文的 35%。", temperature: "回复的随机性程度,数值越高,回复越随机(范围:0~2)。", googleSearch: "为模型启用谷歌搜索。", thinkingBudget: "思考预算,范围:(-1~24576),设置的数值越大,思考时花费的 Token 越多,-1 为动态思考。目前仅支持 gemini 2.5 系列模型。", groundingContentDisplay: "是否显示谷歌搜索结果。", imageGeneration: "为模型启用图像生成。目前仅支持 `gemini-2.0-flash-exp` 和 `gemini-2.5-flash-image-preview` 模型。", searchThreshold: "搜索的[置信度阈值](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval),范围:0~1,设置的数值越低,则越倾向于使用谷歌搜索。(仅支持 `gemini-1.5` 系列模型。gemini 2.0 模型起使用动态的工具调用)", includeThoughts: "是否获取模型的思考内容。", codeExecution: "为模型启用代码执行工具。", urlContext: "为模型启用 URL 内容获取工具。", nonStreaming: "强制不启用流式返回。开启后,将总是以非流式发起请求,即便配置了 stream 参数。" }] };
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
// src/locales/en-US.schema.yml
|
|
16
16
|
var require_en_US_schema = __commonJS({
|
|
17
17
|
"src/locales/en-US.schema.yml"(exports, module) {
|
|
18
|
-
module.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters",
|
|
18
|
+
module.exports = { $inner: [{}, { $desc: "API Configuration", platform: "Adapter platform name. (Do not modify if you do not understand)", apiKeys: { $inner: ["Gemini API Key", "Gemini API Endpoint (optional)", "Enabled"], $desc: "Gemini API access credentials" } }, { $desc: "Model Parameters", maxContextRatio: "Maximum context usage ratio (0-1). Controls the maximum percentage of model context window available for use. For example, 0.35 means at most 35% of the model context can be used.", temperature: "Sampling temperature (0-2). Higher: more random, Lower: more deterministic", googleSearch: "Enable Google search", thinkingBudget: "Thinking budget (-1-24576). (0: dynamic thinking) Higher: more tokens spent on thinking. Currently only supports `gemini-2.5` series models.", groundingContentDisplay: "Enable display of search results", imageGeneration: "Enable image generation (only for `gemini-2.0-flash-exp` and `gemini-2.5-flash-image-preview` model)", searchThreshold: "Search confidence [threshold](https://ai.google.dev/gemini-api/docs/grounding?lang=rest#dynamic-retrieval) (0-1). Lower: more likely to use Google search", includeThoughts: "Enable retrieval of model thoughts", codeExecution: "Enable code execution tool", urlContext: "Enable URL context retrieval tool", nonStreaming: "Force disable streaming response. When enabled, requests will always be made in non-streaming mode, even if the stream parameter is configured." }] };
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -54,7 +54,6 @@ import { checkResponse, sseIterable } from "koishi-plugin-chatluna/utils/sse";
|
|
|
54
54
|
import { readableStreamToAsyncIterable } from "koishi-plugin-chatluna/utils/stream";
|
|
55
55
|
|
|
56
56
|
// src/utils.ts
|
|
57
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
58
57
|
import {
|
|
59
58
|
fetchImageUrl,
|
|
60
59
|
removeAdditionalProperties
|
|
@@ -64,6 +63,7 @@ import {
|
|
|
64
63
|
isMessageContentText
|
|
65
64
|
} from "koishi-plugin-chatluna/utils/string";
|
|
66
65
|
import { isZodSchemaV3 } from "@langchain/core/utils/types";
|
|
66
|
+
import { generateSchema } from "@anatine/zod-openapi";
|
|
67
67
|
async function langchainMessageToGeminiMessage(messages, plugin, model) {
|
|
68
68
|
return Promise.all(
|
|
69
69
|
messages.map(async (message) => {
|
|
@@ -76,7 +76,12 @@ async function langchainMessageToGeminiMessage(messages, plugin, model) {
|
|
|
76
76
|
role,
|
|
77
77
|
parts: []
|
|
78
78
|
};
|
|
79
|
-
|
|
79
|
+
const thoughtData = message.additional_kwargs["thought_data"] ?? {};
|
|
80
|
+
result.parts = typeof message.content === "string" ? [{ text: message.content, ...thoughtData }] : await processGeminiContentParts(
|
|
81
|
+
plugin,
|
|
82
|
+
message.content,
|
|
83
|
+
thoughtData
|
|
84
|
+
);
|
|
80
85
|
const images = message.additional_kwargs.images;
|
|
81
86
|
if (images) {
|
|
82
87
|
processImageParts(result, images, model);
|
|
@@ -126,6 +131,7 @@ function parseJsonArgs(args) {
|
|
|
126
131
|
}
|
|
127
132
|
__name(parseJsonArgs, "parseJsonArgs");
|
|
128
133
|
function processFunctionMessage(message) {
|
|
134
|
+
const thoughtData = message.additional_kwargs["thought_data"] ?? {};
|
|
129
135
|
if (message["tool_calls"]) {
|
|
130
136
|
message = message;
|
|
131
137
|
const toolCalls = message.tool_calls;
|
|
@@ -137,7 +143,8 @@ function processFunctionMessage(message) {
|
|
|
137
143
|
name: toolCall.name,
|
|
138
144
|
args: toolCall.args,
|
|
139
145
|
id: toolCall.id
|
|
140
|
-
}
|
|
146
|
+
},
|
|
147
|
+
...thoughtData
|
|
141
148
|
};
|
|
142
149
|
})
|
|
143
150
|
};
|
|
@@ -192,11 +199,11 @@ async function processGeminiImageContent(plugin, part) {
|
|
|
192
199
|
};
|
|
193
200
|
}
|
|
194
201
|
__name(processGeminiImageContent, "processGeminiImageContent");
|
|
195
|
-
async function processGeminiContentParts(plugin, content) {
|
|
202
|
+
async function processGeminiContentParts(plugin, content, thoughtData) {
|
|
196
203
|
return Promise.all(
|
|
197
204
|
content.map(async (part) => {
|
|
198
205
|
if (isMessageContentText(part)) {
|
|
199
|
-
return { text: part.text };
|
|
206
|
+
return { text: part.text, ...thoughtData };
|
|
200
207
|
}
|
|
201
208
|
if (isMessageContentImageUrl(part)) {
|
|
202
209
|
return await processGeminiImageContent(plugin, part);
|
|
@@ -284,9 +291,7 @@ function formatToolsToGeminiAITools(tools, config, model) {
|
|
|
284
291
|
__name(formatToolsToGeminiAITools, "formatToolsToGeminiAITools");
|
|
285
292
|
function formatToolToGeminiAITool(tool) {
|
|
286
293
|
const parameters = removeAdditionalProperties(
|
|
287
|
-
isZodSchemaV3(tool.schema) ?
|
|
288
|
-
allowedAdditionalProperties: void 0
|
|
289
|
-
}) : tool.schema
|
|
294
|
+
isZodSchemaV3(tool.schema) ? generateSchema(tool.schema, true, "3.0") : tool.schema
|
|
290
295
|
);
|
|
291
296
|
return {
|
|
292
297
|
name: tool.name,
|
|
@@ -402,6 +407,7 @@ __name(isChatResponse, "isChatResponse");
|
|
|
402
407
|
|
|
403
408
|
// src/requester.ts
|
|
404
409
|
import { getMessageContent } from "koishi-plugin-chatluna/utils/string";
|
|
410
|
+
import { trackLogToLocal } from "koishi-plugin-chatluna/utils/logger";
|
|
405
411
|
var GeminiRequester = class extends ModelRequester {
|
|
406
412
|
constructor(ctx, _configPool, _pluginConfig, _plugin) {
|
|
407
413
|
super(ctx, _configPool, _pluginConfig, _plugin);
|
|
@@ -445,6 +451,13 @@ var GeminiRequester = class extends ModelRequester {
|
|
|
445
451
|
await checkResponse(response);
|
|
446
452
|
yield* this._processResponseStream(response);
|
|
447
453
|
} catch (e) {
|
|
454
|
+
if (this.ctx.chatluna.config.isLog) {
|
|
455
|
+
await trackLogToLocal(
|
|
456
|
+
"Request",
|
|
457
|
+
JSON.stringify(chatGenerationParams),
|
|
458
|
+
logger
|
|
459
|
+
);
|
|
460
|
+
}
|
|
448
461
|
if (e instanceof ChatLunaError) {
|
|
449
462
|
throw e;
|
|
450
463
|
} else {
|
|
@@ -454,15 +467,16 @@ var GeminiRequester = class extends ModelRequester {
|
|
|
454
467
|
}
|
|
455
468
|
async completionInternal(params) {
|
|
456
469
|
const modelConfig = prepareModelConfig(params, this._pluginConfig);
|
|
470
|
+
const chatGenerationParams = await createChatGenerationParams(
|
|
471
|
+
params,
|
|
472
|
+
this._plugin,
|
|
473
|
+
modelConfig,
|
|
474
|
+
this._pluginConfig
|
|
475
|
+
);
|
|
457
476
|
try {
|
|
458
477
|
const response = await this._post(
|
|
459
478
|
`models/${modelConfig.model}:generateContent`,
|
|
460
|
-
|
|
461
|
-
params,
|
|
462
|
-
this._plugin,
|
|
463
|
-
modelConfig,
|
|
464
|
-
this._pluginConfig
|
|
465
|
-
),
|
|
479
|
+
chatGenerationParams,
|
|
466
480
|
{
|
|
467
481
|
signal: params.signal
|
|
468
482
|
}
|
|
@@ -470,6 +484,13 @@ var GeminiRequester = class extends ModelRequester {
|
|
|
470
484
|
await checkResponse(response);
|
|
471
485
|
return await this._processResponse(response);
|
|
472
486
|
} catch (e) {
|
|
487
|
+
if (this.ctx.chatluna.config.isLog) {
|
|
488
|
+
await trackLogToLocal(
|
|
489
|
+
"Request",
|
|
490
|
+
JSON.stringify(chatGenerationParams),
|
|
491
|
+
logger
|
|
492
|
+
);
|
|
493
|
+
}
|
|
473
494
|
if (e instanceof ChatLunaError) {
|
|
474
495
|
throw e;
|
|
475
496
|
} else {
|
|
@@ -666,6 +687,18 @@ var GeminiRequester = class extends ModelRequester {
|
|
|
666
687
|
return;
|
|
667
688
|
}
|
|
668
689
|
const transformValue = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
|
|
690
|
+
if (transformValue.usageMetadata) {
|
|
691
|
+
const promptTokens = transformValue.usageMetadata.promptTokenCount;
|
|
692
|
+
const totalTokens = transformValue.usageMetadata.totalTokenCount;
|
|
693
|
+
const completionTokens = transformValue.usageMetadata.candidatesTokenCount ?? totalTokens - promptTokens;
|
|
694
|
+
controller.enqueue({
|
|
695
|
+
usage: {
|
|
696
|
+
promptTokens,
|
|
697
|
+
completionTokens,
|
|
698
|
+
totalTokens
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
669
702
|
if (!transformValue?.candidates) {
|
|
670
703
|
return;
|
|
671
704
|
}
|
|
@@ -704,6 +737,22 @@ var GeminiRequester = class extends ModelRequester {
|
|
|
704
737
|
let errorCount = 0;
|
|
705
738
|
let functionIndex = 0;
|
|
706
739
|
for await (const chunk of iterable) {
|
|
740
|
+
let parsedChunk;
|
|
741
|
+
if (parsedChunk = partAsTypeCheck(
|
|
742
|
+
chunk,
|
|
743
|
+
(chunk2) => chunk2["usage"] != null
|
|
744
|
+
)) {
|
|
745
|
+
const generationChunk = new ChatGenerationChunk({
|
|
746
|
+
message: new AIMessageChunk({
|
|
747
|
+
content: "",
|
|
748
|
+
response_metadata: {
|
|
749
|
+
tokenUsage: parsedChunk.usage
|
|
750
|
+
}
|
|
751
|
+
}),
|
|
752
|
+
text: ""
|
|
753
|
+
});
|
|
754
|
+
yield { type: "generation", generation: generationChunk };
|
|
755
|
+
}
|
|
707
756
|
try {
|
|
708
757
|
const { updatedContent, updatedReasoning, updatedToolCalling } = await this._processChunk(
|
|
709
758
|
chunk,
|
|
@@ -719,10 +768,7 @@ var GeminiRequester = class extends ModelRequester {
|
|
|
719
768
|
const messageChunk = this._createMessageChunk(
|
|
720
769
|
updatedContent,
|
|
721
770
|
updatedToolCalling,
|
|
722
|
-
|
|
723
|
-
chunk,
|
|
724
|
-
(part) => part["inlineData"] != null
|
|
725
|
-
)
|
|
771
|
+
chunk
|
|
726
772
|
);
|
|
727
773
|
const generationChunk = new ChatGenerationChunk({
|
|
728
774
|
message: messageChunk,
|
|
@@ -823,7 +869,11 @@ ${groundingContent}`
|
|
|
823
869
|
}
|
|
824
870
|
}
|
|
825
871
|
}
|
|
826
|
-
_createMessageChunk(content, functionCall,
|
|
872
|
+
_createMessageChunk(content, functionCall, chunk) {
|
|
873
|
+
const imagePart = this.ctx.chatluna_storage != null ? void 0 : partAsTypeCheck(
|
|
874
|
+
chunk,
|
|
875
|
+
(part) => part["inlineData"] != null
|
|
876
|
+
);
|
|
827
877
|
const messageChunk = new AIMessageChunk({
|
|
828
878
|
content: content ?? "",
|
|
829
879
|
tool_call_chunks: [functionCall].filter(Boolean)
|
|
@@ -831,7 +881,10 @@ ${groundingContent}`
|
|
|
831
881
|
messageChunk.additional_kwargs = {
|
|
832
882
|
images: imagePart ? [
|
|
833
883
|
`data:${imagePart.inlineData.mimeType ?? "image/png"};base64,${imagePart.inlineData.data}`
|
|
834
|
-
] : void 0
|
|
884
|
+
] : void 0,
|
|
885
|
+
thought_data: {
|
|
886
|
+
thoughtSignature: chunk["thoughtSignature"]
|
|
887
|
+
}
|
|
835
888
|
};
|
|
836
889
|
return messageChunk;
|
|
837
890
|
}
|
|
@@ -924,7 +977,10 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
|
|
|
924
977
|
ModelCapabilities.ToolCall
|
|
925
978
|
]
|
|
926
979
|
};
|
|
927
|
-
|
|
980
|
+
const thinkingModel = ["gemini-2.5-pro", "gemini-3.0-pro"];
|
|
981
|
+
if (thinkingModel.some(
|
|
982
|
+
(name2) => name2.includes(model.name.toLowerCase())
|
|
983
|
+
)) {
|
|
928
984
|
if (!model.name.includes("-thinking")) {
|
|
929
985
|
models.push(
|
|
930
986
|
{ ...info, name: model.name + "-non-thinking" },
|
|
@@ -957,7 +1013,9 @@ var GeminiClient = class extends PlatformModelAndEmbeddingsClient {
|
|
|
957
1013
|
requester: this._requester,
|
|
958
1014
|
model,
|
|
959
1015
|
modelMaxContextSize: info.maxTokens,
|
|
960
|
-
maxTokenLimit:
|
|
1016
|
+
maxTokenLimit: Math.floor(
|
|
1017
|
+
(info.maxTokens || 1e5) * this._config.maxContextRatio
|
|
1018
|
+
),
|
|
961
1019
|
timeout: this._config.timeout,
|
|
962
1020
|
temperature: this._config.temperature,
|
|
963
1021
|
maxRetries: this._config.maxRetries,
|
|
@@ -977,11 +1035,13 @@ import { createLogger } from "koishi-plugin-chatluna/utils/logger";
|
|
|
977
1035
|
var logger;
|
|
978
1036
|
var reusable = true;
|
|
979
1037
|
function apply(ctx, config) {
|
|
980
|
-
const plugin = new ChatLunaPlugin(ctx, config, config.platform);
|
|
981
1038
|
logger = createLogger(ctx, "chatluna-gemini-adapter");
|
|
982
1039
|
ctx.on("ready", async () => {
|
|
1040
|
+
const plugin = new ChatLunaPlugin(ctx, config, config.platform);
|
|
983
1041
|
plugin.parseConfig((config2) => {
|
|
984
|
-
return config2.apiKeys.
|
|
1042
|
+
return config2.apiKeys.filter(([apiKey, _, enabled]) => {
|
|
1043
|
+
return apiKey.length > 0 && enabled;
|
|
1044
|
+
}).map(([apiKey, apiEndpoint]) => {
|
|
985
1045
|
return {
|
|
986
1046
|
apiKey,
|
|
987
1047
|
apiEndpoint,
|
|
@@ -993,8 +1053,8 @@ function apply(ctx, config) {
|
|
|
993
1053
|
};
|
|
994
1054
|
});
|
|
995
1055
|
});
|
|
996
|
-
plugin.registerClient((
|
|
997
|
-
await plugin.
|
|
1056
|
+
plugin.registerClient(() => new GeminiClient(ctx, config, plugin));
|
|
1057
|
+
await plugin.initClient();
|
|
998
1058
|
});
|
|
999
1059
|
}
|
|
1000
1060
|
__name(apply, "apply");
|
|
@@ -1004,15 +1064,16 @@ var Config4 = Schema.intersect([
|
|
|
1004
1064
|
platform: Schema.string().default("gemini"),
|
|
1005
1065
|
apiKeys: Schema.array(
|
|
1006
1066
|
Schema.tuple([
|
|
1007
|
-
Schema.string().role("secret"),
|
|
1067
|
+
Schema.string().role("secret").default(""),
|
|
1008
1068
|
Schema.string().default(
|
|
1009
1069
|
"https://generativelanguage.googleapis.com/v1beta"
|
|
1010
|
-
)
|
|
1070
|
+
),
|
|
1071
|
+
Schema.boolean().default(true)
|
|
1011
1072
|
])
|
|
1012
|
-
).default([[
|
|
1073
|
+
).default([[]]).role("table")
|
|
1013
1074
|
}),
|
|
1014
1075
|
Schema.object({
|
|
1015
|
-
|
|
1076
|
+
maxContextRatio: Schema.number().min(0).max(1).step(1e-4).role("slider").default(0.35),
|
|
1016
1077
|
temperature: Schema.percent().min(0).max(2).step(0.1).default(1),
|
|
1017
1078
|
googleSearch: Schema.boolean().default(false),
|
|
1018
1079
|
codeExecution: Schema.boolean().default(false),
|
package/lib/types.d.ts
CHANGED
|
@@ -2,11 +2,21 @@ export interface ChatCompletionResponseMessage {
|
|
|
2
2
|
role: string;
|
|
3
3
|
parts?: ChatPart[];
|
|
4
4
|
}
|
|
5
|
-
export type
|
|
5
|
+
export type BaseChatPart = {
|
|
6
|
+
thoughtSignature?: string;
|
|
7
|
+
};
|
|
8
|
+
export type ChatPart = (ChatMessagePart & BaseChatPart) | ChatInlineDataPart | (ChatFunctionCallingPart & BaseChatPart) | ChatFunctionResponsePart | ChatUploadDataPart | ChatUsageMetadataPart;
|
|
6
9
|
export type ChatMessagePart = {
|
|
7
10
|
text: string;
|
|
8
11
|
thought?: boolean;
|
|
9
12
|
};
|
|
13
|
+
export type ChatUsageMetadataPart = {
|
|
14
|
+
usage: {
|
|
15
|
+
promptTokens: number;
|
|
16
|
+
completionTokens: number;
|
|
17
|
+
totalTokens: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
10
20
|
export type ChatInlineDataPart = {
|
|
11
21
|
inlineData: {
|
|
12
22
|
mimeType: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-chatluna-google-gemini-adapter",
|
|
3
3
|
"description": "google-gemini adapter for chatluna",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.1",
|
|
5
5
|
"main": "lib/index.cjs",
|
|
6
6
|
"module": "lib/index.mjs",
|
|
7
7
|
"typings": "lib/index.d.ts",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
24
24
|
"url": "https://github.com/ChatLunaLab/chatluna.git",
|
|
25
|
-
"directory": "packages/
|
|
25
|
+
"directory": "packages/adapter-gemini"
|
|
26
26
|
},
|
|
27
27
|
"license": "AGPL-3.0",
|
|
28
28
|
"bugs": {
|
|
29
29
|
"url": "https://github.com/ChatLunaLab/chatluna/issues"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/ChatLunaLab/chatluna/tree/v1-dev/packages/
|
|
31
|
+
"homepage": "https://github.com/ChatLunaLab/chatluna/tree/v1-dev/packages/adapter-gemini#readme",
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "atsc -b"
|
|
34
34
|
},
|
|
@@ -62,19 +62,21 @@
|
|
|
62
62
|
"adapter"
|
|
63
63
|
],
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@
|
|
65
|
+
"@anatine/zod-openapi": "^2.2.8",
|
|
66
|
+
"@chatluna/v1-shared-adapter": "^1.0.16",
|
|
66
67
|
"@langchain/core": "0.3.62",
|
|
68
|
+
"openapi3-ts": "^4.5.0",
|
|
67
69
|
"zod": "3.25.76",
|
|
68
|
-
"zod-to-json-schema": "^3.24.
|
|
70
|
+
"zod-to-json-schema": "^3.24.6"
|
|
69
71
|
},
|
|
70
72
|
"devDependencies": {
|
|
71
73
|
"atsc": "^2.1.0",
|
|
72
|
-
"koishi": "^4.18.
|
|
74
|
+
"koishi": "^4.18.9"
|
|
73
75
|
},
|
|
74
76
|
"peerDependencies": {
|
|
75
|
-
"koishi": "^4.18.
|
|
76
|
-
"koishi-plugin-chatluna": "^1.3.0
|
|
77
|
-
"koishi-plugin-chatluna-storage-service": "^0.0.
|
|
77
|
+
"koishi": "^4.18.9",
|
|
78
|
+
"koishi-plugin-chatluna": "^1.3.0",
|
|
79
|
+
"koishi-plugin-chatluna-storage-service": "^0.0.11"
|
|
78
80
|
},
|
|
79
81
|
"peerDependenciesMeta": {
|
|
80
82
|
"koishi-plugin-chatluna-storage-service": {
|