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.
Files changed (2) hide show
  1. package/index.js +119 -92
  2. 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 { fileTypeFromBuffer } = require('file-type');
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
- const fileType = fileTypeFromBuffer(buffer);
208
- if (!fileType || !fileType.mime.startsWith('image/')) {
209
- throw new Error('Invalid image buffer - unable to detect valid image format');
210
- }
211
-
212
- const data = buffer.toString('base64');
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
- const imageBuffer = this.readFile(filePath, { encoding: null });
234
- return this.addImageFromBuffer(imageBuffer, { role });
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, config = { role: "user" }) {
238
- if (!this.imagesToProcess) {
239
- this.imagesToProcess = [];
240
- }
241
- this.imagesToProcess.push({ url, config });
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 processImageUrls() {
246
- if (!this.imagesToProcess) return;
247
-
248
- const imageContents = await Promise.all(
249
- this.imagesToProcess.map(async (image) => {
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
- const response = await axios.get(image.url, { responseType: 'arraybuffer' });
252
- const base64 = Buffer.from(response.data, 'binary').toString('base64');
253
- const mimeType = response.headers['content-type'];
254
- return { base64, mimeType, config: image.config };
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 downloading image from ${image.url}:`, error);
257
- return null;
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.stream = false;
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
- this.options.response_format = { type };
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
- this.config.schema = generateJsonSchema(schemaExample, schemaDescription);
321
+ config.schema = generateJsonSchema(schemaExample, schemaDescription);
293
322
 
294
323
  if (addSchema) {
295
- this.config.systemExtra = "\nOutput JSON Schema: \n```\n" + JSON.stringify(this.config.schema) + "\n```";
324
+ config.system += "\nOutput JSON Schema: \n```\n" + JSON.stringify(this.config.schema) + "\n```";
296
325
  }
297
326
  if (addExample) {
298
- this.config.systemExtra += "\nOutput JSON Example: \n```\n" + JSON.stringify(schemaExample) + "\n```";
327
+ config.system += "\nOutput JSON Example: \n```\n" + JSON.stringify(schemaExample) + "\n```";
299
328
  }
300
329
  }
301
- const response = await this.message();
302
- this.config.systemExtra = "";
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
- this.config.systemExtra = "\nReturn the result of the task between triple backtick block code tags ```";
345
+ config.system += "\nReturn the result of the task between triple backtick block code tags ```";
314
346
  }
315
- const response = await this.message();
316
- this.config.systemExtra = "";
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.stream = false;
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.processImageUrls();
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 .gpt(), .sonnet() first.");
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
- let options = {
451
+ options = {
423
452
  ...this.options,
424
453
  ...providerInstance.options,
425
454
  ...optionsTools,
455
+ ...options,
426
456
  model: currentModelKey
427
457
  };
428
458
 
429
- const config = {
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 + config.systemExtra;
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 + config.systemExtra;
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 + config.systemExtra;
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 + config.systemExtra;
1246
+ const content = config.system;
1220
1247
  const systemInstruction = { parts: [{ text: content }] };
1221
1248
 
1222
1249
  options.messages = MixGoogle.convertMessages(options.messages);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "3.6.0",
3
+ "version": "3.6.4",
4
4
  "description": "🧬 ModelMix - Unified API for Diverse AI LLM.",
5
5
  "main": "index.js",
6
6
  "repository": {