modelmix 3.0.2 → 3.0.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.
- package/README.md +22 -0
- package/demo/demo.mjs +10 -9
- package/index.js +29 -10
- package/package.json +1 -1
- package/schema.js +105 -0
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" + '--------|
|
|
37
|
-
const gpt = mmix.create('
|
|
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) + "\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();
|
|
@@ -296,7 +315,6 @@ class MessageHandler {
|
|
|
296
315
|
async prepareMessages() {
|
|
297
316
|
await this.processImageUrls();
|
|
298
317
|
this.applyTemplate();
|
|
299
|
-
this.messages = this.messages.slice(-this.config.max_history);
|
|
300
318
|
this.messages = this.groupByRoles(this.messages);
|
|
301
319
|
this.options.messages = this.messages;
|
|
302
320
|
}
|
|
@@ -313,6 +331,7 @@ class MessageHandler {
|
|
|
313
331
|
try {
|
|
314
332
|
const result = await this.modelEntry.create({ options: this.options, config: this.config });
|
|
315
333
|
this.messages.push({ role: "assistant", content: result.message });
|
|
334
|
+
this.messages = this.messages.slice(-this.config.max_history);
|
|
316
335
|
return result;
|
|
317
336
|
} catch (error) {
|
|
318
337
|
// If there are fallback models available, try the next one
|
|
@@ -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;
|
|
@@ -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
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));
|