modelmix 3.6.2 → 3.6.6

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 +106 -89
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -13,7 +13,6 @@ class ModelMix {
13
13
  constructor({ options = {}, config = {} } = {}) {
14
14
  this.models = [];
15
15
  this.messages = [];
16
- this.imagesToProcess = [];
17
16
  this.tools = {};
18
17
  this.toolClient = {};
19
18
  this.mcp = {};
@@ -32,7 +31,6 @@ class ModelMix {
32
31
 
33
32
  this.config = {
34
33
  system: 'You are an assistant.',
35
- systemExtra: '',
36
34
  max_history: 1, // Default max history
37
35
  debug: false,
38
36
  bottleneck: defaultBottleneckConfig,
@@ -204,113 +202,133 @@ class ModelMix {
204
202
  }
205
203
 
206
204
  addImageFromBuffer(buffer, { role = "user" } = {}) {
207
- this.imagesToProcess.push({ type: 'buffer', buffer, config: { role } });
205
+ this.messages.push({
206
+ role,
207
+ content: [{
208
+ type: "image",
209
+ source: {
210
+ type: "buffer",
211
+ data: buffer
212
+ }
213
+ }]
214
+ });
208
215
  return this;
209
216
  }
210
217
 
211
218
  addImage(filePath, { role = "user" } = {}) {
212
- this.imagesToProcess.push({ type: 'file', filePath, config: { role } });
219
+ this.messages.push({
220
+ role,
221
+ content: [{
222
+ type: "image",
223
+ source: {
224
+ type: "file",
225
+ data: filePath
226
+ }
227
+ }]
228
+ });
213
229
  return this;
214
230
  }
215
231
 
216
- addImageFromUrl(url, config = { role: "user" }) {
217
- this.imagesToProcess.push({ type: 'url', 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
+ });
218
243
  return this;
219
244
  }
220
245
 
221
246
  async processImages() {
222
- if (this.imagesToProcess.length === 0) return;
223
-
224
- const imageProcessors = {
225
- url: async (image) => {
226
- const response = await axios.get(image.url, { responseType: 'arraybuffer' });
227
- return {
228
- buffer: Buffer.from(response.data),
229
- mimeType: response.headers['content-type']
230
- };
231
- },
232
- file: (image) => {
233
- const buffer = this.readFile(image.filePath, { encoding: null });
234
- return { buffer, source: `file: ${image.filePath}` };
235
- },
236
- buffer: (image) => ({
237
- buffer: image.buffer,
238
- source: 'buffer'
239
- })
240
- };
241
-
242
- const imageContents = await Promise.all(
243
- this.imagesToProcess.map(async (image) => {
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
+
244
256
  try {
245
- const processor = imageProcessors[image.type];
246
- if (!processor) {
247
- throw new Error(`Unknown image type: ${image.type}`);
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;
248
273
  }
249
-
250
- const { buffer, mimeType: providedMimeType, source } = await processor(image);
251
274
 
252
- // Detect mimeType from buffer if not provided (for file and buffer types)
253
- let mimeType = providedMimeType;
275
+ // Detect mimeType if not provided
254
276
  if (!mimeType) {
255
277
  const fileType = await fromBuffer(buffer);
256
278
  if (!fileType || !fileType.mime.startsWith('image/')) {
257
- throw new Error(`Invalid image ${source || image.type} - unable to detect valid image format`);
279
+ throw new Error(`Invalid image - unable to detect valid image format`);
258
280
  }
259
281
  mimeType = fileType.mime;
260
282
  }
261
-
262
- return {
263
- base64: buffer.toString('base64'),
264
- mimeType,
265
- config: image.config
266
- };
267
283
 
268
- } catch (error) {
269
- console.error(`Error processing image:`, error);
270
- return null;
271
- }
272
- })
273
- );
274
-
275
- imageContents
276
- .filter(Boolean)
277
- .forEach((image) => {
278
- this.messages.push({
279
- ...image.config,
280
- content: [{
284
+ // Update the content with processed image
285
+ message.content[j] = {
281
286
  type: "image",
282
287
  source: {
283
288
  type: "base64",
284
- media_type: image.mimeType,
285
- data: image.base64
289
+ media_type: mimeType,
290
+ data: buffer.toString('base64')
286
291
  }
287
- }]
288
- });
289
- });
292
+ };
293
+
294
+ } catch (error) {
295
+ console.error(`Error processing image:`, error);
296
+ // Remove failed image from content
297
+ message.content.splice(j, 1);
298
+ j--;
299
+ }
300
+ }
301
+ }
290
302
  }
291
303
 
292
304
  async message() {
293
- this.options.stream = false;
294
- let raw = await this.execute();
305
+ let raw = await this.execute({ options: { stream: false } });
295
306
  return raw.message;
296
307
  }
297
308
 
298
309
  async json(schemaExample = null, schemaDescription = {}, { type = 'json_object', addExample = false, addSchema = true } = {}) {
299
- 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
+ }
300
319
 
301
320
  if (schemaExample) {
302
- this.config.schema = generateJsonSchema(schemaExample, schemaDescription);
321
+ config.schema = generateJsonSchema(schemaExample, schemaDescription);
303
322
 
304
323
  if (addSchema) {
305
- this.config.systemExtra = "\nOutput JSON Schema: \n```\n" + JSON.stringify(this.config.schema) + "\n```";
324
+ config.system += "\nOutput JSON Schema: \n```\n" + JSON.stringify(config.schema) + "\n```";
306
325
  }
307
326
  if (addExample) {
308
- this.config.systemExtra += "\nOutput JSON Example: \n```\n" + JSON.stringify(schemaExample) + "\n```";
327
+ config.system += "\nOutput JSON Example: \n```\n" + JSON.stringify(schemaExample) + "\n```";
309
328
  }
310
329
  }
311
- const response = await this.message();
312
- this.config.systemExtra = "";
313
- return JSON.parse(this._extractBlock(response));
330
+ const { message } = await this.execute({ options, config });
331
+ return JSON.parse(this._extractBlock(message));
314
332
  }
