@lukaplayground/aikit 1.0.1 → 1.1.0
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 +0 -13
- package/dist/aikit.js +108 -570
- package/dist/aikit.js.map +1 -1
- package/dist/aikit.min.js +1 -1
- package/dist/aikit.min.js.map +1 -1
- package/dist/aikit.umd.js +108 -570
- package/dist/aikit.umd.js.map +1 -1
- package/package.json +9 -7
package/dist/aikit.umd.js
CHANGED
|
@@ -892,187 +892,58 @@
|
|
|
892
892
|
constructor() {
|
|
893
893
|
super();
|
|
894
894
|
this.baseURL = 'https://api.openai.com/v1';
|
|
895
|
-
this.defaultModel = 'gpt-3.5-turbo
|
|
895
|
+
this.defaultModel = 'gpt-4o-mini'; // ← 변경: gpt-3.5-turbo에서
|
|
896
|
+
|
|
897
|
+
// 사용 가능한 모델 목록
|
|
898
|
+
this.availableModels = [
|
|
899
|
+
'gpt-4o',
|
|
900
|
+
'gpt-4o-mini',
|
|
901
|
+
'gpt-4-turbo',
|
|
902
|
+
'gpt-3.5-turbo'
|
|
903
|
+
];
|
|
896
904
|
}
|
|
897
905
|
|
|
898
906
|
/**
|
|
899
907
|
* Send chat request to OpenAI
|
|
900
908
|
* @override
|
|
901
909
|
*/
|
|
902
|
-
async chat(
|
|
903
|
-
this.
|
|
904
|
-
|
|
905
|
-
const { message, apiKey, options = {} } = request;
|
|
906
|
-
|
|
907
|
-
const requestBody = {
|
|
908
|
-
model: options.model || this.defaultModel,
|
|
909
|
-
messages: this.buildMessages(message, options),
|
|
910
|
-
temperature: options.temperature ?? 0.7,
|
|
911
|
-
max_tokens: options.maxTokens || options.max_tokens,
|
|
912
|
-
top_p: options.topP || options.top_p,
|
|
913
|
-
frequency_penalty: options.frequencyPenalty || options.frequency_penalty,
|
|
914
|
-
presence_penalty: options.presencePenalty || options.presence_penalty,
|
|
915
|
-
stream: false
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
// Remove undefined values
|
|
919
|
-
Object.keys(requestBody).forEach(key => {
|
|
920
|
-
if (requestBody[key] === undefined) {
|
|
921
|
-
delete requestBody[key];
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
const fetchOptions = {
|
|
926
|
-
method: 'POST',
|
|
927
|
-
headers: this.buildHeaders(apiKey),
|
|
928
|
-
body: JSON.stringify(requestBody)
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
const rawResponse = await this.withRetry(() =>
|
|
932
|
-
this.makeRequest(`${this.baseURL}/chat/completions`, fetchOptions)
|
|
933
|
-
);
|
|
934
|
-
|
|
935
|
-
return this.normalizeResponse(rawResponse);
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
/**
|
|
939
|
-
* Build messages array from user message
|
|
940
|
-
* @private
|
|
941
|
-
*/
|
|
942
|
-
buildMessages(message, options) {
|
|
943
|
-
const messages = [];
|
|
944
|
-
|
|
945
|
-
// Add system message if provided
|
|
946
|
-
if (options.systemMessage) {
|
|
947
|
-
messages.push({
|
|
948
|
-
role: 'system',
|
|
949
|
-
content: options.systemMessage
|
|
950
|
-
});
|
|
951
|
-
}
|
|
910
|
+
async chat(message, options = {}) {
|
|
911
|
+
const model = options.model || this.defaultModel;
|
|
912
|
+
const apiKey = options.apiKey || this.apiKey;
|
|
952
913
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
messages.push(...options.history);
|
|
914
|
+
if (!apiKey) {
|
|
915
|
+
throw new Error('OpenAI API key is required');
|
|
956
916
|
}
|
|
957
917
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
918
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
919
|
+
method: 'POST',
|
|
920
|
+
headers: {
|
|
921
|
+
'Content-Type': 'application/json',
|
|
922
|
+
'Authorization': `Bearer ${apiKey}`
|
|
923
|
+
},
|
|
924
|
+
body: JSON.stringify({
|
|
925
|
+
model: model,
|
|
926
|
+
messages: [
|
|
927
|
+
{
|
|
928
|
+
role: 'user',
|
|
929
|
+
content: message
|
|
930
|
+
}
|
|
931
|
+
],
|
|
932
|
+
...options
|
|
933
|
+
})
|
|
962
934
|
});
|
|
963
935
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* Extract text from OpenAI response
|
|
969
|
-
* @override
|
|
970
|
-
*/
|
|
971
|
-
extractText(rawResponse) {
|
|
972
|
-
if (!rawResponse.choices || rawResponse.choices.length === 0) {
|
|
973
|
-
throw new Error('Invalid OpenAI response: no choices returned');
|
|
936
|
+
if (!response.ok) {
|
|
937
|
+
const error = await response.text();
|
|
938
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
974
939
|
}
|
|
975
940
|
|
|
976
|
-
const
|
|
977
|
-
return firstChoice.message?.content || '';
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
/**
|
|
981
|
-
* Extract usage information
|
|
982
|
-
* @override
|
|
983
|
-
*/
|
|
984
|
-
extractUsage(rawResponse) {
|
|
985
|
-
const usage = rawResponse.usage || {};
|
|
986
|
-
|
|
987
|
-
return {
|
|
988
|
-
promptTokens: usage.prompt_tokens || 0,
|
|
989
|
-
completionTokens: usage.completion_tokens || 0,
|
|
990
|
-
totalTokens: usage.total_tokens || 0,
|
|
991
|
-
// Normalized names for compatibility
|
|
992
|
-
input: usage.prompt_tokens || 0,
|
|
993
|
-
output: usage.completion_tokens || 0,
|
|
994
|
-
total: usage.total_tokens || 0
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
/**
|
|
999
|
-
* Extract model name
|
|
1000
|
-
* @override
|
|
1001
|
-
*/
|
|
1002
|
-
extractModel(rawResponse) {
|
|
1003
|
-
return rawResponse.model || this.defaultModel;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Extract finish reason
|
|
1008
|
-
* @override
|
|
1009
|
-
*/
|
|
1010
|
-
extractFinishReason(rawResponse) {
|
|
1011
|
-
if (!rawResponse.choices || rawResponse.choices.length === 0) {
|
|
1012
|
-
return 'unknown';
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
return rawResponse.choices[0].finish_reason || 'unknown';
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Stream chat (for future implementation)
|
|
1020
|
-
* @param {Object} request - Request object
|
|
1021
|
-
* @param {Function} onChunk - Callback for each chunk
|
|
1022
|
-
*/
|
|
1023
|
-
async streamChat(request, onChunk) {
|
|
1024
|
-
// TODO: Implement streaming support
|
|
1025
|
-
throw new Error('Streaming is not yet implemented for OpenAI adapter');
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
/**
|
|
1029
|
-
* List available models
|
|
1030
|
-
* @param {string} apiKey - API key
|
|
1031
|
-
*/
|
|
1032
|
-
async listModels(apiKey) {
|
|
1033
|
-
const fetchOptions = {
|
|
1034
|
-
method: 'GET',
|
|
1035
|
-
headers: this.buildHeaders(apiKey)
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
const response = await this.makeRequest(`${this.baseURL}/models`, fetchOptions);
|
|
1039
|
-
|
|
1040
|
-
// Filter to chat models only
|
|
1041
|
-
const chatModels = response.data.filter(model =>
|
|
1042
|
-
model.id.includes('gpt') || model.id.includes('turbo')
|
|
1043
|
-
);
|
|
1044
|
-
|
|
1045
|
-
return chatModels.map(model => ({
|
|
1046
|
-
id: model.id,
|
|
1047
|
-
created: model.created,
|
|
1048
|
-
ownedBy: model.owned_by
|
|
1049
|
-
}));
|
|
1050
|
-
}
|
|
941
|
+
const data = await response.json();
|
|
1051
942
|
|
|
1052
|
-
/**
|
|
1053
|
-
* Generate embeddings
|
|
1054
|
-
* @param {string} text - Text to embed
|
|
1055
|
-
* @param {string} apiKey - API key
|
|
1056
|
-
* @param {Object} options - Options
|
|
1057
|
-
*/
|
|
1058
|
-
async createEmbedding(text, apiKey, options = {}) {
|
|
1059
|
-
const requestBody = {
|
|
1060
|
-
model: options.model || 'text-embedding-ada-002',
|
|
1061
|
-
input: text
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
const fetchOptions = {
|
|
1065
|
-
method: 'POST',
|
|
1066
|
-
headers: this.buildHeaders(apiKey),
|
|
1067
|
-
body: JSON.stringify(requestBody)
|
|
1068
|
-
};
|
|
1069
|
-
|
|
1070
|
-
const response = await this.makeRequest(`${this.baseURL}/embeddings`, fetchOptions);
|
|
1071
|
-
|
|
1072
943
|
return {
|
|
1073
|
-
|
|
1074
|
-
model:
|
|
1075
|
-
usage:
|
|
944
|
+
content: data.choices[0].message.content,
|
|
945
|
+
model: data.model,
|
|
946
|
+
usage: data.usage
|
|
1076
947
|
};
|
|
1077
948
|
}
|
|
1078
949
|
}
|
|
@@ -1087,192 +958,74 @@
|
|
|
1087
958
|
constructor() {
|
|
1088
959
|
super();
|
|
1089
960
|
this.baseURL = 'https://api.anthropic.com/v1';
|
|
1090
|
-
this.defaultModel = 'claude-3-sonnet-
|
|
961
|
+
this.defaultModel = 'claude-3-5-sonnet-20241022'; // ← 변경
|
|
1091
962
|
this.apiVersion = '2023-06-01';
|
|
963
|
+
|
|
964
|
+
// 사용 가능한 모델 목록
|
|
965
|
+
this.availableModels = [
|
|
966
|
+
'claude-3-5-sonnet-20241022',
|
|
967
|
+
'claude-3-5-haiku-20241022',
|
|
968
|
+
'claude-3-opus-20240229',
|
|
969
|
+
'claude-3-sonnet-20240229',
|
|
970
|
+
'claude-3-haiku-20240307'
|
|
971
|
+
];
|
|
1092
972
|
}
|
|
1093
973
|
|
|
1094
974
|
/**
|
|
1095
975
|
* Send chat request to Claude
|
|
1096
976
|
* @override
|
|
1097
977
|
*/
|
|
1098
|
-
async chat(
|
|
1099
|
-
this.
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
const requestBody = {
|
|
1104
|
-
model: options.model || this.defaultModel,
|
|
1105
|
-
messages: this.buildMessages(message, options),
|
|
1106
|
-
max_tokens: options.maxTokens || options.max_tokens || 1024,
|
|
1107
|
-
temperature: options.temperature ?? 1.0,
|
|
1108
|
-
top_p: options.topP || options.top_p,
|
|
1109
|
-
top_k: options.topK || options.top_k,
|
|
1110
|
-
stream: false
|
|
1111
|
-
};
|
|
978
|
+
async chat(message, options = {}) {
|
|
979
|
+
const model = options.model || this.defaultModel;
|
|
980
|
+
const apiKey = options.apiKey || this.apiKey;
|
|
981
|
+
const maxTokens = options.maxTokens || 1024;
|
|
1112
982
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
requestBody.system = options.systemMessage;
|
|
983
|
+
if (!apiKey) {
|
|
984
|
+
throw new Error('Claude API key is required');
|
|
1116
985
|
}
|
|
1117
986
|
|
|
1118
|
-
|
|
1119
|
-
Object.keys(requestBody).forEach(key => {
|
|
1120
|
-
if (requestBody[key] === undefined) {
|
|
1121
|
-
delete requestBody[key];
|
|
1122
|
-
}
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
|
-
const fetchOptions = {
|
|
987
|
+
const response = await fetch(`${this.baseURL}/messages`, {
|
|
1126
988
|
method: 'POST',
|
|
1127
|
-
headers:
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
buildHeaders(apiKey) {
|
|
1143
|
-
return {
|
|
1144
|
-
'Content-Type': 'application/json',
|
|
1145
|
-
'x-api-key': apiKey,
|
|
1146
|
-
'anthropic-version': this.apiVersion
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
/**
|
|
1151
|
-
* Build messages array from user message
|
|
1152
|
-
* @private
|
|
1153
|
-
*/
|
|
1154
|
-
buildMessages(message, options) {
|
|
1155
|
-
const messages = [];
|
|
1156
|
-
|
|
1157
|
-
// Add conversation history if provided
|
|
1158
|
-
if (options.history && Array.isArray(options.history)) {
|
|
1159
|
-
// Claude uses 'user' and 'assistant' roles
|
|
1160
|
-
messages.push(...options.history.map(msg => ({
|
|
1161
|
-
role: msg.role === 'system' ? 'user' : msg.role,
|
|
1162
|
-
content: msg.content
|
|
1163
|
-
})));
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
// Add current user message
|
|
1167
|
-
messages.push({
|
|
1168
|
-
role: 'user',
|
|
1169
|
-
content: message
|
|
989
|
+
headers: {
|
|
990
|
+
'Content-Type': 'application/json',
|
|
991
|
+
'x-api-key': apiKey,
|
|
992
|
+
'anthropic-version': this.apiVersion
|
|
993
|
+
},
|
|
994
|
+
body: JSON.stringify({
|
|
995
|
+
model: model,
|
|
996
|
+
max_tokens: maxTokens,
|
|
997
|
+
messages: [
|
|
998
|
+
{
|
|
999
|
+
role: 'user',
|
|
1000
|
+
content: message
|
|
1001
|
+
}
|
|
1002
|
+
]
|
|
1003
|
+
})
|
|
1170
1004
|
});
|
|
1171
1005
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
/**
|
|
1176
|
-
* Extract text from Claude response
|
|
1177
|
-
* @override
|
|
1178
|
-
*/
|
|
1179
|
-
extractText(rawResponse) {
|
|
1180
|
-
if (!rawResponse.content || rawResponse.content.length === 0) {
|
|
1181
|
-
throw new Error('Invalid Claude response: no content returned');
|
|
1006
|
+
if (!response.ok) {
|
|
1007
|
+
const error = await response.text();
|
|
1008
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
1182
1009
|
}
|
|
1183
1010
|
|
|
1184
|
-
|
|
1185
|
-
const textBlocks = rawResponse.content
|
|
1186
|
-
.filter(block => block.type === 'text')
|
|
1187
|
-
.map(block => block.text);
|
|
1188
|
-
|
|
1189
|
-
return textBlocks.join('\n');
|
|
1190
|
-
}
|
|
1011
|
+
const data = await response.json();
|
|
1191
1012
|
|
|
1192
|
-
/**
|
|
1193
|
-
* Extract usage information
|
|
1194
|
-
* @override
|
|
1195
|
-
*/
|
|
1196
|
-
extractUsage(rawResponse) {
|
|
1197
|
-
const usage = rawResponse.usage || {};
|
|
1198
|
-
|
|
1199
1013
|
return {
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
output: usage.output_tokens || 0,
|
|
1206
|
-
total: (usage.input_tokens || 0) + (usage.output_tokens || 0)
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
/**
|
|
1211
|
-
* Extract model name
|
|
1212
|
-
* @override
|
|
1213
|
-
*/
|
|
1214
|
-
extractModel(rawResponse) {
|
|
1215
|
-
return rawResponse.model || this.defaultModel;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* Extract finish reason
|
|
1220
|
-
* @override
|
|
1221
|
-
*/
|
|
1222
|
-
extractFinishReason(rawResponse) {
|
|
1223
|
-
return rawResponse.stop_reason || 'unknown';
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/**
|
|
1227
|
-
* Stream chat (for future implementation)
|
|
1228
|
-
* @param {Object} request - Request object
|
|
1229
|
-
* @param {Function} onChunk - Callback for each chunk
|
|
1230
|
-
*/
|
|
1231
|
-
async streamChat(request, onChunk) {
|
|
1232
|
-
// TODO: Implement streaming support
|
|
1233
|
-
throw new Error('Streaming is not yet implemented for Claude adapter');
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/**
|
|
1237
|
-
* Get model information
|
|
1238
|
-
*/
|
|
1239
|
-
getAvailableModels() {
|
|
1240
|
-
return [
|
|
1241
|
-
{
|
|
1242
|
-
id: 'claude-3-opus-20240229',
|
|
1243
|
-
name: 'Claude 3 Opus',
|
|
1244
|
-
description: 'Most powerful model, best for complex tasks',
|
|
1245
|
-
maxTokens: 4096
|
|
1246
|
-
},
|
|
1247
|
-
{
|
|
1248
|
-
id: 'claude-3-sonnet-20240229',
|
|
1249
|
-
name: 'Claude 3 Sonnet',
|
|
1250
|
-
description: 'Balanced performance and speed',
|
|
1251
|
-
maxTokens: 4096
|
|
1252
|
-
},
|
|
1253
|
-
{
|
|
1254
|
-
id: 'claude-3-haiku-20240307',
|
|
1255
|
-
name: 'Claude 3 Haiku',
|
|
1256
|
-
description: 'Fastest model, best for simple tasks',
|
|
1257
|
-
maxTokens: 4096
|
|
1258
|
-
},
|
|
1259
|
-
{
|
|
1260
|
-
id: 'claude-2.1',
|
|
1261
|
-
name: 'Claude 2.1',
|
|
1262
|
-
description: 'Previous generation model',
|
|
1263
|
-
maxTokens: 4096
|
|
1014
|
+
content: data.content[0].text,
|
|
1015
|
+
model: data.model,
|
|
1016
|
+
usage: {
|
|
1017
|
+
input_tokens: data.usage.input_tokens,
|
|
1018
|
+
output_tokens: data.usage.output_tokens
|
|
1264
1019
|
}
|
|
1265
|
-
|
|
1020
|
+
};
|
|
1266
1021
|
}
|
|
1267
1022
|
|
|
1268
1023
|
/**
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
* This is a rough estimate
|
|
1024
|
+
* Estimate token count (Claude uses characters)
|
|
1025
|
+
* @private
|
|
1272
1026
|
*/
|
|
1273
1027
|
estimateTokens(text) {
|
|
1274
|
-
//
|
|
1275
|
-
// ~2 characters per token for code
|
|
1028
|
+
// Claude roughly: 1 token ≈ 4 characters for English
|
|
1276
1029
|
const avgCharsPerToken = text.match(/[a-zA-Z0-9_]/g) ? 4 : 3;
|
|
1277
1030
|
return Math.ceil(text.length / avgCharsPerToken);
|
|
1278
1031
|
}
|
|
@@ -1288,269 +1041,54 @@
|
|
|
1288
1041
|
constructor() {
|
|
1289
1042
|
super();
|
|
1290
1043
|
this.baseURL = 'https://generativelanguage.googleapis.com/v1beta';
|
|
1291
|
-
this.defaultModel = 'gemini-
|
|
1044
|
+
this.defaultModel = 'gemini-1.5-flash'; // ← 변경
|
|
1045
|
+
|
|
1046
|
+
// 사용 가능한 모델 목록
|
|
1047
|
+
this.availableModels = [
|
|
1048
|
+
'gemini-1.5-flash',
|
|
1049
|
+
'gemini-1.5-flash-8b',
|
|
1050
|
+
'gemini-1.5-pro',
|
|
1051
|
+
'gemini-2.0-flash-exp'
|
|
1052
|
+
];
|
|
1292
1053
|
}
|
|
1293
1054
|
|
|
1294
1055
|
/**
|
|
1295
1056
|
* Send chat request to Gemini
|
|
1296
1057
|
* @override
|
|
1297
1058
|
*/
|
|
1298
|
-
async chat(
|
|
1299
|
-
this.validateRequest(request);
|
|
1300
|
-
|
|
1301
|
-
const { message, apiKey, options = {} } = request;
|
|
1059
|
+
async chat(message, options = {}) {
|
|
1302
1060
|
const model = options.model || this.defaultModel;
|
|
1061
|
+
const apiKey = options.apiKey || this.apiKey;
|
|
1303
1062
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
generationConfig: {
|
|
1307
|
-
temperature: options.temperature ?? 0.9,
|
|
1308
|
-
topK: options.topK || options.top_k,
|
|
1309
|
-
topP: (options.topP || options.top_p) ?? 1,
|
|
1310
|
-
maxOutputTokens: options.maxTokens || options.max_tokens || options.maxOutputTokens,
|
|
1311
|
-
stopSequences: options.stopSequences || options.stop
|
|
1312
|
-
}
|
|
1313
|
-
};
|
|
1314
|
-
|
|
1315
|
-
// Add safety settings if provided
|
|
1316
|
-
if (options.safetySettings) {
|
|
1317
|
-
requestBody.safetySettings = options.safetySettings;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
// Remove undefined values
|
|
1321
|
-
if (requestBody.generationConfig) {
|
|
1322
|
-
Object.keys(requestBody.generationConfig).forEach(key => {
|
|
1323
|
-
if (requestBody.generationConfig[key] === undefined) {
|
|
1324
|
-
delete requestBody.generationConfig[key];
|
|
1325
|
-
}
|
|
1326
|
-
});
|
|
1063
|
+
if (!apiKey) {
|
|
1064
|
+
throw new Error('Gemini API key is required');
|
|
1327
1065
|
}
|
|
1328
1066
|
|
|
1329
1067
|
const url = `${this.baseURL}/models/${model}:generateContent?key=${apiKey}`;
|
|
1330
1068
|
|
|
1331
|
-
const
|
|
1069
|
+
const response = await fetch(url, {
|
|
1332
1070
|
method: 'POST',
|
|
1333
1071
|
headers: {
|
|
1334
1072
|
'Content-Type': 'application/json'
|
|
1335
1073
|
},
|
|
1336
|
-
body: JSON.stringify(
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
return this.normalizeResponse(rawResponse);
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
/**
|
|
1347
|
-
* Build contents array from user message
|
|
1348
|
-
* @private
|
|
1349
|
-
*/
|
|
1350
|
-
buildContents(message, options) {
|
|
1351
|
-
const contents = [];
|
|
1352
|
-
|
|
1353
|
-
// Add conversation history if provided
|
|
1354
|
-
if (options.history && Array.isArray(options.history)) {
|
|
1355
|
-
options.history.forEach(msg => {
|
|
1356
|
-
contents.push({
|
|
1357
|
-
role: msg.role === 'assistant' ? 'model' : 'user',
|
|
1358
|
-
parts: [{ text: msg.content }]
|
|
1359
|
-
});
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// Add current user message
|
|
1364
|
-
contents.push({
|
|
1365
|
-
role: 'user',
|
|
1366
|
-
parts: [{ text: message }]
|
|
1074
|
+
body: JSON.stringify({
|
|
1075
|
+
contents: [{
|
|
1076
|
+
parts: [{
|
|
1077
|
+
text: message
|
|
1078
|
+
}]
|
|
1079
|
+
}]
|
|
1080
|
+
})
|
|
1367
1081
|
});
|
|
1368
1082
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
/**
|
|
1373
|
-
* Extract text from Gemini response
|
|
1374
|
-
* @override
|
|
1375
|
-
*/
|
|
1376
|
-
extractText(rawResponse) {
|
|
1377
|
-
if (!rawResponse.candidates || rawResponse.candidates.length === 0) {
|
|
1378
|
-
throw new Error('Invalid Gemini response: no candidates returned');
|
|
1083
|
+
if (!response.ok) {
|
|
1084
|
+
const error = await response.text();
|
|
1085
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
1379
1086
|
}
|
|
1380
1087
|
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
if (!candidate.content || !candidate.content.parts) {
|
|
1384
|
-
throw new Error('Invalid Gemini response: no content parts');
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
// Combine all text parts
|
|
1388
|
-
const textParts = candidate.content.parts
|
|
1389
|
-
.filter(part => part.text)
|
|
1390
|
-
.map(part => part.text);
|
|
1391
|
-
|
|
1392
|
-
return textParts.join('\n');
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
/**
|
|
1396
|
-
* Extract usage information
|
|
1397
|
-
* @override
|
|
1398
|
-
*/
|
|
1399
|
-
extractUsage(rawResponse) {
|
|
1400
|
-
const metadata = rawResponse.usageMetadata || {};
|
|
1401
|
-
|
|
1402
|
-
return {
|
|
1403
|
-
promptTokens: metadata.promptTokenCount || 0,
|
|
1404
|
-
completionTokens: metadata.candidatesTokenCount || 0,
|
|
1405
|
-
totalTokens: metadata.totalTokenCount || 0,
|
|
1406
|
-
// Normalized names for compatibility
|
|
1407
|
-
input: metadata.promptTokenCount || 0,
|
|
1408
|
-
output: metadata.candidatesTokenCount || 0,
|
|
1409
|
-
total: metadata.totalTokenCount || 0
|
|
1410
|
-
};
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
/**
|
|
1414
|
-
* Extract model name
|
|
1415
|
-
* @override
|
|
1416
|
-
*/
|
|
1417
|
-
extractModel(rawResponse) {
|
|
1418
|
-
return rawResponse.modelVersion || this.defaultModel;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
/**
|
|
1422
|
-
* Extract finish reason
|
|
1423
|
-
* @override
|
|
1424
|
-
*/
|
|
1425
|
-
extractFinishReason(rawResponse) {
|
|
1426
|
-
if (!rawResponse.candidates || rawResponse.candidates.length === 0) {
|
|
1427
|
-
return 'unknown';
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
return rawResponse.candidates[0].finishReason || 'STOP';
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
/**
|
|
1434
|
-
* Stream chat (for future implementation)
|
|
1435
|
-
* @param {Object} request - Request object
|
|
1436
|
-
* @param {Function} onChunk - Callback for each chunk
|
|
1437
|
-
*/
|
|
1438
|
-
async streamChat(request, onChunk) {
|
|
1439
|
-
// TODO: Implement streaming support
|
|
1440
|
-
throw new Error('Streaming is not yet implemented for Gemini adapter');
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
/**
|
|
1444
|
-
* Get available models
|
|
1445
|
-
*/
|
|
1446
|
-
getAvailableModels() {
|
|
1447
|
-
return [
|
|
1448
|
-
{
|
|
1449
|
-
id: 'gemini-pro',
|
|
1450
|
-
name: 'Gemini Pro',
|
|
1451
|
-
description: 'Best for text generation',
|
|
1452
|
-
maxTokens: 8192
|
|
1453
|
-
},
|
|
1454
|
-
{
|
|
1455
|
-
id: 'gemini-pro-vision',
|
|
1456
|
-
name: 'Gemini Pro Vision',
|
|
1457
|
-
description: 'Multimodal model for text and images',
|
|
1458
|
-
maxTokens: 4096
|
|
1459
|
-
},
|
|
1460
|
-
{
|
|
1461
|
-
id: 'gemini-ultra',
|
|
1462
|
-
name: 'Gemini Ultra',
|
|
1463
|
-
description: 'Most capable model (limited access)',
|
|
1464
|
-
maxTokens: 8192
|
|
1465
|
-
}
|
|
1466
|
-
];
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
/**
|
|
1470
|
-
* List models via API
|
|
1471
|
-
* @param {string} apiKey - API key
|
|
1472
|
-
*/
|
|
1473
|
-
async listModels(apiKey) {
|
|
1474
|
-
const url = `${this.baseURL}/models?key=${apiKey}`;
|
|
1475
|
-
|
|
1476
|
-
const fetchOptions = {
|
|
1477
|
-
method: 'GET',
|
|
1478
|
-
headers: {
|
|
1479
|
-
'Content-Type': 'application/json'
|
|
1480
|
-
}
|
|
1481
|
-
};
|
|
1482
|
-
|
|
1483
|
-
const response = await this.makeRequest(url, fetchOptions);
|
|
1484
|
-
|
|
1485
|
-
return response.models
|
|
1486
|
-
.filter(model => model.supportedGenerationMethods?.includes('generateContent'))
|
|
1487
|
-
.map(model => ({
|
|
1488
|
-
id: model.name.replace('models/', ''),
|
|
1489
|
-
displayName: model.displayName,
|
|
1490
|
-
description: model.description,
|
|
1491
|
-
inputTokenLimit: model.inputTokenLimit,
|
|
1492
|
-
outputTokenLimit: model.outputTokenLimit
|
|
1493
|
-
}));
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
/**
|
|
1497
|
-
* Count tokens
|
|
1498
|
-
* @param {string} text - Text to count tokens for
|
|
1499
|
-
* @param {string} apiKey - API key
|
|
1500
|
-
* @param {string} model - Model name
|
|
1501
|
-
*/
|
|
1502
|
-
async countTokens(text, apiKey, model = this.defaultModel) {
|
|
1503
|
-
const url = `${this.baseURL}/models/${model}:countTokens?key=${apiKey}`;
|
|
1504
|
-
|
|
1505
|
-
const requestBody = {
|
|
1506
|
-
contents: [{
|
|
1507
|
-
role: 'user',
|
|
1508
|
-
parts: [{ text }]
|
|
1509
|
-
}]
|
|
1510
|
-
};
|
|
1511
|
-
|
|
1512
|
-
const fetchOptions = {
|
|
1513
|
-
method: 'POST',
|
|
1514
|
-
headers: {
|
|
1515
|
-
'Content-Type': 'application/json'
|
|
1516
|
-
},
|
|
1517
|
-
body: JSON.stringify(requestBody)
|
|
1518
|
-
};
|
|
1519
|
-
|
|
1520
|
-
const response = await this.makeRequest(url, fetchOptions);
|
|
1521
|
-
|
|
1522
|
-
return {
|
|
1523
|
-
totalTokens: response.totalTokens || 0
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
/**
|
|
1528
|
-
* Generate embeddings
|
|
1529
|
-
* @param {string} text - Text to embed
|
|
1530
|
-
* @param {string} apiKey - API key
|
|
1531
|
-
*/
|
|
1532
|
-
async createEmbedding(text, apiKey) {
|
|
1533
|
-
const model = 'embedding-001';
|
|
1534
|
-
const url = `${this.baseURL}/models/${model}:embedContent?key=${apiKey}`;
|
|
1535
|
-
|
|
1536
|
-
const requestBody = {
|
|
1537
|
-
content: {
|
|
1538
|
-
parts: [{ text }]
|
|
1539
|
-
}
|
|
1540
|
-
};
|
|
1541
|
-
|
|
1542
|
-
const fetchOptions = {
|
|
1543
|
-
method: 'POST',
|
|
1544
|
-
headers: {
|
|
1545
|
-
'Content-Type': 'application/json'
|
|
1546
|
-
},
|
|
1547
|
-
body: JSON.stringify(requestBody)
|
|
1548
|
-
};
|
|
1549
|
-
|
|
1550
|
-
const response = await this.makeRequest(url, fetchOptions);
|
|
1088
|
+
const data = await response.json();
|
|
1551
1089
|
|
|
1552
1090
|
return {
|
|
1553
|
-
|
|
1091
|
+
content: data.candidates[0].content.parts[0].text,
|
|
1554
1092
|
model: model
|
|
1555
1093
|
};
|
|
1556
1094
|
}
|