modelmix 4.3.4 → 4.4.0
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 +160 -71
- package/demo/custom.js +1 -1
- package/demo/demo.js +2 -2
- package/demo/free.js +1 -1
- package/demo/gpt51.js +1 -1
- package/demo/grok.js +1 -1
- package/demo/images.js +1 -1
- package/demo/json.js +16 -4
- package/demo/mcp-simple.js +1 -1
- package/demo/minimax.js +1 -1
- package/demo/parallel.js +1 -1
- package/demo/repl-powers.js +1 -1
- package/demo/short.js +1 -1
- package/index.js +114 -21
- package/package.json +3 -2
- package/skills/modelmix/SKILL.md +320 -0
package/README.md
CHANGED
|
@@ -18,10 +18,13 @@ Ever found yourself wanting to integrate AI models into your projects but worrie
|
|
|
18
18
|
## 🛠️ Usage
|
|
19
19
|
|
|
20
20
|
1. **Install the ModelMix package:**
|
|
21
|
-
Recommended: install dotenv to manage environment variables
|
|
22
21
|
```bash
|
|
23
|
-
npm install modelmix
|
|
22
|
+
npm install modelmix
|
|
24
23
|
```
|
|
24
|
+
> **AI Skill**: You can also add ModelMix as a skill for AI agentic development:
|
|
25
|
+
> ```bash
|
|
26
|
+
> npx skills add https://github.com/clasen/ModelMix --skill modelmix
|
|
27
|
+
> ```
|
|
25
28
|
|
|
26
29
|
2. **Setup your environment variables (.env file)**:
|
|
27
30
|
Only the API keys you plan to use are required.
|
|
@@ -34,6 +37,8 @@ MINIMAX_API_KEY="your-minimax-key..."
|
|
|
34
37
|
GEMINI_API_KEY="AIza..."
|
|
35
38
|
```
|
|
36
39
|
|
|
40
|
+
For environment variables, use `dotenv` or Node's built-in `process.loadEnvFile()`.
|
|
41
|
+
|
|
37
42
|
3. **Create and configure your models**:
|
|
38
43
|
|
|
39
44
|
```javascript
|
|
@@ -55,7 +60,7 @@ console.log(await model.json(outputExample));
|
|
|
55
60
|
const setup = {
|
|
56
61
|
config: {
|
|
57
62
|
system: "You are ALF, if they ask your name, respond with 'ALF'.",
|
|
58
|
-
debug:
|
|
63
|
+
debug: 2
|
|
59
64
|
}
|
|
60
65
|
};
|
|
61
66
|
|
|
@@ -187,107 +192,175 @@ const result = await ModelMix.new({
|
|
|
187
192
|
.message();
|
|
188
193
|
```
|
|
189
194
|
|
|
190
|
-
## 🔄
|
|
195
|
+
## 🔄 Templates
|
|
196
|
+
|
|
197
|
+
ModelMix includes a simple but powerful templating system. You can write your system prompts and user messages in external `.md` files with placeholders, then use `replace` to fill them in at runtime.
|
|
191
198
|
|
|
192
|
-
###
|
|
199
|
+
### Core methods
|
|
193
200
|
|
|
194
|
-
|
|
201
|
+
| Method | Description |
|
|
202
|
+
| --- | --- |
|
|
203
|
+
| `setSystemFromFile(path)` | Load the system prompt from a file |
|
|
204
|
+
| `addTextFromFile(path)` | Load a user message from a file |
|
|
205
|
+
| `replace({ key: value })` | Replace placeholders in all messages and the system prompt |
|
|
206
|
+
| `replaceKeyFromFile(key, path)` | Replace a placeholder with the contents of a file |
|
|
207
|
+
|
|
208
|
+
### Basic example with `replace`
|
|
195
209
|
|
|
196
|
-
#### Usage:
|
|
197
210
|
```javascript
|
|
198
|
-
|
|
211
|
+
const gpt = ModelMix.new().gpt5mini();
|
|
212
|
+
|
|
213
|
+
gpt.addText('Write a short story about a {animal} that lives in {place}.');
|
|
214
|
+
gpt.replace({ '{animal}': 'cat', '{place}': 'a haunted castle' });
|
|
215
|
+
|
|
216
|
+
console.log(await gpt.message());
|
|
199
217
|
```
|
|
200
218
|
|
|
201
|
-
|
|
202
|
-
1. It updates the `config.replace` object with the provided key-value pairs.
|
|
203
|
-
2. In the template, placeholders like `{{key1}}` will be replaced with 'value1'.
|
|
219
|
+
### Loading prompts from `.md` files
|
|
204
220
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
Instead of writing long prompts inline, keep them in separate Markdown files. This makes them easier to read, edit, and version control.
|
|
222
|
+
|
|
223
|
+
**`prompts/system.md`**
|
|
224
|
+
```markdown
|
|
225
|
+
You are {role}, an expert in {topic}.
|
|
226
|
+
Always respond in {language}.
|
|
210
227
|
```
|
|
211
|
-
This would result in the message: "Hello Alice, are you 30 years old?"
|
|
212
228
|
|
|
213
|
-
|
|
229
|
+
**`prompts/task.md`**
|
|
230
|
+
```markdown
|
|
231
|
+
Analyze the following and provide 3 key insights:
|
|
214
232
|
|
|
215
|
-
|
|
233
|
+
{content}
|
|
234
|
+
```
|
|
216
235
|
|
|
217
|
-
|
|
236
|
+
**`app.js`**
|
|
218
237
|
```javascript
|
|
219
|
-
|
|
238
|
+
const gpt = ModelMix.new().gpt5mini();
|
|
239
|
+
|
|
240
|
+
gpt.setSystemFromFile('./prompts/system.md');
|
|
241
|
+
gpt.addTextFromFile('./prompts/task.md');
|
|
242
|
+
|
|
243
|
+
gpt.replace({
|
|
244
|
+
'{role}': 'a senior analyst',
|
|
245
|
+
'{topic}': 'market trends',
|
|
246
|
+
'{language}': 'Spanish',
|
|
247
|
+
'{content}': 'Bitcoin surpassed $100,000 in December 2024...'
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
console.log(await gpt.message());
|
|
220
251
|
```
|
|
221
252
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
### Injecting file contents into a placeholder
|
|
254
|
+
|
|
255
|
+
Use `replaceKeyFromFile` when the replacement value itself is a large text stored in a file.
|
|
256
|
+
|
|
257
|
+
**`prompts/summarize.md`**
|
|
258
|
+
```markdown
|
|
259
|
+
Summarize the following article in 3 bullet points:
|
|
225
260
|
|
|
226
|
-
|
|
261
|
+
{article}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**`app.js`**
|
|
227
265
|
```javascript
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
266
|
+
const gpt = ModelMix.new().gpt5mini();
|
|
267
|
+
|
|
268
|
+
gpt.addTextFromFile('./prompts/summarize.md');
|
|
269
|
+
gpt.replaceKeyFromFile('{article}', './data/article.md');
|
|
270
|
+
|
|
271
|
+
console.log(await gpt.message());
|
|
231
272
|
```
|
|
232
|
-
This would replace `article_file_contents` with the entire content of 'article.txt'.
|
|
233
273
|
|
|
234
|
-
###
|
|
235
|
-
|
|
236
|
-
|
|
274
|
+
### Full template workflow
|
|
275
|
+
|
|
276
|
+
Combine all methods to build reusable, file-based prompt pipelines:
|
|
237
277
|
|
|
238
|
-
|
|
278
|
+
**`prompts/system.md`**
|
|
279
|
+
```markdown
|
|
280
|
+
You are {role}. Follow these rules:
|
|
281
|
+
- Be concise
|
|
282
|
+
- Use examples when possible
|
|
283
|
+
- Respond in {language}
|
|
284
|
+
```
|
|
239
285
|
|
|
240
|
-
|
|
286
|
+
**`prompts/review.md`**
|
|
287
|
+
```markdown
|
|
288
|
+
Review the following code and suggest improvements:
|
|
241
289
|
|
|
242
|
-
|
|
290
|
+
{code}
|
|
291
|
+
```
|
|
243
292
|
|
|
293
|
+
**`app.js`**
|
|
244
294
|
```javascript
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
295
|
+
const gpt = ModelMix.new().gpt5mini();
|
|
296
|
+
|
|
297
|
+
gpt.setSystemFromFile('./prompts/system.md');
|
|
298
|
+
gpt.addTextFromFile('./prompts/review.md');
|
|
299
|
+
|
|
300
|
+
gpt.replace({ '{role}': 'a senior code reviewer', '{language}': 'English' });
|
|
301
|
+
gpt.replaceKeyFromFile('{code}', './src/utils.js');
|
|
302
|
+
|
|
303
|
+
console.log(await gpt.message());
|
|
251
304
|
```
|
|
252
305
|
|
|
253
|
-
|
|
306
|
+
## 🧩 JSON Structured Output
|
|
254
307
|
|
|
255
|
-
|
|
256
|
-
- When set to `true`, includes the generated JSON schema in the system prompt
|
|
308
|
+
The `json` method forces the model to return a structured JSON response. You define the shape with an example object and optionally describe each field.
|
|
257
309
|
|
|
258
|
-
|
|
259
|
-
|
|
310
|
+
```javascript
|
|
311
|
+
await model.json(schemaExample, schemaDescription, options)
|
|
312
|
+
```
|
|
260
313
|
|
|
261
|
-
|
|
262
|
-
- When set to `true`, adds a technical note about JSON formatting requirements
|
|
263
|
-
- Specifically adds this instruction to the system prompt:
|
|
264
|
-
```
|
|
265
|
-
Output JSON Note: Escape all unescaped double quotes, backslashes, and ASCII control characters inside JSON strings, and ensure the output contains no comments.
|
|
266
|
-
```
|
|
267
|
-
- Helps prevent common JSON parsing errors
|
|
314
|
+
### Basic usage
|
|
268
315
|
|
|
269
|
-
|
|
316
|
+
```javascript
|
|
317
|
+
const model = ModelMix.new()
|
|
318
|
+
.gpt5mini()
|
|
319
|
+
.addText('Name and capital of 3 South American countries.');
|
|
320
|
+
|
|
321
|
+
const result = await model.json({ countries: [{ name: "", capital: "" }] });
|
|
322
|
+
console.log(result);
|
|
323
|
+
// { countries: [{ name: "Argentina", capital: "Buenos Aires" }, ...] }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Adding field descriptions
|
|
327
|
+
|
|
328
|
+
The second argument lets you describe each field so the model understands exactly what you expect:
|
|
270
329
|
|
|
271
330
|
```javascript
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
{ name: "Person's full name", age: "Age in years" },
|
|
276
|
-
{ addExample: true, addNote: true }
|
|
277
|
-
);
|
|
331
|
+
const model = ModelMix.new()
|
|
332
|
+
.gpt5mini()
|
|
333
|
+
.addText('Name and capital of 3 South American countries.');
|
|
278
334
|
|
|
279
|
-
// Only add the example, skip the technical note
|
|
280
335
|
const result = await model.json(
|
|
281
|
-
{
|
|
282
|
-
{},
|
|
283
|
-
{
|
|
336
|
+
{ countries: [{ name: "Argentina", capital: "BUENOS AIRES" }] },
|
|
337
|
+
{ countries: [{ name: "name of the country", capital: "capital of the country in uppercase" }] },
|
|
338
|
+
{ addNote: true }
|
|
284
339
|
);
|
|
340
|
+
console.log(result);
|
|
341
|
+
// { countries: [
|
|
342
|
+
// { name: "Brazil", capital: "BRASILIA" },
|
|
343
|
+
// { name: "Colombia", capital: "BOGOTA" },
|
|
344
|
+
// { name: "Chile", capital: "SANTIAGO" }
|
|
345
|
+
// ]}
|
|
346
|
+
```
|
|
285
347
|
|
|
286
|
-
|
|
348
|
+
The example values (like `"Argentina"` and `"BUENOS AIRES"`) show the model the expected format, while the descriptions clarify what each field should contain.
|
|
349
|
+
|
|
350
|
+
### Options
|
|
351
|
+
|
|
352
|
+
| Option | Default | Description |
|
|
353
|
+
| --- | --- | --- |
|
|
354
|
+
| `addSchema` | `true` | Include the generated JSON schema in the system prompt |
|
|
355
|
+
| `addExample` | `false` | Include the example object in the system prompt |
|
|
356
|
+
| `addNote` | `false` | Add a note about JSON escaping to prevent parsing errors |
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
// Include the example and the escaping note
|
|
287
360
|
const result = await model.json(
|
|
288
|
-
{
|
|
289
|
-
{},
|
|
290
|
-
{ addNote: true }
|
|
361
|
+
{ name: "John", age: 30, skills: ["JavaScript"] },
|
|
362
|
+
{ name: "Full name", age: "Age in years", skills: "List of programming languages" },
|
|
363
|
+
{ addExample: true, addNote: true }
|
|
291
364
|
);
|
|
292
365
|
```
|
|
293
366
|
|
|
@@ -311,16 +384,28 @@ Every response from `raw()` now includes a `tokens` object with the following st
|
|
|
311
384
|
}
|
|
312
385
|
```
|
|
313
386
|
|
|
387
|
+
### `lastRaw` — Access full response after `message()` or `json()`
|
|
388
|
+
|
|
389
|
+
After calling `message()` or `json()`, use `lastRaw` to access the complete response (tokens, thinking, tool calls, etc.). It has the same structure as `raw()`.
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
const text = await model.message();
|
|
393
|
+
console.log(model.lastRaw.tokens);
|
|
394
|
+
// { input: 122, output: 86, total: 541, cost: 0.000319 }
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
The `cost` field is the estimated cost in USD based on the model's pricing per 1M tokens (input/output). If the model is not found in the pricing table, `cost` will be `null`.
|
|
398
|
+
|
|
314
399
|
## 🐛 Enabling Debug Mode
|
|
315
400
|
|
|
316
401
|
To activate debug mode in ModelMix and view detailed request information, follow these two steps:
|
|
317
402
|
|
|
318
|
-
1. In the ModelMix constructor, include `debug
|
|
403
|
+
1. In the ModelMix constructor, include a `debug` level in the configuration:
|
|
319
404
|
|
|
320
405
|
```javascript
|
|
321
406
|
const mix = ModelMix.new({
|
|
322
407
|
config: {
|
|
323
|
-
debug:
|
|
408
|
+
debug: 4 // 0=silent, 1=minimal, 2=summary, 3=full (no truncate), 4=verbose (raw details)
|
|
324
409
|
// ... other configuration options ...
|
|
325
410
|
}
|
|
326
411
|
});
|
|
@@ -390,10 +475,14 @@ new ModelMix(args = { options: {}, config: {} })
|
|
|
390
475
|
- `new()`: `static` Creates a new `ModelMix`.
|
|
391
476
|
- `new()`: Creates a new `ModelMix` using instance setup.
|
|
392
477
|
|
|
478
|
+
- `setSystem(text)`: Sets the system prompt.
|
|
479
|
+
- `setSystemFromFile(filePath)`: Sets the system prompt from a file.
|
|
393
480
|
- `addText(text, config = { role: "user" })`: Adds a text message.
|
|
394
|
-
- `addTextFromFile(filePath, config = { role: "user" })`: Adds a text message from a file
|
|
481
|
+
- `addTextFromFile(filePath, config = { role: "user" })`: Adds a text message from a file.
|
|
395
482
|
- `addImage(filePath, config = { role: "user" })`: Adds an image message from a file path.
|
|
396
483
|
- `addImageFromUrl(url, config = { role: "user" })`: Adds an image message from URL.
|
|
484
|
+
- `replace(keyValues)`: Defines placeholder replacements for messages and system prompt.
|
|
485
|
+
- `replaceKeyFromFile(key, filePath)`: Defines a placeholder replacement with file contents as value.
|
|
397
486
|
- `message()`: Sends the message and returns the response.
|
|
398
487
|
- `raw()`: Sends the message and returns the complete response data including:
|
|
399
488
|
- `message`: The text response from the model
|
package/demo/custom.js
CHANGED
package/demo/demo.js
CHANGED
|
@@ -10,7 +10,7 @@ const mmix = new ModelMix({
|
|
|
10
10
|
system: 'You are {name} from Melmac.',
|
|
11
11
|
max_history: 2,
|
|
12
12
|
bottleneck: { maxConcurrent: 1 },
|
|
13
|
-
debug:
|
|
13
|
+
debug: 3,
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
16
|
|
|
@@ -33,7 +33,7 @@ gpt.replace({ '{animal}': 'cat' });
|
|
|
33
33
|
await gpt.json({ time: '24:00:00', message: 'Hello' }, { time: 'Time in format HH:MM:SS' });
|
|
34
34
|
|
|
35
35
|
console.log("\n" + '--------| sonnet45() |--------');
|
|
36
|
-
const claude = mmix.new({ config: { debug:
|
|
36
|
+
const claude = mmix.new({ config: { debug: 2 } }).sonnet45();
|
|
37
37
|
claude.addImageFromUrl('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC');
|
|
38
38
|
claude.addText('in one word, which is the main color of the image?');
|
|
39
39
|
const imageDescription = await claude.message();
|
package/demo/free.js
CHANGED
package/demo/gpt51.js
CHANGED
package/demo/grok.js
CHANGED
package/demo/images.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
process.loadEnvFile();
|
|
2
2
|
import { ModelMix } from '../index.js';
|
|
3
3
|
|
|
4
|
-
const model = ModelMix.new({ config: { max_history: 2, debug:
|
|
4
|
+
const model = ModelMix.new({ config: { max_history: 2, debug: 2 } }).maverick()
|
|
5
5
|
// model.addImageFromUrl('https://pbs.twimg.com/media/F6-GsjraAAADDGy?format=jpg');
|
|
6
6
|
model.addImage('./img.png');
|
|
7
7
|
model.addText('in one word, which is the main color of the image?');
|
package/demo/json.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
process.loadEnvFile();
|
|
2
2
|
import { ModelMix } from '../index.js';
|
|
3
3
|
|
|
4
|
-
const model = await ModelMix.new({ options: { max_tokens: 10000 }, config: { debug:
|
|
5
|
-
.
|
|
4
|
+
const model = await ModelMix.new({ options: { max_tokens: 10000 }, config: { debug: 3 } })
|
|
5
|
+
.gemini3flash()
|
|
6
6
|
// .gptOss()
|
|
7
7
|
// .scout({ config: { temperature: 0 } })
|
|
8
8
|
// .o4mini()
|
|
@@ -11,5 +11,17 @@ const model = await ModelMix.new({ options: { max_tokens: 10000 }, config: { deb
|
|
|
11
11
|
// .gemini25flash()
|
|
12
12
|
.addText("Name and capital of 3 South American countries.")
|
|
13
13
|
|
|
14
|
-
const jsonResult = await model.json({
|
|
15
|
-
|
|
14
|
+
const jsonResult = await model.json({
|
|
15
|
+
countries: [{
|
|
16
|
+
name: "Argentina",
|
|
17
|
+
capital: "BUENOS AIRES"
|
|
18
|
+
}]
|
|
19
|
+
}, {
|
|
20
|
+
countries: [{
|
|
21
|
+
name: "name of the country",
|
|
22
|
+
capital: "capital of the country in uppercase"
|
|
23
|
+
}]
|
|
24
|
+
}, { addNote: true });
|
|
25
|
+
|
|
26
|
+
console.log(jsonResult);
|
|
27
|
+
console.log(model.lastRaw.tokens);
|
package/demo/mcp-simple.js
CHANGED
|
@@ -92,7 +92,7 @@ async function simpleCalculator() {
|
|
|
92
92
|
async function contentGenerator() {
|
|
93
93
|
console.log('\n=== Content Generator ===');
|
|
94
94
|
|
|
95
|
-
const mmix = ModelMix.new({ config: { debug:
|
|
95
|
+
const mmix = ModelMix.new({ config: { debug: 2, max_history: 1 } })
|
|
96
96
|
.gemini3flash()
|
|
97
97
|
.setSystem('You are a creative assistant that can generate different types of content.');
|
|
98
98
|
|
package/demo/minimax.js
CHANGED
package/demo/parallel.js
CHANGED
package/demo/repl-powers.js
CHANGED
|
@@ -11,7 +11,7 @@ const isolate = new ivm.Isolate({ memoryLimit: 128 }); // 128MB máximo
|
|
|
11
11
|
async function replPowersExample() {
|
|
12
12
|
console.log('\n=== JavaScript REPL - Potencias de 2 ===\n');
|
|
13
13
|
const gptArgs = { options: { reasoning_effort: "none", verbosity: null } };
|
|
14
|
-
const mmix = ModelMix.new({ config: { debug:
|
|
14
|
+
const mmix = ModelMix.new({ config: { debug: 2, max_history: 10 } })
|
|
15
15
|
.gpt41nano()
|
|
16
16
|
.gpt52(gptArgs)
|
|
17
17
|
.gemini3flash()
|
package/demo/short.js
CHANGED
package/index.js
CHANGED
|
@@ -10,6 +10,81 @@ const { Client } = require("@modelcontextprotocol/sdk/client/index.js");
|
|
|
10
10
|
const { StdioClientTransport } = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
11
11
|
const { MCPToolsManager } = require('./mcp-tools');
|
|
12
12
|
|
|
13
|
+
// Pricing per 1M tokens: [input, output] in USD
|
|
14
|
+
// Based on provider pricing pages linked in README
|
|
15
|
+
const MODEL_PRICING = {
|
|
16
|
+
// OpenAI
|
|
17
|
+
'gpt-5.2': [1.75, 14.00],
|
|
18
|
+
'gpt-5.2-chat-latest': [1.75, 14.00],
|
|
19
|
+
'gpt-5.1': [1.25, 10.00],
|
|
20
|
+
'gpt-5': [1.25, 10.00],
|
|
21
|
+
'gpt-5-mini': [0.25, 2.00],
|
|
22
|
+
'gpt-5-nano': [0.05, 0.40],
|
|
23
|
+
'gpt-4.1': [2.00, 8.00],
|
|
24
|
+
'gpt-4.1-mini': [0.40, 1.60],
|
|
25
|
+
'gpt-4.1-nano': [0.10, 0.40],
|
|
26
|
+
// gptOss (Together/Groq/Cerebras/OpenRouter)
|
|
27
|
+
'openai/gpt-oss-120b': [0.15, 0.60],
|
|
28
|
+
'gpt-oss-120b': [0.15, 0.60],
|
|
29
|
+
'openai/gpt-oss-120b:free': [0, 0],
|
|
30
|
+
// Anthropic
|
|
31
|
+
'claude-opus-4-6': [5.00, 25.00],
|
|
32
|
+
'claude-opus-4-5-20251101': [5.00, 25.00],
|
|
33
|
+
'claude-opus-4-1-20250805': [15.00, 75.00],
|
|
34
|
+
'claude-sonnet-4-5-20250929': [3.00, 15.00],
|
|
35
|
+
'claude-sonnet-4-20250514': [3.00, 15.00],
|
|
36
|
+
'claude-3-5-haiku-20241022': [0.80, 4.00],
|
|
37
|
+
'claude-haiku-4-5-20251001': [1.00, 5.00],
|
|
38
|
+
// Google
|
|
39
|
+
'gemini-3-pro-preview': [2.00, 12.00],
|
|
40
|
+
'gemini-3-flash-preview': [0.50, 3.00],
|
|
41
|
+
'gemini-2.5-pro': [1.25, 10.00],
|
|
42
|
+
'gemini-2.5-flash': [0.30, 2.50],
|
|
43
|
+
// Grok
|
|
44
|
+
'grok-4-0709': [3.00, 15.00],
|
|
45
|
+
'grok-4-1-fast-reasoning': [0.20, 0.50],
|
|
46
|
+
'grok-4-1-fast-non-reasoning': [0.20, 0.50],
|
|
47
|
+
// Fireworks
|
|
48
|
+
'accounts/fireworks/models/deepseek-v3p2': [0.56, 1.68],
|
|
49
|
+
'accounts/fireworks/models/glm-4p7': [0.55, 2.19],
|
|
50
|
+
'accounts/fireworks/models/kimi-k2p5': [0.50, 2.80],
|
|
51
|
+
// MiniMax
|
|
52
|
+
'MiniMax-M2.1': [0.30, 1.20],
|
|
53
|
+
// Perplexity
|
|
54
|
+
'sonar': [1.00, 1.00],
|
|
55
|
+
'sonar-pro': [3.00, 15.00],
|
|
56
|
+
// Scout (Groq/Together/Cerebras)
|
|
57
|
+
'meta-llama/llama-4-scout-17b-16e-instruct': [0.11, 0.34],
|
|
58
|
+
'meta-llama/Llama-4-Scout-17B-16E-Instruct': [0.11, 0.34],
|
|
59
|
+
'llama-4-scout-17b-16e-instruct': [0.11, 0.34],
|
|
60
|
+
// Maverick (Groq/Together/Lambda)
|
|
61
|
+
'meta-llama/llama-4-maverick-17b-128e-instruct': [0.20, 0.60],
|
|
62
|
+
'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8': [0.20, 0.60],
|
|
63
|
+
'llama-4-maverick-17b-128e-instruct-fp8': [0.20, 0.60],
|
|
64
|
+
// Hermes3 (Lambda/OpenRouter)
|
|
65
|
+
'Hermes-3-Llama-3.1-405B-FP8': [0.80, 0.80],
|
|
66
|
+
'nousresearch/hermes-3-llama-3.1-405b:free': [0, 0],
|
|
67
|
+
// Qwen3 (Together/Cerebras)
|
|
68
|
+
'Qwen/Qwen3-235B-A22B-fp8-tput': [0.20, 0.60],
|
|
69
|
+
'qwen-3-32b': [0.20, 0.60],
|
|
70
|
+
// Kimi K2 (Together/Groq/OpenRouter)
|
|
71
|
+
'moonshotai/Kimi-K2-Instruct-0905': [1.00, 3.00],
|
|
72
|
+
'moonshotai/kimi-k2-instruct-0905': [1.00, 3.00],
|
|
73
|
+
'moonshotai/kimi-k2:free': [0, 0],
|
|
74
|
+
'moonshotai/Kimi-K2-Thinking': [1.00, 3.00],
|
|
75
|
+
'moonshotai/kimi-k2-thinking': [1.00, 3.00],
|
|
76
|
+
// Kimi K2.5 (Together/Fireworks/OpenRouter)
|
|
77
|
+
'moonshotai/Kimi-K2.5': [0.50, 2.80],
|
|
78
|
+
'moonshotai/kimi-k2.5': [0.50, 2.80],
|
|
79
|
+
// DeepSeek V3.2 (OpenRouter)
|
|
80
|
+
'deepseek/deepseek-v3.2': [0.56, 1.68],
|
|
81
|
+
// GLM 4.7 (OpenRouter/Cerebras)
|
|
82
|
+
'z-ai/glm-4.7': [0.55, 2.19],
|
|
83
|
+
'zai-glm-4.7': [0.55, 2.19],
|
|
84
|
+
// DeepSeek R1 (OpenRouter free)
|
|
85
|
+
'deepseek/deepseek-r1-0528:free': [0, 0],
|
|
86
|
+
};
|
|
87
|
+
|
|
13
88
|
class ModelMix {
|
|
14
89
|
|
|
15
90
|
constructor({ options = {}, config = {}, mix = {} } = {}) {
|
|
@@ -19,6 +94,7 @@ class ModelMix {
|
|
|
19
94
|
this.toolClient = {};
|
|
20
95
|
this.mcp = {};
|
|
21
96
|
this.mcpToolsManager = new MCPToolsManager();
|
|
97
|
+
this.lastRaw = null;
|
|
22
98
|
this.options = {
|
|
23
99
|
max_tokens: 8192,
|
|
24
100
|
temperature: 1, // 1 --> More creative, 0 --> More deterministic.
|
|
@@ -34,7 +110,7 @@ class ModelMix {
|
|
|
34
110
|
this.config = {
|
|
35
111
|
system: 'You are an assistant.',
|
|
36
112
|
max_history: 1, // Default max history
|
|
37
|
-
debug: 0, // 0=silent, 1=minimal, 2=readable summary, 3=full details
|
|
113
|
+
debug: 0, // 0=silent, 1=minimal, 2=readable summary, 3=full (no truncate), 4=verbose (raw details)
|
|
38
114
|
bottleneck: defaultBottleneckConfig,
|
|
39
115
|
roundRobin: false, // false=fallback mode, true=round robin rotation
|
|
40
116
|
...config
|
|
@@ -82,12 +158,19 @@ class ModelMix {
|
|
|
82
158
|
}
|
|
83
159
|
|
|
84
160
|
// debug logging helpers
|
|
85
|
-
static truncate(str, maxLen =
|
|
161
|
+
static truncate(str, maxLen = 1000) {
|
|
86
162
|
if (!str || typeof str !== 'string') return str;
|
|
87
163
|
return str.length > maxLen ? str.substring(0, maxLen) + '...' : str;
|
|
88
164
|
}
|
|
89
165
|
|
|
90
|
-
static
|
|
166
|
+
static calculateCost(modelKey, tokens) {
|
|
167
|
+
const pricing = MODEL_PRICING[modelKey];
|
|
168
|
+
if (!pricing) return null;
|
|
169
|
+
const [inputPerMillion, outputPerMillion] = pricing;
|
|
170
|
+
return (tokens.input * inputPerMillion / 1_000_000) + (tokens.output * outputPerMillion / 1_000_000);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static formatInputSummary(messages, system, debug = 2) {
|
|
91
174
|
const lastMessage = messages[messages.length - 1];
|
|
92
175
|
let inputText = '';
|
|
93
176
|
|
|
@@ -98,38 +181,39 @@ class ModelMix {
|
|
|
98
181
|
inputText = lastMessage.content;
|
|
99
182
|
}
|
|
100
183
|
|
|
101
|
-
const
|
|
102
|
-
const
|
|
184
|
+
const noTruncate = debug >= 3;
|
|
185
|
+
const systemStr = noTruncate ? (system || '') : ModelMix.truncate(system, 500);
|
|
186
|
+
const inputStr = noTruncate ? inputText : ModelMix.truncate(inputText, 1200);
|
|
103
187
|
const msgCount = `(${messages.length} msg${messages.length !== 1 ? 's' : ''})`;
|
|
104
188
|
|
|
105
|
-
return
|
|
189
|
+
return `| SYSTEM\n${systemStr}\n| INPUT ${msgCount}\n${inputStr}`;
|
|
106
190
|
}
|
|
107
191
|
|
|
108
192
|
static formatOutputSummary(result, debug) {
|
|
109
193
|
const parts = [];
|
|
194
|
+
const noTruncate = debug >= 3;
|
|
110
195
|
if (result.message) {
|
|
111
196
|
// Try to parse as JSON for better formatting
|
|
112
197
|
try {
|
|
113
198
|
const parsed = JSON.parse(result.message.trim());
|
|
114
199
|
// If it's valid JSON and debug >= 2, show it formatted
|
|
115
200
|
if (debug >= 2) {
|
|
116
|
-
parts.push(
|
|
201
|
+
parts.push(`| OUTPUT (JSON)\n${ModelMix.formatJSON(parsed)}`);
|
|
117
202
|
} else {
|
|
118
|
-
parts.push(
|
|
203
|
+
parts.push(`| OUTPUT\n${ModelMix.truncate(result.message, 1500)}`);
|
|
119
204
|
}
|
|
120
205
|
} catch (e) {
|
|
121
|
-
|
|
122
|
-
parts.push(`Output: ${ModelMix.truncate(result.message, 150)}`);
|
|
206
|
+
parts.push(`| OUTPUT\n${noTruncate ? result.message : ModelMix.truncate(result.message, 1500)}`);
|
|
123
207
|
}
|
|
124
208
|
}
|
|
125
209
|
if (result.think) {
|
|
126
|
-
parts.push(
|
|
210
|
+
parts.push(`| THINK\n${noTruncate ? result.think : ModelMix.truncate(result.think, 800)}`);
|
|
127
211
|
}
|
|
128
212
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
129
213
|
const toolNames = result.toolCalls.map(t => t.function?.name || t.name).join(', ');
|
|
130
|
-
parts.push(
|
|
214
|
+
parts.push(`| TOOLS\n${toolNames}`);
|
|
131
215
|
}
|
|
132
|
-
return parts.join('
|
|
216
|
+
return parts.join('\n');
|
|
133
217
|
}
|
|
134
218
|
|
|
135
219
|
attach(key, provider) {
|
|
@@ -759,7 +843,7 @@ class ModelMix {
|
|
|
759
843
|
const header = `\n${prefix} [${providerName}:${currentModelKey}] #${originalIndex + 1}${suffix}`;
|
|
760
844
|
|
|
761
845
|
if (currentConfig.debug >= 2) {
|
|
762
|
-
console.log(`${header}
|
|
846
|
+
console.log(`${header}\n${ModelMix.formatInputSummary(this.messages, currentConfig.system, currentConfig.debug)}`);
|
|
763
847
|
} else {
|
|
764
848
|
console.log(header);
|
|
765
849
|
}
|
|
@@ -772,6 +856,11 @@ class ModelMix {
|
|
|
772
856
|
|
|
773
857
|
const result = await providerInstance.create({ options: currentOptions, config: currentConfig });
|
|
774
858
|
|
|
859
|
+
// Calculate cost based on model pricing
|
|
860
|
+
if (result.tokens) {
|
|
861
|
+
result.tokens.cost = ModelMix.calculateCost(currentModelKey, result.tokens);
|
|
862
|
+
}
|
|
863
|
+
|
|
775
864
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
776
865
|
|
|
777
866
|
if (result.message) {
|
|
@@ -809,11 +898,14 @@ class ModelMix {
|
|
|
809
898
|
|
|
810
899
|
// debug level 2: Readable summary of output
|
|
811
900
|
if (currentConfig.debug >= 2) {
|
|
812
|
-
|
|
901
|
+
const tokenInfo = result.tokens
|
|
902
|
+
? ` ${result.tokens.input}→${result.tokens.output} tok` + (result.tokens.cost != null ? ` $${result.tokens.cost.toFixed(4)}` : '')
|
|
903
|
+
: '';
|
|
904
|
+
console.log(`✓${tokenInfo}\n${ModelMix.formatOutputSummary(result, currentConfig.debug).trim()}`);
|
|
813
905
|
}
|
|
814
906
|
|
|
815
|
-
// debug level
|
|
816
|
-
if (currentConfig.debug >=
|
|
907
|
+
// debug level 4 (verbose): Full response details
|
|
908
|
+
if (currentConfig.debug >= 4) {
|
|
817
909
|
if (result.response) {
|
|
818
910
|
console.log('\n[RAW RESPONSE]');
|
|
819
911
|
console.log(ModelMix.formatJSON(result.response));
|
|
@@ -832,6 +924,7 @@ class ModelMix {
|
|
|
832
924
|
|
|
833
925
|
if (currentConfig.debug >= 1) console.log('');
|
|
834
926
|
|
|
927
|
+
this.lastRaw = result;
|
|
835
928
|
return result;
|
|
836
929
|
|
|
837
930
|
} catch (error) {
|
|
@@ -1059,8 +1152,8 @@ class MixCustom {
|
|
|
1059
1152
|
|
|
1060
1153
|
options.messages = this.convertMessages(options.messages, config);
|
|
1061
1154
|
|
|
1062
|
-
// debug level
|
|
1063
|
-
if (config.debug >=
|
|
1155
|
+
// debug level 4 (verbose): Full request details
|
|
1156
|
+
if (config.debug >= 4) {
|
|
1064
1157
|
console.log('\n[REQUEST DETAILS]');
|
|
1065
1158
|
|
|
1066
1159
|
console.log('\n[CONFIG]');
|
|
@@ -1943,8 +2036,8 @@ class MixGoogle extends MixCustom {
|
|
|
1943
2036
|
};
|
|
1944
2037
|
|
|
1945
2038
|
try {
|
|
1946
|
-
// debug level
|
|
1947
|
-
if (config.debug >=
|
|
2039
|
+
// debug level 4 (verbose): Full request details
|
|
2040
|
+
if (config.debug >= 4) {
|
|
1948
2041
|
console.log('\n[REQUEST DETAILS - GOOGLE]');
|
|
1949
2042
|
|
|
1950
2043
|
console.log('\n[CONFIG]');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelmix",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"mcp",
|
|
12
|
+
"skill",
|
|
12
13
|
"llm",
|
|
13
14
|
"ai",
|
|
14
15
|
"model",
|
|
@@ -72,4 +73,4 @@
|
|
|
72
73
|
"test:live.mcp": "mocha test/live.mcp.js --timeout 60000 --require dotenv/config --require test/setup.js",
|
|
73
74
|
"test:tokens": "mocha test/tokens.test.js --timeout 10000 --require dotenv/config --require test/setup.js"
|
|
74
75
|
}
|
|
75
|
-
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: modelmix
|
|
3
|
+
description: Instructions for using the ModelMix Node.js library to interact with multiple AI LLM providers through a unified interface. Use when integrating AI models (OpenAI, Anthropic, Google, Groq, Perplexity, Grok, etc.), chaining models with fallback, getting structured JSON from LLMs, adding MCP tools, streaming responses, or managing multi-provider AI workflows in Node.js.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ModelMix Library Skill
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
ModelMix is a Node.js library that provides a unified fluent API to interact with multiple AI LLM providers. It handles automatic fallback between models, round-robin load balancing, structured JSON output, streaming, MCP tool integration, rate limiting, and token tracking.
|
|
11
|
+
|
|
12
|
+
Use this skill when:
|
|
13
|
+
- Integrating one or more AI models into a Node.js project
|
|
14
|
+
- Chaining models with automatic fallback
|
|
15
|
+
- Extracting structured JSON from LLMs
|
|
16
|
+
- Adding MCP tools or custom tools to models
|
|
17
|
+
- Working with templates and file-based prompts
|
|
18
|
+
|
|
19
|
+
Do NOT use this skill for:
|
|
20
|
+
- Python or non-Node.js projects
|
|
21
|
+
- Direct HTTP calls to LLM APIs (use ModelMix instead)
|
|
22
|
+
|
|
23
|
+
## Common Tasks
|
|
24
|
+
|
|
25
|
+
- [Get a text response](#get-a-text-response)
|
|
26
|
+
- [Get structured JSON](#get-structured-json)
|
|
27
|
+
- [Stream a response](#stream-a-response)
|
|
28
|
+
- [Get raw response (tokens, thinking, tool calls)](#get-raw-response-tokens-thinking-tool-calls)
|
|
29
|
+
- [Access full response after `message()` or `json()` with `lastRaw`](#access-full-response-after-message-or-json-with-lastraw)
|
|
30
|
+
- [Add images](#add-images)
|
|
31
|
+
- [Use templates with placeholders](#use-templates-with-placeholders)
|
|
32
|
+
- [Round-robin load balancing](#round-robin-load-balancing)
|
|
33
|
+
- [MCP integration (external tools)](#mcp-integration-external-tools)
|
|
34
|
+
- [Custom local tools (addTool)](#custom-local-tools-addtool)
|
|
35
|
+
- [Rate limiting (Bottleneck)](#rate-limiting-bottleneck)
|
|
36
|
+
- [Debug mode](#debug-mode)
|
|
37
|
+
- [Use free-tier models](#use-free-tier-models)
|
|
38
|
+
- [Conversation history](#conversation-history)
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install modelmix
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Core Concepts
|
|
47
|
+
|
|
48
|
+
### Import
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { ModelMix } from 'modelmix';
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Creating an Instance
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
// Static factory (preferred)
|
|
58
|
+
const model = ModelMix.new();
|
|
59
|
+
|
|
60
|
+
// With global options
|
|
61
|
+
const model = ModelMix.new({
|
|
62
|
+
options: { max_tokens: 4096, temperature: 0.7 },
|
|
63
|
+
config: {
|
|
64
|
+
system: "You are a helpful assistant.",
|
|
65
|
+
max_history: 5,
|
|
66
|
+
debug: 0, // 0=silent, 1=minimal, 2=summary, 3=full (no truncate), 4=verbose
|
|
67
|
+
roundRobin: false // false=fallback, true=rotate models
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Attaching Models (Fluent Chain)
|
|
73
|
+
|
|
74
|
+
Chain shorthand methods to attach providers. First model is primary; others are fallbacks:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const model = ModelMix.new()
|
|
78
|
+
.sonnet45() // primary
|
|
79
|
+
.gpt5mini() // fallback 1
|
|
80
|
+
.gemini3flash() // fallback 2
|
|
81
|
+
.addText("Hello!")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If `sonnet45` fails, it automatically tries `gpt5mini`, then `gemini3flash`.
|
|
85
|
+
|
|
86
|
+
## Available Model Shorthands
|
|
87
|
+
|
|
88
|
+
- **OpenAI**: `gpt52` `gpt51` `gpt5` `gpt5mini` `gpt5nano` `gpt41` `gpt41mini` `gpt41nano`
|
|
89
|
+
- **Anthropic**: `opus46` `opus45` `sonnet45` `sonnet4` `haiku45` `haiku35` (thinking variants: add `think` suffix)
|
|
90
|
+
- **Google**: `gemini3pro` `gemini3flash` `gemini25pro` `gemini25flash`
|
|
91
|
+
- **Grok**: `grok4` `grok41` (thinking variant available)
|
|
92
|
+
- **Perplexity**: `sonar` `sonarPro`
|
|
93
|
+
- **Groq**: `scout` `maverick`
|
|
94
|
+
- **Together**: `qwen3` `kimiK2`
|
|
95
|
+
- **Multi-provider**: `deepseekR1` `gptOss`
|
|
96
|
+
- **MiniMax**: `minimaxM21`
|
|
97
|
+
- **Fireworks**: `deepseekV32` `GLM47`
|
|
98
|
+
|
|
99
|
+
Each method is called as `mix.methodName()` and accepts optional `{ options, config }` to override per-model settings.
|
|
100
|
+
|
|
101
|
+
## Common Tasks
|
|
102
|
+
|
|
103
|
+
### Get a text response
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
const answer = await ModelMix.new()
|
|
107
|
+
.gpt5mini()
|
|
108
|
+
.addText("What is the capital of France?")
|
|
109
|
+
.message();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Get structured JSON
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const result = await ModelMix.new()
|
|
116
|
+
.gpt5mini()
|
|
117
|
+
.addText("Name and capital of 3 South American countries.")
|
|
118
|
+
.json(
|
|
119
|
+
{ countries: [{ name: "", capital: "" }] }, // schema example
|
|
120
|
+
{ countries: [{ name: "country name", capital: "in uppercase" }] }, // descriptions
|
|
121
|
+
{ addNote: true } // options
|
|
122
|
+
);
|
|
123
|
+
// result.countries → [{ name: "Brazil", capital: "BRASILIA" }, ...]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`json()` signature: `json(schemaExample, schemaDescription?, { addSchema, addExample, addNote }?)`
|
|
127
|
+
|
|
128
|
+
### Stream a response
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
await ModelMix.new()
|
|
132
|
+
.gpt5mini()
|
|
133
|
+
.addText("Tell me a story.")
|
|
134
|
+
.stream(({ delta, message }) => {
|
|
135
|
+
process.stdout.write(delta);
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Get raw response (tokens, thinking, tool calls)
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
const raw = await ModelMix.new()
|
|
143
|
+
.sonnet45think()
|
|
144
|
+
.addText("Solve this step by step: 2+2*3")
|
|
145
|
+
.raw();
|
|
146
|
+
// raw.message, raw.think, raw.tokens, raw.toolCalls, raw.response
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Access full response after `message()` or `json()` with `lastRaw`
|
|
150
|
+
|
|
151
|
+
After calling `message()`, `json()`, `block()`, or `stream()`, use `lastRaw` to access the complete response (tokens, thinking, tool calls, etc.). It has the same structure as `raw()`.
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
const model = ModelMix.new().gpt5mini().addText("Hello!");
|
|
155
|
+
const text = await model.message();
|
|
156
|
+
console.log(model.lastRaw.tokens);
|
|
157
|
+
// { input: 122, output: 86, total: 541, cost: 0.000319 }
|
|
158
|
+
console.log(model.lastRaw.think); // reasoning content (if available)
|
|
159
|
+
console.log(model.lastRaw.response); // raw API response
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Add images
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const model = ModelMix.new().sonnet45();
|
|
166
|
+
model.addImage('./photo.jpg'); // from file
|
|
167
|
+
model.addImageFromUrl('https://example.com/img.png'); // from URL
|
|
168
|
+
model.addText('Describe this image.');
|
|
169
|
+
const description = await model.message();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Use templates with placeholders
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
const model = ModelMix.new().gpt5mini();
|
|
176
|
+
model.setSystemFromFile('./prompts/system.md');
|
|
177
|
+
model.addTextFromFile('./prompts/task.md');
|
|
178
|
+
model.replace({
|
|
179
|
+
'{role}': 'data analyst',
|
|
180
|
+
'{language}': 'Spanish'
|
|
181
|
+
});
|
|
182
|
+
model.replaceKeyFromFile('{code}', './src/utils.js');
|
|
183
|
+
console.log(await model.message());
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Round-robin load balancing
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
const pool = ModelMix.new({ config: { roundRobin: true } })
|
|
190
|
+
.gpt5mini()
|
|
191
|
+
.sonnet45()
|
|
192
|
+
.gemini3flash();
|
|
193
|
+
|
|
194
|
+
// Each call rotates to the next model
|
|
195
|
+
const r1 = await pool.new().addText("Request 1").message();
|
|
196
|
+
const r2 = await pool.new().addText("Request 2").message();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### MCP integration (external tools)
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
const model = ModelMix.new({ config: { max_history: 10 } }).gpt5nano();
|
|
203
|
+
model.setSystem('You are an assistant. Today is ' + new Date().toISOString());
|
|
204
|
+
await model.addMCP('@modelcontextprotocol/server-brave-search');
|
|
205
|
+
model.addText('Use Internet: What is the latest news about AI?');
|
|
206
|
+
console.log(await model.message());
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Requires `BRAVE_API_KEY` in `.env` for Brave Search MCP.
|
|
210
|
+
|
|
211
|
+
### Custom local tools (addTool)
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const model = ModelMix.new({ config: { max_history: 10 } }).gpt5mini();
|
|
215
|
+
|
|
216
|
+
model.addTool({
|
|
217
|
+
name: "get_weather",
|
|
218
|
+
description: "Get weather for a city",
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: { city: { type: "string" } },
|
|
222
|
+
required: ["city"]
|
|
223
|
+
}
|
|
224
|
+
}, async ({ city }) => {
|
|
225
|
+
return `The weather in ${city} is sunny, 25C`;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
model.addText("What's the weather in Tokyo?");
|
|
229
|
+
console.log(await model.message());
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Rate limiting (Bottleneck)
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
const model = ModelMix.new({
|
|
236
|
+
config: {
|
|
237
|
+
bottleneck: {
|
|
238
|
+
maxConcurrent: 4,
|
|
239
|
+
minTime: 1000
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}).gpt5mini();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Debug mode
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
const model = ModelMix.new({
|
|
249
|
+
config: { debug: 2 } // 0=silent, 1=minimal, 2=summary, 3=full (no truncate), 4=verbose
|
|
250
|
+
}).gpt5mini();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
For full debug output, also set the env: `DEBUG=ModelMix* node script.js`
|
|
254
|
+
|
|
255
|
+
### Use free-tier models
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// These use providers with free quotas (OpenRouter, Groq, Cerebras)
|
|
259
|
+
const model = ModelMix.new()
|
|
260
|
+
.gptOss()
|
|
261
|
+
.kimiK2()
|
|
262
|
+
.deepseekR1()
|
|
263
|
+
.hermes3()
|
|
264
|
+
.addText("What is the capital of France?");
|
|
265
|
+
console.log(await model.message());
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Conversation history
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const chat = ModelMix.new({ config: { max_history: 10 } }).gpt5mini();
|
|
272
|
+
chat.addText("My name is Martin.");
|
|
273
|
+
await chat.message();
|
|
274
|
+
chat.addText("What's my name?");
|
|
275
|
+
const reply = await chat.message(); // "Martin"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Agent Usage Rules
|
|
279
|
+
|
|
280
|
+
- Always check `package.json` for `modelmix` before running `npm install`.
|
|
281
|
+
- Use `ModelMix.new()` static factory to create instances (not `new ModelMix()`).
|
|
282
|
+
- Store API keys in `.env` and load with `dotenv/config` or `process.loadEnvFile()`. Never hardcode keys.
|
|
283
|
+
- Chain models for resilience: primary model first, fallbacks after.
|
|
284
|
+
- When using MCP tools or `addTool()`, set `max_history` to at least 3.
|
|
285
|
+
- Use `.json()` for structured output instead of parsing text manually.
|
|
286
|
+
- Use `.message()` for simple text, `.raw()` when you need tokens/thinking/toolCalls.
|
|
287
|
+
- For thinking models, append `think` to the method name (e.g. `sonnet45think()`).
|
|
288
|
+
- Template placeholders use `{key}` syntax in both system prompts and user messages.
|
|
289
|
+
- The library uses CommonJS internally (`require`) but supports ESM import via `{ ModelMix }`.
|
|
290
|
+
- Available provider Mix classes for custom setups: `MixOpenAI`, `MixAnthropic`, `MixGoogle`, `MixPerplexity`, `MixGroq`, `MixTogether`, `MixGrok`, `MixOpenRouter`, `MixOllama`, `MixLMStudio`, `MixCustom`, `MixCerebras`, `MixFireworks`, `MixMiniMax`.
|
|
291
|
+
|
|
292
|
+
## API Quick Reference
|
|
293
|
+
|
|
294
|
+
| Method | Returns | Description |
|
|
295
|
+
| --- | --- | --- |
|
|
296
|
+
| `.addText(text)` | `this` | Add user message |
|
|
297
|
+
| `.addTextFromFile(path)` | `this` | Add user message from file |
|
|
298
|
+
| `.setSystem(text)` | `this` | Set system prompt |
|
|
299
|
+
| `.setSystemFromFile(path)` | `this` | Set system prompt from file |
|
|
300
|
+
| `.addImage(path)` | `this` | Add image from file |
|
|
301
|
+
| `.addImageFromUrl(url)` | `this` | Add image from URL or data URI |
|
|
302
|
+
| `.replace({})` | `this` | Set placeholder replacements |
|
|
303
|
+
| `.replaceKeyFromFile(key, path)` | `this` | Replace placeholder with file content |
|
|
304
|
+
| `.message()` | `Promise<string>` | Get text response |
|
|
305
|
+
| `.json(example, desc?, opts?)` | `Promise<object>` | Get structured JSON |
|
|
306
|
+
| `.raw()` | `Promise<{message, think, toolCalls, tokens, response}>` | Full response |
|
|
307
|
+
| `.lastRaw` | `object \| null` | Full response from last `message()`/`json()`/`block()`/`stream()` call |
|
|
308
|
+
| `.stream(callback)` | `Promise` | Stream response |
|
|
309
|
+
| `.block()` | `Promise<string>` | Extract code block from response |
|
|
310
|
+
| `.addMCP(package)` | `Promise` | Add MCP server tools |
|
|
311
|
+
| `.addTool(def, callback)` | `this` | Register custom local tool |
|
|
312
|
+
| `.addTools([{tool, callback}])` | `this` | Register multiple tools |
|
|
313
|
+
| `.removeTool(name)` | `this` | Remove a tool |
|
|
314
|
+
| `.listTools()` | `{local, mcp}` | List registered tools |
|
|
315
|
+
| `.new()` | `ModelMix` | Clone instance sharing models |
|
|
316
|
+
| `.attach(key, provider)` | `this` | Attach custom provider |
|
|
317
|
+
|
|
318
|
+
## References
|
|
319
|
+
|
|
320
|
+
- [GitHub Repository](https://github.com/clasen/ModelMix)
|