modelmix 3.6.0 → 3.6.4
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/index.js +119 -92
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const {
|
|
3
|
+
const { fromBuffer } = require('file-type');
|
|
4
4
|
const log = require('lemonlog')('ModelMix');
|
|
5
5
|
const Bottleneck = require('bottleneck');
|
|
6
6
|
const path = require('path');
|
|
@@ -31,7 +31,6 @@ class ModelMix {
|
|
|
31
31
|
|
|
32
32
|
this.config = {
|
|
33
33
|
system: 'You are an assistant.',
|
|
34
|
-
systemExtra: '',
|
|
35
34
|
max_history: 1, // Default max history
|
|
36
35
|
debug: false,
|
|
37
36
|
bottleneck: defaultBottleneckConfig,
|
|
@@ -203,104 +202,133 @@ class ModelMix {
|
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
addImageFromBuffer(buffer, { role = "user" } = {}) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const imageMessage = {
|
|
215
|
-
...{ role },
|
|
216
|
-
content: [
|
|
217
|
-
{
|
|
218
|
-
type: "image",
|
|
219
|
-
"source": {
|
|
220
|
-
type: "base64",
|
|
221
|
-
media_type: fileType.mime,
|
|
222
|
-
data
|
|
223
|
-
}
|
|
205
|
+
this.messages.push({
|
|
206
|
+
role,
|
|
207
|
+
content: [{
|
|
208
|
+
type: "image",
|
|
209
|
+
source: {
|
|
210
|
+
type: "buffer",
|
|
211
|
+
data: buffer
|
|
224
212
|
}
|
|
225
|
-
]
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
this.messages.push(imageMessage);
|
|
213
|
+
}]
|
|
214
|
+
});
|
|
229
215
|
return this;
|
|
230
216
|
}
|
|
231
217
|
|
|
232
218
|
addImage(filePath, { role = "user" } = {}) {
|
|
233
|
-
|
|
234
|
-
|
|
219
|
+
this.messages.push({
|
|
220
|
+
role,
|
|
221
|
+
content: [{
|
|
222
|
+
type: "image",
|
|
223
|
+
source: {
|
|
224
|
+
type: "file",
|
|
225
|
+
data: filePath
|
|
226
|
+
}
|
|
227
|
+
}]
|
|
228
|
+
});
|
|
229
|
+
return this;
|
|
235
230
|
}
|
|
236
231
|
|
|
237
|
-
addImageFromUrl(url,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
232
|
+
addImageFromUrl(url, { role = "user" } = {}) {
|
|
233
|
+
this.messages.push({
|
|
234
|
+
role,
|
|
235
|
+
content: [{
|
|
236
|
+
type: "image",
|
|
237
|
+
source: {
|
|
238
|
+
type: "url",
|
|
239
|
+
data: url
|
|
240
|
+
}
|
|
241
|
+
}]
|
|
242
|
+
});
|
|
242
243
|
return this;
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
async
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
246
|
+
async processImages() {
|
|
247
|
+
// Process images that are in messages
|
|
248
|
+
for (let i = 0; i < this.messages.length; i++) {
|
|
249
|
+
const message = this.messages[i];
|
|
250
|
+
if (!message.content) continue;
|
|
251
|
+
|
|
252
|
+
for (let j = 0; j < message.content.length; j++) {
|
|
253
|
+
const content = message.content[j];
|
|
254
|
+
if (content.type !== 'image' || content.source.type === 'base64') continue;
|
|
255
|
+
|
|
250
256
|
try {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
let buffer, mimeType;
|
|
258
|
+
|
|
259
|
+
switch (content.source.type) {
|
|
260
|
+
case 'url':
|
|
261
|
+
const response = await axios.get(content.source.data, { responseType: 'arraybuffer' });
|
|
262
|
+
buffer = Buffer.from(response.data);
|
|
263
|
+
mimeType = response.headers['content-type'];
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case 'file':
|
|
267
|
+
buffer = this.readFile(content.source.data, { encoding: null });
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
case 'buffer':
|
|
271
|
+
buffer = content.source.data;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Detect mimeType if not provided
|
|
276
|
+
if (!mimeType) {
|
|
277
|
+
const fileType = await fromBuffer(buffer);
|
|
278
|
+
if (!fileType || !fileType.mime.startsWith('image/')) {
|
|
279
|
+
throw new Error(`Invalid image - unable to detect valid image format`);
|
|
280
|
+
}
|
|
281
|
+
mimeType = fileType.mime;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Update the content with processed image
|
|
285
|
+
message.content[j] = {
|
|
286
|
+
type: "image",
|
|
287
|
+
source: {
|
|
288
|
+
type: "base64",
|
|
289
|
+
media_type: mimeType,
|
|
290
|
+
data: buffer.toString('base64')
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
255
294
|
} catch (error) {
|
|
256
|
-
console.error(`Error
|
|
257
|
-
|
|
295
|
+
console.error(`Error processing image:`, error);
|
|
296
|
+
// Remove failed image from content
|
|
297
|
+
message.content.splice(j, 1);
|
|
298
|
+
j--;
|
|
258
299
|
}
|
|
259
|
-
})
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
imageContents.forEach((image) => {
|
|
263
|
-
if (image) {
|
|
264
|
-
const imageMessage = {
|
|
265
|
-
...image.config,
|
|
266
|
-
content: [
|
|
267
|
-
{
|
|
268
|
-
type: "image",
|
|
269
|
-
"source": {
|
|
270
|
-
type: "base64",
|
|
271
|
-
media_type: image.mimeType,
|
|
272
|
-
data: image.base64
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
]
|
|
276
|
-
};
|
|
277
|
-
this.messages.push(imageMessage);
|
|
278
300
|
}
|
|
279
|
-
}
|
|
301
|
+
}
|
|
280
302
|
}
|
|
281
303
|
|
|
282
304
|
async message() {
|
|
283
|
-
this.options
|
|
284
|
-
let raw = await this.execute();
|
|
305
|
+
let raw = await this.execute({ options: { stream: false } });
|
|
285
306
|
return raw.message;
|
|
286
307
|
}
|
|
287
308
|
|
|
288
309
|
async json(schemaExample = null, schemaDescription = {}, { type = 'json_object', addExample = false, addSchema = true } = {}) {
|
|
289
|
-
|
|
310
|
+
|
|
311
|
+
let options = {
|
|
312
|
+
response_format: { type },
|
|
313
|
+
stream: false,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let config = {
|
|
317
|
+
system: this.config.system,
|
|
318
|
+
}
|
|
290
319
|
|
|
291
320
|
if (schemaExample) {
|
|
292
|
-
|
|
321
|
+
config.schema = generateJsonSchema(schemaExample, schemaDescription);
|
|
293
322
|
|
|
294
323
|
if (addSchema) {
|
|
295
|
-
|
|
324
|
+
config.system += "\nOutput JSON Schema: \n```\n" + JSON.stringify(this.config.schema) + "\n```";
|
|
296
325
|
}
|
|
297
326
|
if (addExample) {
|
|
298
|
-
|
|
327
|
+
config.system += "\nOutput JSON Example: \n```\n" + JSON.stringify(schemaExample) + "\n```";
|
|
299
328
|
}
|
|
300
329
|
}
|
|
301
|
-
const
|
|
302
|
-
this.
|
|
303
|
-
return JSON.parse(this._extractBlock(response));
|
|
330
|
+
const { message } = await this.execute({ options, config });
|
|
331
|
+
return JSON.parse(this._extractBlock(message));
|
|
304
332
|
}
|
|
305
333
|
|
|
306
334
|
_extractBlock(response) {
|
|
@@ -309,23 +337,24 @@ class ModelMix {
|
|
|
309
337
|
}
|
|
310
338
|
|
|
311
339
|
async block({ addSystemExtra = true } = {}) {
|
|
340
|
+
let config = {
|
|
341
|
+
system: this.config.system,
|
|
342
|
+
}
|
|
343
|
+
|
|
312
344
|
if (addSystemExtra) {
|
|
313
|
-
|
|
345
|
+
config.system += "\nReturn the result of the task between triple backtick block code tags ```";
|
|
314
346
|
}
|
|
315
|
-
const
|
|
316
|
-
this.
|
|
317
|
-
return this._extractBlock(response);
|
|
347
|
+
const { message } = await this.execute({ options: { stream: false }, config });
|
|
348
|
+
return this._extractBlock(message);
|
|
318
349
|
}
|
|
319
350
|
|
|
320
351
|
async raw() {
|
|
321
|
-
this.options
|
|
322
|
-
return this.execute();
|
|
352
|
+
return this.execute({ options: { stream: false } });
|
|
323
353
|
}
|
|
324
354
|
|
|
325
355
|
async stream(callback) {
|
|
326
|
-
this.options.stream = true;
|
|
327
356
|
this.streamCallback = callback;
|
|
328
|
-
return this.execute();
|
|
357
|
+
return this.execute({ options: { stream: true } });
|
|
329
358
|
}
|
|
330
359
|
|
|
331
360
|
replaceKeyFromFile(key, filePath) {
|
|
@@ -376,7 +405,7 @@ class ModelMix {
|
|
|
376
405
|
}
|
|
377
406
|
|
|
378
407
|
async prepareMessages() {
|
|
379
|
-
await this.
|
|
408
|
+
await this.processImages();
|
|
380
409
|
this.applyTemplate();
|
|
381
410
|
this.messages = this.messages.slice(-this.config.max_history);
|
|
382
411
|
this.messages = this.groupByRoles(this.messages);
|
|
@@ -398,9 +427,9 @@ class ModelMix {
|
|
|
398
427
|
}
|
|
399
428
|
}
|
|
400
429
|
|
|
401
|
-
async execute() {
|
|
430
|
+
async execute({ config = {}, options = {} } = {}) {
|
|
402
431
|
if (!this.models || this.models.length === 0) {
|
|
403
|
-
throw new Error("No models specified. Use methods like .
|
|
432
|
+
throw new Error("No models specified. Use methods like .gpt41mini(), .sonnet4() first.");
|
|
404
433
|
}
|
|
405
434
|
|
|
406
435
|
return this.limiter.schedule(async () => {
|
|
@@ -419,16 +448,18 @@ class ModelMix {
|
|
|
419
448
|
const providerInstance = currentModel.provider;
|
|
420
449
|
const optionsTools = providerInstance.getOptionsTools(this.tools);
|
|
421
450
|
|
|
422
|
-
|
|
451
|
+
options = {
|
|
423
452
|
...this.options,
|
|
424
453
|
...providerInstance.options,
|
|
425
454
|
...optionsTools,
|
|
455
|
+
...options,
|
|
426
456
|
model: currentModelKey
|
|
427
457
|
};
|
|
428
458
|
|
|
429
|
-
|
|
459
|
+
config = {
|
|
430
460
|
...this.config,
|
|
431
461
|
...providerInstance.config,
|
|
462
|
+
...config,
|
|
432
463
|
};
|
|
433
464
|
|
|
434
465
|
if (config.debug) {
|
|
@@ -761,7 +792,7 @@ class MixOpenAI extends MixCustom {
|
|
|
761
792
|
|
|
762
793
|
static convertMessages(messages, config) {
|
|
763
794
|
|
|
764
|
-
const content = config.system
|
|
795
|
+
const content = config.system;
|
|
765
796
|
messages = [{ role: 'system', content }, ...messages || []];
|
|
766
797
|
|
|
767
798
|
const results = []
|
|
@@ -851,7 +882,7 @@ class MixAnthropic extends MixCustom {
|
|
|
851
882
|
|
|
852
883
|
delete options.response_format;
|
|
853
884
|
|
|
854
|
-
options.system = config.system
|
|
885
|
+
options.system = config.system;
|
|
855
886
|
return super.create({ config, options });
|
|
856
887
|
}
|
|
857
888
|
|
|
@@ -982,16 +1013,12 @@ class MixPerplexity extends MixCustom {
|
|
|
982
1013
|
async create({ config = {}, options = {} } = {}) {
|
|
983
1014
|
|
|
984
1015
|
if (config.schema) {
|
|
985
|
-
config.systemExtra = '';
|
|
986
|
-
|
|
987
1016
|
options.response_format = {
|
|
988
1017
|
type: 'json_schema',
|
|
989
1018
|
json_schema: { schema: config.schema }
|
|
990
1019
|
};
|
|
991
1020
|
}
|
|
992
1021
|
|
|
993
|
-
const content = config.system + config.systemExtra;
|
|
994
|
-
options.messages = [{ role: 'system', content }, ...options.messages || []];
|
|
995
1022
|
return super.create({ config, options });
|
|
996
1023
|
}
|
|
997
1024
|
}
|
|
@@ -1025,7 +1052,7 @@ class MixOllama extends MixCustom {
|
|
|
1025
1052
|
}
|
|
1026
1053
|
|
|
1027
1054
|
static convertMessages(messages, config) {
|
|
1028
|
-
const content = config.system
|
|
1055
|
+
const content = config.system;
|
|
1029
1056
|
messages = [{ role: 'system', content }, ...messages || []];
|
|
1030
1057
|
|
|
1031
1058
|
return messages.map(entry => {
|
|
@@ -1216,7 +1243,7 @@ class MixGoogle extends MixCustom {
|
|
|
1216
1243
|
const fullUrl = `${this.config.url}/${options.model}:${generateContentApi}?key=${this.config.apiKey}`;
|
|
1217
1244
|
|
|
1218
1245
|
|
|
1219
|
-
const content = config.system
|
|
1246
|
+
const content = config.system;
|
|
1220
1247
|
const systemInstruction = { parts: [{ text: content }] };
|
|
1221
1248
|
|
|
1222
1249
|
options.messages = MixGoogle.convertMessages(options.messages);
|