315
333
 
316
334
  _extractBlock(response) {
@@ -319,23 +337,24 @@ class ModelMix {
319
337
  }
320
338
 
321
339
  async block({ addSystemExtra = true } = {}) {
340
+ let config = {
341
+ system: this.config.system,
342
+ }
343
+
322
344
  if (addSystemExtra) {
323
- 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 ```";
324
346
  }
325
- const response = await this.message();
326
- this.config.systemExtra = "";
327
- return this._extractBlock(response);
347
+ const { message } = await this.execute({ options: { stream: false }, config });
348
+ return this._extractBlock(message);
328
349
  }
329
350
 
330
351
  async raw() {
331
- this.options.stream = false;
332
- return this.execute();
352
+ return this.execute({ options: { stream: false } });
333
353
  }
334
354
 
335
355
  async stream(callback) {
336
- this.options.stream = true;
337
356
  this.streamCallback = callback;
338
- return this.execute();
357
+ return this.execute({ options: { stream: true } });
339
358
  }
340
359
 
341
360
  replaceKeyFromFile(key, filePath) {
@@ -408,9 +427,9 @@ class ModelMix {
408
427
  }
409
428
  }
410
429
 
411
- async execute() {
430
+ async execute({ config = {}, options = {} } = {}) {
412
431
  if (!this.models || this.models.length === 0) {
413
- 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.");
414
433
  }
415
434
 
416
435
  return this.limiter.schedule(async () => {
@@ -429,16 +448,18 @@ class ModelMix {
429
448
  const providerInstance = currentModel.provider;
430
449
  const optionsTools = providerInstance.getOptionsTools(this.tools);
431
450
 
432
- let options = {
451
+ options = {
433
452
  ...this.options,
434
453
  ...providerInstance.options,
435
454
  ...optionsTools,
455
+ ...options,
436
456
  model: currentModelKey
437
457
  };
438
458
 
439
- const config = {
459
+ config = {
440
460
  ...this.config,
441
461
  ...providerInstance.config,
462
+ ...config,
442
463
  };
443
464
 
444
465
  if (config.debug) {
@@ -771,7 +792,7 @@ class MixOpenAI extends MixCustom {
771
792
 
772
793
  static convertMessages(messages, config) {
773
794
 
774
- const content = config.system + config.systemExtra;
795
+ const content = config.system;
775
796
  messages = [{ role: 'system', content }, ...messages || []];
776
797
 
777
798
  const results = []
@@ -861,7 +882,7 @@ class MixAnthropic extends MixCustom {
861
882
 
862
883
  delete options.response_format;
863
884
 
864
- options.system = config.system + config.systemExtra;
885
+ options.system = config.system;
865
886
  return super.create({ config, options });
866
887
  }
867
888
 
@@ -992,16 +1013,12 @@ class MixPerplexity extends MixCustom {
992
1013
  async create({ config = {}, options = {} } = {}) {
993
1014
 
994
1015
  if (config.schema) {
995
- config.systemExtra = '';
996
-
997
1016
  options.response_format = {
998
1017
  type: 'json_schema',
999
1018
  json_schema: { schema: config.schema }
1000
1019
  };
1001
1020
  }
1002
1021
 
1003
- const content = config.system + config.systemExtra;
1004
- options.messages = [{ role: 'system', content }, ...options.messages || []];
1005
1022
  return super.create({ config, options });
1006
1023
  }
1007
1024
  }
@@ -1035,7 +1052,7 @@ class MixOllama extends MixCustom {
1035
1052
  }
1036
1053
 
1037
1054
  static convertMessages(messages, config) {
1038
- const content = config.system + config.systemExtra;
1055
+ const content = config.system;
1039
1056
  messages = [{ role: 'system', content }, ...messages || []];
1040
1057
 
1041
1058
  return messages.map(entry => {
@@ -1226,7 +1243,7 @@ class MixGoogle extends MixCustom {
1226
1243
  const fullUrl = `${this.config.url}/${options.model}:${generateContentApi}?key=${this.config.apiKey}`;
1227
1244
 
1228
1245
 
1229
- const content = config.system + config.systemExtra;
1246
+ const content = config.system;
1230
1247
  const systemInstruction = { parts: [{ text: content }] };
1231
1248
 
1232
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.2",
3
+ "version": "3.6.6",
4
4
  "description": "🧬 ModelMix - Unified API for Diverse AI LLM.",
5
5
  "main": "index.js",
6
6
  "repository": {