modelmix 3.0.0 → 3.0.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/README.md CHANGED
@@ -106,6 +106,16 @@ Here's a quick example to get you started:
106
106
  console.log(await gpt.message());
107
107
  ```
108
108
 
109
+ #### gpt-4.1-nano (json)
110
+ ```javascript
111
+ console.log("\n" + '--------| gpt-4.1-nano |--------');
112
+ const gpt = mmix.create('gpt-4.1-nano', { options: { temperature: 0 } }).addText("Have you ever eaten a {animal}?");
113
+ gpt.replace({ '{animal}': 'cat' });
114
+ const schemaExample = { time: '24:00:00', message: 'Hello' };
115
+ const schemaDescription = { time: 'Time in format HH:MM:SS' }; // optional
116
+ console.log(await gpt.json(schemaExample, schemaDescription));
117
+ ```
118
+
109
119
  #### claude-3-5-sonnet-20240620 (writer)
110
120
  ```javascript
111
121
  const writer = mmix.create('claude-3-5-sonnet-20240620', { options: { temperature: 0.5 } });
@@ -289,6 +299,18 @@ new ModelMix(args = { options: {}, config: {} })
289
299
  - `message()`: Sends the message and returns the response.
290
300
  - `raw()`: Sends the message and returns the raw response data.
291
301
  - `stream(callback)`: Sends the message and streams the response, invoking the callback with each streamed part.
302
+ - `json(schemaExample, descriptions = {})`: Forces the model to return a response in a specific JSON format.
303
+ - `schemaExample`: Optional example of the JSON structure to be returned.
304
+ - `descriptions`: Optional descriptions for each field in the JSON structure
305
+ - Returns a Promise that resolves to the structured JSON response
306
+ - Example:
307
+ ```javascript
308
+ const response = await handler.json(
309
+ { time: '24:00:00', message: 'Hello' },
310
+ { time: 'Time in format HH:MM:SS' }
311
+ );
312
+ ```
313
+ - `block({ addText = true })`: Forces the model to return a response in a specific block format.
292
314
 
293
315
  ### MixCustom Class Overview
294
316
 
package/demo/demo.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import 'dotenv/config';
2
2
  import { ModelMix, MixOpenAI, MixAnthropic, MixPerplexity, MixOllama } from '../index.js';
3
3
 
