modelmix 4.2.2 β 4.2.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 +4 -5
- package/demo/demo.js +2 -2
- package/demo/free.js +1 -6
- package/demo/gpt51.js +3 -8
- package/demo/mcp-simple.js +1 -1
- package/demo/package-lock.json +449 -0
- package/demo/package.json +1 -0
- package/demo/repl-powers.js +77 -0
- package/demo/verbose.js +103 -0
- package/index.js +122 -28
- package/package.json +2 -2
- package/test/bottleneck.test.js +9 -9
- package/test/images.test.js +1 -1
- package/test/json.test.js +2 -2
- package/test/live.mcp.js +10 -8
- package/test/templates.test.js +15 -15
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
process.loadEnvFile();
|
|
2
|
+
import { ModelMix } from '../index.js';
|
|
3
|
+
import ivm from 'isolated-vm';
|
|
4
|
+
|
|
5
|
+
console.log('𧬠ModelMix - JavaScript REPL Tool Demo');
|
|
6
|
+
|
|
7
|
+
// Crear isolate una sola vez (reutilizable)
|
|
8
|
+
const isolate = new ivm.Isolate({ memoryLimit: 128 }); // 128MB mΓ‘ximo
|
|
9
|
+
|
|
10
|
+
// Ejemplo simple: REPL de JavaScript para calcular potencias de 2
|
|
11
|
+
async function replPowersExample() {
|
|
12
|
+
console.log('\n=== JavaScript REPL - Potencias de 2 ===\n');
|
|
13
|
+
const gptArgs = { options: { reasoning_effort: "none", verbosity: null } };
|
|
14
|
+
const mmix = ModelMix.new({ config: { debug: true, max_history: 10 } })
|
|
15
|
+
.gpt41nano()
|
|
16
|
+
.gpt52(gptArgs)
|
|
17
|
+
.gemini3flash()
|
|
18
|
+
.setSystem('You are a helpful assistant with access to a JavaScript REPL. When you use the REPL and get results, always show them to the user in your response.');
|
|
19
|
+
|
|
20
|
+
// Variable para capturar el resultado de la herramienta
|
|
21
|
+
let toolResult = null;
|
|
22
|
+
|
|
23
|
+
// Agregar herramienta REPL personalizada
|
|
24
|
+
mmix.addTool({
|
|
25
|
+
name: "javascript_repl",
|
|
26
|
+
description: "Execute JavaScript code in a REPL environment. You can run any valid JavaScript code and get the result.",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
code: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "JavaScript code to execute"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ["code"]
|
|
36
|
+
}
|
|
37
|
+
}, async ({ code }) => {
|
|
38
|
+
console.log('π§ Ejecutando cΓ³digo JavaScript:');
|
|
39
|
+
console.log('β'.repeat(50));
|
|
40
|
+
console.log(code);
|
|
41
|
+
console.log('β'.repeat(50));
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const context = await isolate.createContext();
|
|
45
|
+
const result = await context.eval(`JSON.stringify(eval(${JSON.stringify(code)}))`, { timeout: 10000 });
|
|
46
|
+
toolResult = JSON.parse(result);
|
|
47
|
+
console.log('\nβ
Resultado:', toolResult);
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.log('\nβ Error:', error.message);
|
|
51
|
+
return `Error: ${error.message}`;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Pedir al modelo que calcule 100 potencias de 2
|
|
56
|
+
mmix.addText('Calcular las primeras 100 potencias de 2 (2^0 hasta 2^99). DespuΓ©s de ejecutar el cΓ³digo, menciona algunos valores del resultado como las primeras 5 y las ΓΊltimas 5 potencias.');
|
|
57
|
+
|
|
58
|
+
const result = await mmix.message();
|
|
59
|
+
console.log('\n㪠Respuesta del modelo:');
|
|
60
|
+
console.log(result);
|
|
61
|
+
|
|
62
|
+
// Mostrar muestra del resultado si estΓ‘ disponible
|
|
63
|
+
if (toolResult && Array.isArray(toolResult)) {
|
|
64
|
+
console.log('\nπ Muestra de resultados (primeros 10 y ΓΊltimos 10):');
|
|
65
|
+
console.log('Primeros 10:', toolResult.slice(0, 10));
|
|
66
|
+
console.log('Γltimos 10:', toolResult.slice(-10));
|
|
67
|
+
console.log(`\nTotal: ${toolResult.length} potencias calculadas`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await replPowersExample();
|
|
73
|
+
console.log('\nβ
Ejemplo completado');
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('β Error:', error);
|
|
76
|
+
}
|
|
77
|
+
|
package/demo/verbose.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
process.loadEnvFile();
|
|
2
|
+
import { ModelMix, MixOpenAI } from '../index.js';
|
|
3
|
+
|
|
4
|
+
const prompt = "Say 'Hello World' in exactly 2 words.";
|
|
5
|
+
|
|
6
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
7
|
+
console.log('DEMO: Verbose Modes in ModelMix');
|
|
8
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
// ===================================================================
|
|
12
|
+
// VERBOSE LEVEL 0 - Silent Mode
|
|
13
|
+
// ===================================================================
|
|
14
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
15
|
+
console.log('1. VERBOSE LEVEL 0 - Silent Mode');
|
|
16
|
+
console.log(' No output at all, only the result');
|
|
17
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
18
|
+
|
|
19
|
+
await ModelMix
|
|
20
|
+
.new({ config: { verbose: 0 } })
|
|
21
|
+
.gpt41nano()
|
|
22
|
+
.addText(prompt)
|
|
23
|
+
.message();
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// ===================================================================
|
|
27
|
+
// VERBOSE LEVEL 1 - Minimal Mode
|
|
28
|
+
// ===================================================================
|
|
29
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
30
|
+
console.log('2. VERBOSE LEVEL 1 - Minimal Mode');
|
|
31
|
+
console.log(' Shows: β [model] #N and β Success');
|
|
32
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
33
|
+
|
|
34
|
+
await ModelMix
|
|
35
|
+
.new({ config: { verbose: 1 } })
|
|
36
|
+
.gpt41nano()
|
|
37
|
+
.addText(prompt)
|
|
38
|
+
.message();
|
|
39
|
+
|
|
40
|
+
// ===================================================================
|
|
41
|
+
// VERBOSE LEVEL 2 - Readable Summary (DEFAULT)
|
|
42
|
+
// ===================================================================
|
|
43
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
44
|
+
console.log('3. VERBOSE LEVEL 2 - Readable Summary (DEFAULT)');
|
|
45
|
+
console.log(' Shows: model, system prompt, input, message count, output');
|
|
46
|
+
console.log(' Everything in compact format on 2 lines');
|
|
47
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
48
|
+
|
|
49
|
+
await ModelMix
|
|
50
|
+
.new({ config: { verbose: 2 } })
|
|
51
|
+
.gpt41nano()
|
|
52
|
+
.addText(prompt)
|
|
53
|
+
.json({ message: 'string' });
|
|
54
|
+
|
|
55
|
+
// ===================================================================
|
|
56
|
+
// VERBOSE LEVEL 3 - Full Debug
|
|
57
|
+
// ===================================================================
|
|
58
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
59
|
+
console.log('4. VERBOSE LEVEL 3 - Full Debug Mode');
|
|
60
|
+
console.log(' Shows: everything from level 2 + raw response, full message,');
|
|
61
|
+
console.log(' request details, config, and options');
|
|
62
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
63
|
+
|
|
64
|
+
await ModelMix
|
|
65
|
+
.new({ config: { verbose: 3 } })
|
|
66
|
+
.gpt41nano()
|
|
67
|
+
.addText(prompt)
|
|
68
|
+
.message();
|
|
69
|
+
|
|
70
|
+
// ===================================================================
|
|
71
|
+
// FALLBACK EXAMPLE (with verbose 2)
|
|
72
|
+
// ===================================================================
|
|
73
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
74
|
+
console.log('5. FALLBACK EXAMPLE (Verbose Level 2)');
|
|
75
|
+
console.log(' Shows how fallback models are displayed');
|
|
76
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const resultFallback = await ModelMix
|
|
80
|
+
.new({ config: { verbose: 2 } })
|
|
81
|
+
.attach('fake-model-that-will-fail', new MixOpenAI())
|
|
82
|
+
.gpt41nano() // This will be the fallback
|
|
83
|
+
.addText(prompt)
|
|
84
|
+
.message();
|
|
85
|
+
|
|
86
|
+
console.log(`Result: ${resultFallback}\n`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.log(`Error (should not happen): ${error.message}\n`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
// ===================================================================
|
|
93
|
+
// SUMMARY
|
|
94
|
+
// ===================================================================
|
|
95
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
96
|
+
console.log('DEMO COMPLETED');
|
|
97
|
+
console.log('βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n');
|
|
98
|
+
console.log('Summary:');
|
|
99
|
+
console.log(' - Level 0: Silent, no logs');
|
|
100
|
+
console.log(' - Level 1: Minimal (β model, β Success)');
|
|
101
|
+
console.log(' - Level 2: Readable (1 line input + 1 line output) [DEFAULT]');
|
|
102
|
+
console.log(' - Level 3: Full debug (includes raw responses and configs)');
|
|
103
|
+
console.log('');
|
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ class ModelMix {
|
|
|
20
20
|
this.mcp = {};
|
|
21
21
|
this.mcpToolsManager = new MCPToolsManager();
|
|
22
22
|
this.options = {
|
|
23
|
-
max_tokens:
|
|
23
|
+
max_tokens: 8192,
|
|
24
24
|
temperature: 1, // 1 --> More creative, 0 --> More deterministic.
|
|
25
25
|
...options
|
|
26
26
|
};
|
|
@@ -35,6 +35,7 @@ class ModelMix {
|
|
|
35
35
|
system: 'You are an assistant.',
|
|
36
36
|
max_history: 1, // Default max history
|
|
37
37
|
debug: false,
|
|
38
|
+
verbose: 2, // 0=silent, 1=minimal, 2=readable summary, 3=full details
|
|
38
39
|
bottleneck: defaultBottleneckConfig,
|
|
39
40
|
...config
|
|
40
41
|
}
|
|
@@ -45,7 +46,6 @@ class ModelMix {
|
|
|
45
46
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
|
|
49
49
|
replace(keyValues) {
|
|
50
50
|
this.config.replace = { ...this.config.replace, ...keyValues };
|
|
51
51
|
return this;
|
|
@@ -55,8 +55,8 @@ class ModelMix {
|
|
|
55
55
|
return new ModelMix({ options, config, mix });
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
new() {
|
|
59
|
-
return new ModelMix({ options: this.options, config: this.config });
|
|
58
|
+
new({ options = {}, config = {}, mix = {} } = {}) {
|
|
59
|
+
return new ModelMix({ options: { ...this.options, ...options }, config: { ...this.config, ...config }, mix: { ...this.mix, ...mix } });
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
static formatJSON(obj) {
|
|
@@ -79,6 +79,69 @@ class ModelMix {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// Verbose logging helpers
|
|
83
|
+
static truncate(str, maxLen = 100) {
|
|
84
|
+
if (!str || typeof str !== 'string') return str;
|
|
85
|
+
return str.length > maxLen ? str.substring(0, maxLen) + '...' : str;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static getVerboseLevel(config) {
|
|
89
|
+
// debug=true acts as verbose level 3
|
|
90
|
+
return config.verbose || 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static verboseLog(level, config, ...args) {
|
|
94
|
+
const verboseLevel = ModelMix.getVerboseLevel(config);
|
|
95
|
+
if (verboseLevel >= level) {
|
|
96
|
+
console.log(...args);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static formatInputSummary(messages, system) {
|
|
101
|
+
const lastMessage = messages[messages.length - 1];
|
|
102
|
+
let inputText = '';
|
|
103
|
+
|
|
104
|
+
if (lastMessage && Array.isArray(lastMessage.content)) {
|
|
105
|
+
const textContent = lastMessage.content.find(c => c.type === 'text');
|
|
106
|
+
if (textContent) inputText = textContent.text;
|
|
107
|
+
} else if (lastMessage && typeof lastMessage.content === 'string') {
|
|
108
|
+
inputText = lastMessage.content;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const systemStr = `System: ${ModelMix.truncate(system, 50)}`;
|
|
112
|
+
const inputStr = `Input: ${ModelMix.truncate(inputText, 120)}`;
|
|
113
|
+
const msgCount = `(${messages.length} msg${messages.length !== 1 ? 's' : ''})`;
|
|
114
|
+
|
|
115
|
+
return `${systemStr} \n| ${inputStr} ${msgCount}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static formatOutputSummary(result, verboseLevel = 2) {
|
|
119
|
+
const parts = [];
|
|
120
|
+
if (result.message) {
|
|
121
|
+
// Try to parse as JSON for better formatting
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(result.message.trim());
|
|
124
|
+
// If it's valid JSON and verbose >= 2, show it formatted
|
|
125
|
+
if (verboseLevel >= 2) {
|
|
126
|
+
parts.push(`Output (JSON):\n${ModelMix.formatJSON(parsed)}`);
|
|
127
|
+
} else {
|
|
128
|
+
parts.push(`Output: ${ModelMix.truncate(result.message, 150)}`);
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// Not JSON, show truncated as before
|
|
132
|
+
parts.push(`Output: ${ModelMix.truncate(result.message, 150)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (result.think) {
|
|
136
|
+
parts.push(`Thinking: ${ModelMix.truncate(result.think, 80)}`);
|
|
137
|
+
}
|
|
138
|
+
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
139
|
+
const toolNames = result.toolCalls.map(t => t.function?.name || t.name).join(', ');
|
|
140
|
+
parts.push(`Tools: ${toolNames}`);
|
|
141
|
+
}
|
|
142
|
+
return parts.join(' | ');
|
|
143
|
+
}
|
|
144
|
+
|
|
82
145
|
attach(key, provider) {
|
|
83
146
|
|
|
84
147
|
if (this.models.some(model => model.key === key)) {
|
|
@@ -102,9 +165,6 @@ class ModelMix {
|
|
|
102
165
|
gpt41nano({ options = {}, config = {} } = {}) {
|
|
103
166
|
return this.attach('gpt-4.1-nano', new MixOpenAI({ options, config }));
|
|
104
167
|
}
|
|
105
|
-
gpt4o({ options = {}, config = {} } = {}) {
|
|
106
|
-
return this.attach('gpt-4o', new MixOpenAI({ options, config }));
|
|
107
|
-
}
|
|
108
168
|
o4mini({ options = {}, config = {} } = {}) {
|
|
109
169
|
return this.attach('o4-mini', new MixOpenAI({ options, config }));
|
|
110
170
|
}
|
|
@@ -276,8 +336,11 @@ class ModelMix {
|
|
|
276
336
|
return this.attach('MiniMax-M2', new MixMiniMax({ options, config }));
|
|
277
337
|
}
|
|
278
338
|
|
|
279
|
-
minimaxM21({ options = {}, config = {} } = {}) {
|
|
280
|
-
|
|
339
|
+
minimaxM21({ options = {}, config = {}, mix = { minimax: true } } = {}) {
|
|
340
|
+
mix = { ...this.mix, ...mix };
|
|
341
|
+
if (mix.minimax) this.attach('MiniMax-M2.1', new MixMiniMax({ options, config }));
|
|
342
|
+
if (mix.cerebras) this.attach('MiniMax-M2.1', new MixCerebras({ options, config }));
|
|
343
|
+
return this;
|
|
281
344
|
}
|
|
282
345
|
|
|
283
346
|
minimaxM2Stable({ options = {}, config = {} } = {}) {
|
|
@@ -292,8 +355,10 @@ class ModelMix {
|
|
|
292
355
|
}
|
|
293
356
|
|
|
294
357
|
GLM47({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
|
|
358
|
+
mix = { ...this.mix, ...mix };
|
|
295
359
|
if (mix.fireworks) this.attach('accounts/fireworks/models/glm-4p7', new MixFireworks({ options, config }));
|
|
296
360
|
if (mix.openrouter) this.attach('z-ai/glm-4.7', new MixOpenRouter({ options, config }));
|
|
361
|
+
if (mix.cerebras) this.attach('zai-glm-4.7', new MixCerebras({ options, config }));
|
|
297
362
|
return this;
|
|
298
363
|
}
|
|
299
364
|
|
|
@@ -656,9 +721,19 @@ class ModelMix {
|
|
|
656
721
|
...config,
|
|
657
722
|
};
|
|
658
723
|
|
|
659
|
-
|
|
724
|
+
const verboseLevel = ModelMix.getVerboseLevel(currentConfig);
|
|
725
|
+
|
|
726
|
+
if (verboseLevel >= 1) {
|
|
660
727
|
const isPrimary = i === 0;
|
|
661
|
-
|
|
728
|
+
const prefix = isPrimary ? 'β' : 'β»';
|
|
729
|
+
const suffix = isPrimary ? '' : ' (fallback)';
|
|
730
|
+
const header = `\n${prefix} [${currentModelKey}] #${i + 1}${suffix}`;
|
|
731
|
+
|
|
732
|
+
if (verboseLevel >= 2) {
|
|
733
|
+
console.log(`${header} | ${ModelMix.formatInputSummary(this.messages, currentConfig.system)}`);
|
|
734
|
+
} else {
|
|
735
|
+
console.log(header);
|
|
736
|
+
}
|
|
662
737
|
}
|
|
663
738
|
|
|
664
739
|
try {
|
|
@@ -692,27 +767,36 @@ class ModelMix {
|
|
|
692
767
|
return this.execute();
|
|
693
768
|
}
|
|
694
769
|
|
|
695
|
-
|
|
696
|
-
|
|
770
|
+
// Verbose level 1: Just success indicator
|
|
771
|
+
if (verboseLevel === 1) {
|
|
772
|
+
console.log(`β Success`);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Verbose level 2: Readable summary of output
|
|
776
|
+
if (verboseLevel >= 2) {
|
|
777
|
+
console.log(`β ${ModelMix.formatOutputSummary(result, verboseLevel).trim()}`);
|
|
778
|
+
}
|
|
697
779
|
|
|
780
|
+
// Verbose level 3 (debug): Full response details
|
|
781
|
+
if (verboseLevel >= 3) {
|
|
698
782
|
if (result.response) {
|
|
699
|
-
console.log('\
|
|
783
|
+
console.log('\n[RAW RESPONSE]');
|
|
700
784
|
console.log(ModelMix.formatJSON(result.response));
|
|
701
785
|
}
|
|
702
786
|
|
|
703
787
|
if (result.message) {
|
|
704
|
-
console.log('\
|
|
788
|
+
console.log('\n[FULL MESSAGE]');
|
|
705
789
|
console.log(ModelMix.formatMessage(result.message));
|
|
706
790
|
}
|
|
707
791
|
|
|
708
792
|
if (result.think) {
|
|
709
|
-
console.log('\
|
|
793
|
+
console.log('\n[FULL THINKING]');
|
|
710
794
|
console.log(result.think);
|
|
711
795
|
}
|
|
712
|
-
|
|
713
|
-
console.log('');
|
|
714
796
|
}
|
|
715
797
|
|
|
798
|
+
if (verboseLevel >= 1) console.log('');
|
|
799
|
+
|
|
716
800
|
return result;
|
|
717
801
|
|
|
718
802
|
} catch (error) {
|
|
@@ -940,15 +1024,19 @@ class MixCustom {
|
|
|
940
1024
|
|
|
941
1025
|
options.messages = this.convertMessages(options.messages, config);
|
|
942
1026
|
|
|
943
|
-
|
|
944
|
-
console.log('\nREQUEST:');
|
|
1027
|
+
const verboseLevel = ModelMix.getVerboseLevel(config);
|
|
945
1028
|
|
|
946
|
-
|
|
1029
|
+
// Verbose level 3 (debug): Full request details
|
|
1030
|
+
if (verboseLevel >= 3) {
|
|
1031
|
+
console.log('\n[REQUEST DETAILS]');
|
|
1032
|
+
|
|
1033
|
+
console.log('\n[CONFIG]');
|
|
947
1034
|
const configToLog = { ...config };
|
|
948
1035
|
delete configToLog.debug;
|
|
1036
|
+
delete configToLog.verbose;
|
|
949
1037
|
console.log(ModelMix.formatJSON(configToLog));
|
|
950
1038
|
|
|
951
|
-
console.log('\
|
|
1039
|
+
console.log('\n[OPTIONS]');
|
|
952
1040
|
console.log(ModelMix.formatJSON(options));
|
|
953
1041
|
}
|
|
954
1042
|
|
|
@@ -1680,7 +1768,8 @@ class MixGoogle extends MixCustom {
|
|
|
1680
1768
|
functionCall: {
|
|
1681
1769
|
name: toolCall.function.name,
|
|
1682
1770
|
args: JSON.parse(toolCall.function.arguments)
|
|
1683
|
-
}
|
|
1771
|
+
},
|
|
1772
|
+
thought_signature: toolCall.thought_signature || ""
|
|
1684
1773
|
}))
|
|
1685
1774
|
}
|
|
1686
1775
|
}
|
|
@@ -1766,15 +1855,19 @@ class MixGoogle extends MixCustom {
|
|
|
1766
1855
|
};
|
|
1767
1856
|
|
|
1768
1857
|
try {
|
|
1769
|
-
|
|
1770
|
-
console.log('\nREQUEST (GOOGLE):');
|
|
1858
|
+
const verboseLevel = ModelMix.getVerboseLevel(config);
|
|
1771
1859
|
|
|
1772
|
-
|
|
1860
|
+
// Verbose level 3 (debug): Full request details
|
|
1861
|
+
if (verboseLevel >= 3) {
|
|
1862
|
+
console.log('\n[REQUEST DETAILS - GOOGLE]');
|
|
1863
|
+
|
|
1864
|
+
console.log('\n[CONFIG]');
|
|
1773
1865
|
const configToLog = { ...config };
|
|
1774
1866
|
delete configToLog.debug;
|
|
1867
|
+
delete configToLog.verbose;
|
|
1775
1868
|
console.log(ModelMix.formatJSON(configToLog));
|
|
1776
1869
|
|
|
1777
|
-
console.log('\
|
|
1870
|
+
console.log('\n[PAYLOAD]');
|
|
1778
1871
|
console.log(ModelMix.formatJSON(payload));
|
|
1779
1872
|
}
|
|
1780
1873
|
|
|
@@ -1808,7 +1901,8 @@ class MixGoogle extends MixCustom {
|
|
|
1808
1901
|
function: {
|
|
1809
1902
|
name: part.functionCall.name,
|
|
1810
1903
|
arguments: JSON.stringify(part.functionCall.args)
|
|
1811
|
-
}
|
|
1904
|
+
},
|
|
1905
|
+
thought_signature: part.thoughtSignature || ""
|
|
1812
1906
|
};
|
|
1813
1907
|
}
|
|
1814
1908
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelmix",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.6",
|
|
4
4
|
"description": "𧬠Reliable interface with automatic fallback for AI LLMs.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://github.com/clasen/ModelMix#readme",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
50
50
|
"axios": "^1.12.2",
|
|
51
51
|
"bottleneck": "^2.19.5",
|
|
52
52
|
"file-type": "^16.5.4",
|
package/test/bottleneck.test.js
CHANGED
|
@@ -72,7 +72,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
72
72
|
it('should enforce minimum time between requests', async () => {
|
|
73
73
|
const startTimes = [];
|
|
74
74
|
|
|
75
|
-
model.
|
|
75
|
+
model.gpt41();
|
|
76
76
|
|
|
77
77
|
// Mock API responses
|
|
78
78
|
nock('https://api.openai.com')
|
|
@@ -122,7 +122,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
model.
|
|
125
|
+
model.gpt41();
|
|
126
126
|
|
|
127
127
|
// Mock API with delay to simulate concurrent requests
|
|
128
128
|
nock('https://api.openai.com')
|
|
@@ -184,7 +184,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
184
184
|
it('should apply rate limiting to OpenAI requests', async () => {
|
|
185
185
|
const requestTimes = [];
|
|
186
186
|
|
|
187
|
-
model.
|
|
187
|
+
model.gpt41();
|
|
188
188
|
|
|
189
189
|
nock('https://api.openai.com')
|
|
190
190
|
.post('/v1/chat/completions')
|
|
@@ -267,7 +267,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
267
267
|
});
|
|
268
268
|
|
|
269
269
|
it('should handle rate limiting with API errors', async () => {
|
|
270
|
-
model.
|
|
270
|
+
model.gpt41();
|
|
271
271
|
|
|
272
272
|
nock('https://api.openai.com')
|
|
273
273
|
.post('/v1/chat/completions')
|
|
@@ -289,7 +289,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
289
289
|
it('should continue rate limiting after errors', async () => {
|
|
290
290
|
const requestTimes = [];
|
|
291
291
|
|
|
292
|
-
model.
|
|
292
|
+
model.gpt41();
|
|
293
293
|
|
|
294
294
|
// First request fails
|
|
295
295
|
nock('https://api.openai.com')
|
|
@@ -345,7 +345,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
345
345
|
}
|
|
346
346
|
});
|
|
347
347
|
|
|
348
|
-
model.
|
|
348
|
+
model.gpt41();
|
|
349
349
|
|
|
350
350
|
let requestCount = 0;
|
|
351
351
|
|
|
@@ -392,7 +392,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
392
392
|
}
|
|
393
393
|
});
|
|
394
394
|
|
|
395
|
-
model.
|
|
395
|
+
model.gpt41();
|
|
396
396
|
|
|
397
397
|
const results = [];
|
|
398
398
|
|
|
@@ -440,7 +440,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
440
440
|
}
|
|
441
441
|
});
|
|
442
442
|
|
|
443
|
-
model.
|
|
443
|
+
model.gpt41();
|
|
444
444
|
|
|
445
445
|
nock('https://api.openai.com')
|
|
446
446
|
.post('/v1/chat/completions')
|
|
@@ -489,7 +489,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
|
|
|
489
489
|
done();
|
|
490
490
|
});
|
|
491
491
|
|
|
492
|
-
model.
|
|
492
|
+
model.gpt41();
|
|
493
493
|
|
|
494
494
|
nock('https://api.openai.com')
|
|
495
495
|
.post('/v1/chat/completions')
|
package/test/images.test.js
CHANGED
|
@@ -25,7 +25,7 @@ describe('Image Processing and Multimodal Support Tests', () => {
|
|
|
25
25
|
it('should handle base64 image data correctly', async () => {
|
|
26
26
|
const base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC';
|
|
27
27
|
|
|
28
|
-
model.
|
|
28
|
+
model.gpt41()
|
|
29
29
|
.addText('What do you see in this image?')
|
|
30
30
|
.addImageFromUrl(base64Image);
|
|
31
31
|
|
package/test/json.test.js
CHANGED
|
@@ -198,7 +198,7 @@ describe('JSON Schema and Structured Output Tests', () => {
|
|
|
198
198
|
}]
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
-
model.
|
|
201
|
+
model.gpt41().addText('List 3 countries');
|
|
202
202
|
|
|
203
203
|
// Mock the API response
|
|
204
204
|
nock('https://api.openai.com')
|
|
@@ -270,7 +270,7 @@ describe('JSON Schema and Structured Output Tests', () => {
|
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
it('should handle JSON parsing errors gracefully', async () => {
|
|
273
|
-
model.
|
|
273
|
+
model.gpt41().addText('Generate invalid JSON');
|
|
274
274
|
|
|
275
275
|
// Mock invalid JSON response
|
|
276
276
|
nock('https://api.openai.com')
|
package/test/live.mcp.js
CHANGED
|
@@ -110,8 +110,8 @@ describe('Live MCP Integration Tests', function () {
|
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
it('should use custom MCP tools with Gemini
|
|
114
|
-
const model = ModelMix.new(setup).
|
|
113
|
+
it('should use custom MCP tools with Gemini 3 Flash', async function () {
|
|
114
|
+
const model = ModelMix.new(setup).gemini3flash();
|
|
115
115
|
|
|
116
116
|
// Add password generator tool
|
|
117
117
|
model.addTool({
|
|
@@ -149,11 +149,13 @@ describe('Live MCP Integration Tests', function () {
|
|
|
149
149
|
model.addText('Generate a secure password of 16 characters with symbols.');
|
|
150
150
|
|
|
151
151
|
const response = await model.message();
|
|
152
|
-
console.log(`Gemini
|
|
152
|
+
console.log(`Gemini 3 Flash with MCP tools: ${response}`);
|
|
153
153
|
|
|
154
154
|
expect(response).to.be.a('string');
|
|
155
|
-
|
|
156
|
-
expect(response).to.include('
|
|
155
|
+
// Check password is mentioned and a generated password string is present
|
|
156
|
+
expect(response.toLowerCase()).to.include('password');
|
|
157
|
+
// Verify a generated password is in the response (at least 12 chars with mix of alphanumeric/symbols)
|
|
158
|
+
expect(response).to.match(/[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{}|;:,.<>?]{12,}/);
|
|
157
159
|
});
|
|
158
160
|
|
|
159
161
|
});
|
|
@@ -374,8 +376,8 @@ describe('Live MCP Integration Tests', function () {
|
|
|
374
376
|
expect(result.factorial_result).to.equal(120);
|
|
375
377
|
});
|
|
376
378
|
|
|
377
|
-
it('should use MCP tools with JSON output using Gemini
|
|
378
|
-
const model = ModelMix.new(setup).
|
|
379
|
+
it('should use MCP tools with JSON output using Gemini 3 Flash', async function () {
|
|
380
|
+
const model = ModelMix.new(setup).gemini3flash();
|
|
379
381
|
|
|
380
382
|
// Add system info tool
|
|
381
383
|
model.addTool({
|
|
@@ -414,7 +416,7 @@ describe('Live MCP Integration Tests', function () {
|
|
|
414
416
|
generated_at: ""
|
|
415
417
|
});
|
|
416
418
|
|
|
417
|
-
console.log(`Gemini
|
|
419
|
+
console.log(`Gemini 3 Flash with MCP tools JSON result:`, result);
|
|
418
420
|
|
|
419
421
|
expect(result).to.be.an('object');
|
|
420
422
|
expect(result.timestamp).to.be.a('number');
|