4
+
4
5
  const mmix = new ModelMix({
5
6
  options: {
6
7
  max_tokens: 200,
@@ -20,7 +21,7 @@ mmix.attach(new MixPerplexity({
20
21
  apiKey: process.env.PPLX_API_KEY,
21
22
  system: 'You are my personal assistant.'
22
23
  },
23
-
24
+
24
25
  }));
25
26
  mmix.attach(new MixOllama({
26
27
  config: {
@@ -33,16 +34,16 @@ mmix.attach(new MixOllama({
33
34
 
34
35
  mmix.replace({ '{name}': 'ALF' });
35
36
 
36
- console.log("\n" + '--------| o3-mini |--------');
37
- const gpt = mmix.create('o3-mini', { options: { temperature: 0 } }).addText("Have you ever eaten a {animal}?");
37
+ console.log("\n" + '--------| gpt-4.1-nano |--------');
38
+ const gpt = mmix.create('gpt-4.1-nano', { options: { temperature: 0 } }).addText("Have you ever eaten a {animal}?");
38
39
  gpt.replace({ '{animal}': 'cat' });
39
- console.log(await gpt.message());
40
+ console.log(await gpt.json({ time: '24:00:00', message: 'Hello' }, { time: 'Time in format HH:MM:SS' }));
40
41
 
41
- console.log("\n" + '--------| claude-3-5-sonnet-20240620 |--------');
42
- const claude = mmix.create('claude-3-5-sonnet-20240620', { options: { temperature: 0 } });
43
- claude.addImageFromUrl('https://pbs.twimg.com/media/F6-GsjraAAADDGy?format=jpg');
44
- const imageDescription = await claude.addText('describe the image').message();
45
- console.log(imageDescription);
42
+ // console.log("\n" + '--------| claude-3-5-sonnet-20240620 |--------');
43
+ // const claude = mmix.create('claude-3-5-sonnet-20240620', { options: { temperature: 0 } });
44
+ // claude.addImageFromUrl('https://pbs.twimg.com/media/F6-GsjraAAADDGy?format=jpg');
45
+ // const imageDescription = await claude.addText('describe the image').message();
46
+ // console.log(imageDescription);
46
47
 
47
48
  console.log("\n" + '--------| claude-3-7-sonnet-20250219 |--------');
48
49
  const writer = mmix.create('claude-3-7-sonnet-20250219', { options: { temperature: 0.5 } });
package/index.js CHANGED
@@ -4,6 +4,7 @@ const mime = require('mime-types');
4
4
  const log = require('lemonlog')('ModelMix');
5
5
  const Bottleneck = require('bottleneck');
6
6
  const path = require('path');
7
+ const generateJsonSchema = require('./schema');
7
8
 
8
9
  class ModelMix {
9
10
  constructor(args = { options: {}, config: {} }) {
@@ -232,6 +233,24 @@ class MessageHandler {
232
233
  return response.message;
233
234
  }
234
235
 
236
+ async json(schemaExample = null, schemaDescription = {}) {
237
+ this.options.response_format = { type: "json_object" };
238
+ if (schemaExample) {
239
+ const schema = generateJsonSchema(schemaExample, schemaDescription);
240
+ this.addText("Output expected JSON Schema: \n```\n" + JSON.stringify(schema, null, 2) + "\n```");
241
+ }
242
+ return JSON.parse(await this.message());
243
+ }
244
+
245
+ async block({ addText = true } = {}) {
246
+ if (addText) {
247
+ this.addText("Output expected between triple backtick block code tags: \n```\n");
248
+ }
249
+ const response = await this.message();
250
+ const block = response.match(/```(?:\w+)?\s*([\s\S]*?)```/);
251
+ return block ? block[1].trim() : response;
252
+ }
253
+
235
254
  async raw() {
236
255
  this.options.stream = false;
237
256
  return this.execute();
@@ -323,7 +342,7 @@ class MessageHandler {
323
342
 
324
343
  // Create a completely new handler with the fallback model
325
344
  const nextHandler = this.mix.create(
326
- [nextModelKey, ...this.fallbackModels.slice(1)],
345
+ [nextModelKey, ...this.fallbackModels.slice(1)],
327
346
  {
328
347
  options: {
329
348
  // Keep only generic options, not model-specific ones
@@ -334,21 +353,21 @@ class MessageHandler {
334
353
  }
335
354
  }
336
355
  );
337
-
356
+
338
357
  // Assign all messages directly
339
358
  nextHandler.messages = [...this.messages];
340
-
359
+
341
360
  // Keep same system and replacements
342
361
  nextHandler.setSystem(this.config.system);
343
362
  if (this.config.replace) {
344
363
  nextHandler.replace(this.config.replace);
345
364
  }
346
-
365
+
347
366
  await nextHandler.prepareMessages();
348
-
349
- const result = await nextHandler.modelEntry.create({
350
- options: nextHandler.options,
351
- config: nextHandler.config
367
+
368
+ const result = await nextHandler.modelEntry.create({
369
+ options: nextHandler.options,
370
+ config: nextHandler.config
352
371
  });
353
372
  nextHandler.messages.push({ role: "assistant", content: result.message });
354
373
  return result;
@@ -491,7 +510,7 @@ class MixOpenAI extends MixCustom {
491
510
  getDefaultConfig(customConfig) {
492
511
  return super.getDefaultConfig({
493
512
  url: 'https://api.openai.com/v1/chat/completions',
494
- prefix: ['gpt', 'ft:', 'o3', 'o1'],
513
+ prefix: ['gpt', 'ft:', 'o'],
495
514
  apiKey: process.env.OPENAI_API_KEY,
496
515
  ...customConfig
497
516
  });
@@ -649,7 +668,7 @@ class MixGrok extends MixOpenAI {
649
668
  return super.getDefaultConfig({
650
669
  url: 'https://api.x.ai/v1/chat/completions',
651
670
  prefix: ['grok'],
652
- apiKey: process.env.XAI_API_KEY,
671
+ apiKey: process.env.XAI_API_KEY,
653
672
  ...customConfig
654
673
  });
655
674
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "3.0.0",
3
+ "version": "3.0.4",
4
4
  "description": "🧬 ModelMix - Unified API for Diverse AI LLM.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "together",
30
30
  "nano",
31
31
  "deepseek",
32
- "o3",
32
+ "o4",
33
33
  "4.1",
34
34
  "nousresearch",
35
35
  "reasoning",
package/schema.js ADDED
@@ -0,0 +1,105 @@
1
+ function generateJsonSchema(example, descriptions = {}) {
2
+ function detectType(key, value) {
3
+ if (value === null) return { type: 'null' };
4
+ if (typeof value === 'boolean') return { type: 'boolean' };
5
+ if (typeof value === 'number') {
6
+ return Number.isInteger(value) ? { type: 'integer' } : { type: 'number' };
7
+ }
8
+ if (typeof value === 'string') {
9
+ const schema = { type: 'string' };
10
+ if (/^\S+@\S+\.\S+$/.test(value)) {
11
+ schema.format = 'email';
12
+ } else if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
13
+ schema.format = 'date';
14
+ if (!descriptions[key]) {
15
+ schema.description = 'Date in format YYYY-MM-DD';
16
+ }
17
+ } else if (/^([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/.test(value)) {
18
+ schema.format = 'time';
19
+ if (!descriptions[key]) {
20
+ const hasSeconds = value.split(':').length === 3;
21
+ schema.description = hasSeconds ?
22
+ 'Time in format HH:MM:SS' :
23
+ 'Time in format HH:MM';
24
+ }
25
+ }
26
+ return schema;
27
+ }
28
+ if (Array.isArray(value)) {
29
+ if (value.length > 0 && typeof value[0] === 'object' && !Array.isArray(value[0])) {
30
+ // Es un array de objetos
31
+ return {
32
+ type: 'array',
33
+ items: generateJsonSchema(value[0], descriptions[key] || {})
34
+ };
35
+ } else {
36
+ // Es un array de valores simples
37
+ return {
38
+ type: 'array',
39
+ items: value.length > 0 ? detectType(key, value[0]) : {}
40
+ };
41
+ }
42
+ }
43
+ if (typeof value === 'object') {
44
+ return generateJsonSchema(value, descriptions[key] || {});
45
+ }
46
+ return {};
47
+ }
48
+
49
+ if (Array.isArray(example)) {
50
+ // Si el ejemplo raíz es un array
51
+ return {
52
+ type: 'array',
53
+ items: generateJsonSchema(example[0], descriptions)
54
+ };
55
+ }
56
+
57
+ const schema = {
58
+ type: 'object',
59
+ properties: {},
60
+ required: []
61
+ };
62
+
63
+ for (const key in example) {
64
+ const fieldSchema = detectType(key, example[key]);
65
+
66
+ if (descriptions[key] && typeof fieldSchema === 'object') {
67
+ fieldSchema.description = descriptions[key];
68
+ }
69
+
70
+ schema.properties[key] = fieldSchema;
71
+ schema.required.push(key);
72
+ }
73
+
74
+ return schema;
75
+ }
76
+
77
+ module.exports = generateJsonSchema;
78
+
79
+ // const example = {
80
+ // name: 'Alice',
81
+ // age: 30,
82
+ // email: 'alice@example.com',
83
+ // birthDate: '1990-01-01',
84
+ // isAdmin: false,
85
+ // preferences: {
86
+ // theme: 'dark',
87
+ // notifications: true
88
+ // },
89
+ // tags: ['admin', 'user']
90
+ // };
91
+
92
+ // const descriptions = {
93
+ // name: 'Full name of the user',
94
+ // age: 'Age must be 0 or greater',
95
+ // email: 'User email address',
96
+ // // birthDate: 'User birth date in YYYY-MM-DD format',
97
+ // preferences: {
98
+ // theme: 'Theme preference (light/dark)',
99
+ // notifications: 'Whether notifications are enabled'
100
+ // }
101
+ // };
102
+
103
+ // const schema = generateJsonSchema(example);
104
+
105
+ // console.log(JSON.stringify(schema, null, 2));