modelmix 4.4.11 → 4.4.14
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 +11 -11
- package/demo/custom.js +1 -2
- package/demo/demo.js +1 -1
- package/demo/fallback.js +1 -3
- package/demo/fireworks.js +1 -1
- package/demo/free.js +1 -1
- package/demo/gemini.js +2 -2
- package/demo/gpt51.js +2 -1
- package/demo/grok.js +1 -2
- package/demo/groq.js +1 -2
- package/demo/images.js +1 -1
- package/demo/json.js +1 -1
- package/demo/mcp-simple.js +1 -1
- package/demo/mcp-tools.js +1 -1
- package/demo/mcp.js +1 -1
- package/demo/minimax.js +1 -3
- package/demo/package-lock.json +11 -1
- package/demo/package.json +2 -1
- package/demo/parallel-strategy.js +1 -1
- package/demo/parallel.js +1 -2
- package/demo/repl-powers.js +1 -1
- package/demo/rlm-basic.js +1 -1
- package/demo/rlm-fast.js +1 -1
- package/demo/rlm-simple.js +1 -1
- package/demo/round-robin.js +1 -1
- package/demo/save_the_cat-spanish.md +109 -0
- package/demo/short.js +1 -2
- package/demo/story.md +15 -0
- package/demo/stream.js +1 -1
- package/demo/together.js +1 -2
- package/demo/tokens-simple.js +1 -1
- package/demo/tokens.js +1 -1
- package/demo/verbose.js +1 -1
- package/index.js +9 -2
- package/package.json +1 -1
- package/skills/modelmix/SKILL.md +183 -78
package/README.md
CHANGED
|
@@ -42,8 +42,8 @@ For environment variables, use `dotenv` or Node's built-in `process.loadEnvFile(
|
|
|
42
42
|
3. **Create and configure your models**:
|
|
43
43
|
|
|
44
44
|
```javascript
|
|
45
|
-
process.loadEnvFile();
|
|
46
45
|
import { ModelMix } from 'modelmix';
|
|
46
|
+
try { process.loadEnvFile(); } catch {}
|
|
47
47
|
|
|
48
48
|
// Get structured JSON responses
|
|
49
49
|
const model = ModelMix.new()
|
|
@@ -148,8 +148,8 @@ Here's a comprehensive list of available methods:
|
|
|
148
148
|
| `opus45[think]()` | Anthropic | claude-opus-4-5-20251101 | [\$5.00 / \$25.00][2] |
|
|
149
149
|
| `sonnet46[think]()`| Anthropic | claude-sonnet-4-6 | [\$3.00 / \$15.00][2] |
|
|
150
150
|
| `sonnet45[think]()`| Anthropic | claude-sonnet-4-5-20250929 | [\$3.00 / \$15.00][2] |
|
|
151
|
-
| `haiku35()` | Anthropic | claude-3-5-haiku-20241022 | [\$0.80 / \$4.00][2] |
|
|
152
151
|
| `haiku45[think]()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00 / \$5.00][2] |
|
|
152
|
+
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00 / \$12.00][3] |
|
|
153
153
|
| `gemini3pro()` | Google | gemini-3-pro-preview | [\$2.00 / \$12.00][3] |
|
|
154
154
|
| `gemini3flash()` | Google | gemini-3-flash-preview | [\$0.50 / \$3.00][3] |
|
|
155
155
|
| `gemini25pro()` | Google | gemini-2.5-pro | [\$1.25 / \$10.00][3] |
|
|
@@ -161,8 +161,6 @@ Here's a comprehensive list of available methods:
|
|
|
161
161
|
| `minimaxM25()` | MiniMax | MiniMax-M2.5 | [\$0.30 / \$1.20][9] |
|
|
162
162
|
| `sonar()` | Perplexity | sonar | [\$1.00 / \$1.00][4] |
|
|
163
163
|
| `sonarPro()` | Perplexity | sonar-pro | [\$3.00 / \$15.00][4] |
|
|
164
|
-
| `scout()` | Groq | Llama-4-Scout-17B-16E-Instruct | [\$0.11 / \$0.34][5] |
|
|
165
|
-
| `maverick()` | Groq | Maverick-17B-128E-Instruct-FP8 | [\$0.20 / \$0.60][5] |
|
|
166
164
|
| `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80 / \$0.80][8] |
|
|
167
165
|
| `qwen3()` | Together | Qwen3-235B-A22B-fp8-tput | [\$0.20 / \$0.60][7] |
|
|
168
166
|
| `kimiK2()` | Together | Kimi-K2-Instruct | [\$1.00 / \$3.00][7] |
|
|
@@ -345,11 +343,11 @@ Descriptions support **descriptor objects** with `description`, `required`, `enu
|
|
|
345
343
|
|
|
346
344
|
```javascript
|
|
347
345
|
const result = await model.json(
|
|
348
|
-
{ name: '
|
|
346
|
+
{ name: 'Martin', age: 22, sex: 'male' },
|
|
349
347
|
{
|
|
350
348
|
name: { description: 'Name of the actor', required: false },
|
|
351
|
-
age: 'Age of the actor',
|
|
352
|
-
sex: { description: 'Gender', enum: ['
|
|
349
|
+
age: 'Age of the actor', // string still works
|
|
350
|
+
sex: { description: 'Gender', enum: ['male', 'female', null], default: null }
|
|
353
351
|
}
|
|
354
352
|
);
|
|
355
353
|
```
|
|
@@ -406,7 +404,9 @@ Every response from `raw()` now includes a `tokens` object with the following st
|
|
|
406
404
|
tokens: {
|
|
407
405
|
input: 150, // Number of tokens in the prompt/input
|
|
408
406
|
output: 75, // Number of tokens in the completion/output
|
|
409
|
-
total: 225
|
|
407
|
+
total: 225, // Total tokens used (input + output)
|
|
408
|
+
cost: 0.0012, // Estimated cost in USD (null if model not in pricing table)
|
|
409
|
+
speed: 42 // Output tokens per second (int)
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
```
|
|
@@ -418,10 +418,10 @@ After calling `message()` or `json()`, use `lastRaw` to access the complete resp
|
|
|
418
418
|
```javascript
|
|
419
419
|
const text = await model.message();
|
|
420
420
|
console.log(model.lastRaw.tokens);
|
|
421
|
-
// { input: 122, output: 86, total: 541, cost: 0.000319 }
|
|
421
|
+
// { input: 122, output: 86, total: 541, cost: 0.000319, speed: 38 }
|
|
422
422
|
```
|
|
423
423
|
|
|
424
|
-
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`.
|
|
424
|
+
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`. The `speed` field is the generation speed measured in output tokens per second (integer).
|
|
425
425
|
|
|
426
426
|
## 🐛 Enabling Debug Mode
|
|
427
427
|
|
|
@@ -515,7 +515,7 @@ new ModelMix(args = { options: {}, config: {} })
|
|
|
515
515
|
- `message`: The text response from the model
|
|
516
516
|
- `think`: Reasoning/thinking content (if available)
|
|
517
517
|
- `toolCalls`: Array of tool calls made by the model (if any)
|
|
518
|
-
- `tokens`: Object with `input`, `output`,
|
|
518
|
+
- `tokens`: Object with `input`, `output`, `total` token counts, `cost` (USD), and `speed` (output tokens/sec)
|
|
519
519
|
- `response`: The raw API response
|
|
520
520
|
- `stream(callback)`: Sends the message and streams the response, invoking the callback with each streamed part.
|
|
521
521
|
- `json(schemaExample, descriptions = {}, options = {})`: Forces the model to return a response in a specific JSON format.
|
package/demo/custom.js
CHANGED
package/demo/demo.js
CHANGED
package/demo/fallback.js
CHANGED
package/demo/fireworks.js
CHANGED
package/demo/free.js
CHANGED
package/demo/gemini.js
CHANGED
package/demo/gpt51.js
CHANGED
package/demo/grok.js
CHANGED
package/demo/groq.js
CHANGED
package/demo/images.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
process.loadEnvFile();
|
|
2
1
|
import { ModelMix } from '../index.js';
|
|
2
|
+
try { process.loadEnvFile(); } catch {}
|
|
3
3
|
|
|
4
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');
|
package/demo/json.js
CHANGED
package/demo/mcp-simple.js
CHANGED
package/demo/mcp-tools.js
CHANGED
package/demo/mcp.js
CHANGED
package/demo/minimax.js
CHANGED
package/demo/package-lock.json
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"dotenv": "^17.2.3",
|
|
13
13
|
"isolated-vm": "^6.0.2",
|
|
14
|
-
"lemonlog": "^1.1.4"
|
|
14
|
+
"lemonlog": "^1.1.4",
|
|
15
|
+
"pathmix": "^1.0.0"
|
|
15
16
|
}
|
|
16
17
|
},
|
|
17
18
|
".api/apis/pplx": {
|
|
@@ -290,6 +291,15 @@
|
|
|
290
291
|
"wrappy": "1"
|
|
291
292
|
}
|
|
292
293
|
},
|
|
294
|
+
"node_modules/pathmix": {
|
|
295
|
+
"version": "1.0.0",
|
|
296
|
+
"resolved": "https://registry.npmjs.org/pathmix/-/pathmix-1.0.0.tgz",
|
|
297
|
+
"integrity": "sha512-oLbvoOKuyV6TjkKLEYqH5O+q+d+qZwtRNzMrBI93IsCYN0liDw8W8aZq3BPvIaF4jJU+igeO/1p6lCwFfy8E5Q==",
|
|
298
|
+
"license": "ISC",
|
|
299
|
+
"engines": {
|
|
300
|
+
"node": ">=16.0.0"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
293
303
|
"node_modules/prebuild-install": {
|
|
294
304
|
"version": "7.1.3",
|
|
295
305
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
package/demo/package.json
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* This is GENERIC - works with any data structure, not hardcoded for specific use cases.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
process.loadEnvFile();
|
|
27
26
|
import { ModelMix } from '../index.js';
|
|
27
|
+
try { process.loadEnvFile(); } catch {}
|
|
28
28
|
|
|
29
29
|
console.log('🧬 ModelMix - RLM (Recursive Language Models) Demo');
|
|
30
30
|
console.log('🎯 Generic parallel strategy with environment variables\n');
|
package/demo/parallel.js
CHANGED
package/demo/repl-powers.js
CHANGED
package/demo/rlm-basic.js
CHANGED
package/demo/rlm-fast.js
CHANGED
package/demo/rlm-simple.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
process.loadEnvFile();
|
|
2
1
|
import { ModelMix } from '../index.js';
|
|
3
2
|
import ivm from 'isolated-vm';
|
|
3
|
+
try { process.loadEnvFile(); } catch {}
|
|
4
4
|
|
|
5
5
|
console.log('🧬 ModelMix - RLM (Recursive Language Model) Demo');
|
|
6
6
|
console.log('📄 Basado en: https://arxiv.org/html/2512.24601v1\n');
|
package/demo/round-robin.js
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
## MONSTER IN THE HOUSE
|
|
2
|
+
|
|
3
|
+
¿Qué tienen en común _Jaws_, _The Exorcist_ y _Alien_? Son ejemplos del género que llamo “Monstruo en la casa”. Se basa en dos elementos: un monstruo y una casa. Al meter personas dentro, intentando matar al monstruo, surge una historia **primitiva** y universal: no… te… dejes… devorar.
|
|
4
|
+
|
|
5
|
+
Por eso este género ha generado tantos éxitos y franquicias. _Jurassic Park_; las series _Nightmare On Elm Street_, _Friday the 13th_ y _Scream_; _Tremors_ y sus secuelas; y las historias de casas embrujadas entran aquí. Incluso sin lo sobrenatural, como _Fatal Attraction_ (con Glenn Close como el “monstruo”), funciona igual. Películas como _Arachnophobia_, _Lake Placid_ y _Deep Blue Sea_ muestran que si no entiendes sus reglas, fallas.
|
|
6
|
+
Para mí, las reglas son simples. La “casa” debe ser un espacio confinado: un pueblo costero, una nave espacial, un Disneyland futurista con dinosaurios o una familia. Debe cometerse un pecado —casi siempre la codicia (económica o sexual)— que detona la creación de un monstruo sobrenatural, un ángel vengador que mata a los culpables y perdona a quienes entienden su falta. El resto es “correr y esconderse”. El trabajo del guionista es aportar un giro al monstruo, sus poderes y la forma de asustar (“¡Bú!”).
|
|
7
|
+
|
|
8
|
+
Un mal ejemplo es _Arachnophobia_, con Jeff Daniels y John Goodman. El “monstruo” es una araña pequeña: poco sobrenatural y no tan aterradora; la pisas y muere. Además, no hay “casa”: los personajes pueden irse cuando quieran. Sin encierro, no hay tensión. Al romper las reglas de “Monstruo en la Casa”, la película queda en un híbrido: ¿comedia o drama?, ¿de verdad busca asustar?
|
|
9
|
+
Ningún género está agotado. Siempre se puede crear uno nuevo, pero debe tener un giro fresco y romper el cliché: “Danos lo mismo… pero distinto”. Quien crea que el género *Monstruo en la casa* ya no ofrece nada, piense en el mito del Minotauro: un gran monstruo (mitad hombre/mitad toro) y una gran casa (un laberinto donde envían a morir a los condenados). Aun así, nadie imaginó variaciones modernas como Glenn Close con un mal permanente y un conejo hervido.
|
|
10
|
+
|
|
11
|
+
## EL VELLOCINO DE ORO
|
|
12
|
+
|
|
13
|
+
El mito de la búsqueda sigue siendo de los más efectivos. Si tu guion es una *Road Movie*, aplica las reglas de “El Vellocino de Oro”, inspirado en Jasón y los Argonautas: un héroe sale a la ruta por una cosa y termina descubriendo otra —a sí mismo. Así, _Wizard Of Oz_, _Planes, Trains and Automobiles_, _Star Wars_, _Road Trip_ y _Back to the Future_ son, en esencia, la misma historia.
|
|
14
|
+
|
|
15
|
+
¿Da miedo, no?
|
|
16
|
+
Como en cualquier historia, los hitos de *El vellocino de oro* son las personas y los incidentes que el héroe encuentra en el camino. Aunque sea episódico y parezca desconectado, debe estar unido por un tema: el crecimiento interno. El efecto de cada incidente en el héroe es la trama; el progreso real no es la distancia recorrida, sino cómo cambia. Tu tarea es hacer que esos hitos tengan significado para el héroe.
|
|
17
|
+
Estoy trabajando en una historia de “Vellocino de Oro” con mi socio de escritura, Sheldon Bull, y hemos analizado varias películas del género. Como la nuestra es una comedia, revisamos _Planes, Trains and Automobiles_ y hablamos de las dinámicas de _Rain Man_, _Road Trip_ y _Animal House_ para entender mejor la premisa: un chico vuelve a casa tras ser expulsado injustamente de una escuela militar y descubre que sus padres se mudaron sin avisarle. Es, en esencia, “_Home Alone_ en la carretera”.
|
|
18
|
+
|
|
19
|
+
Los cambios no se enfocan en la aventura, sino en lo que cada incidente significa para el protagonista: las escenas deben marcar hitos de crecimiento. Al final, como en _The Odyssey_ y _Gulliver’s Travels_, lo que hace funcionar la historia no son los hechos, sino lo que el héroe aprende de ellos.
|
|
20
|
+
Este género incluye las películas de atracos. Cualquier búsqueda, misión o “tesoro en un castillo” emprendida por una persona o un grupo entra en la categoría del **Vellocino de Oro** y sigue las mismas reglas. A menudo, la misión pasa a segundo plano frente a descubrimientos personales; los giros importan menos que el sentido que deja el atraco, como muestran _Ocean’s Eleven_, _The Dirty Dozen_ y _The Magnificent Seven_.
|
|
21
|
+
|
|
22
|
+
## FUERA DE LA BOTELLA
|
|
23
|
+
|
|
24
|
+
“¡Ojalá tuviera mi propio dinero!” dice Preston Waters en _Blank Check_, película que Colby Carr y yo escribimos y vendimos a Disney. Pronto tendrá un millón de dólares y lo gastará sin control. Este tipo de cumplimiento de deseos es común porque refleja una parte central de la psicología humana: “Ojalá tuviera ________” es una de las plegarias más repetidas. Las historias “¿qué pasaría si...?” que explotan esas fantasías son primitivas, fáciles de entender, abundan y suelen funcionar.
|
|
25
|
+
_Bruce Almighty_ ejemplifica el género “Out of the Bottle”. La magia no tiene que venir de Dios: puede provenir de un objeto (_The Mask_), un auto (_The Love Bug_), una fórmula (_Love Potion #9_) o una sustancia (_Flubber_).
|
|
26
|
+
|
|
27
|
+
El nombre sugiere a un genio que concede deseos, pero no requiere magia literal. En _Blank Check_ no hay hechizo: por suerte o circunstancias, el deseo se cumple. Como simpatizamos con el protagonista y creemos que lo merece, su vida empieza a cambiar.
|
|
28
|
+
|
|
29
|
+
La otra cara del mismo esquema es la maldición: historias de castigo o lección. _Liar, Liar_ es un ejemplo, con la misma premisa.
|
|
30
|
+
Un niño desea que su padre, un abogado mentiroso, diga solo la verdad, y sucede: de pronto Jim Carrey no puede mentir justo el día de un caso clave. Para salir adelante debe cambiar y madurar, y así obtiene lo que quería: el respeto de su esposa e hijo. Otras historias de “escarmiento” incluyen _Freaky Friday_, _All Of Me_ y _Groundhog Day_.
|
|
31
|
+
|
|
32
|
+
Las reglas de **Out of the Bottle** son: en relatos de cumplimiento de deseos, el héroe debe ser un “Cenicienta” oprimido por su entorno, de modo que el público quiera que al fin sea feliz. Pero tampoco queremos verlo triunfar demasiado tiempo. Al final debe aprender que la magia no lo es todo y que es mejor ser como la audiencia; por eso la historia debe cerrar con una lección moral.
|
|
33
|
+
Si es una versión de **ajuste de cuentas** de *Out of the Bottle*, se invierte la premisa: el protagonista merece una lección, pero tiene algo rescatable. Esto es más difícil y requiere una escena inicial de *Save the Cat* que muestre que, aunque sea un patán, vale la pena salvarlo. A lo largo de la historia recibe el “beneficio” de la magia (aunque sea una maldición) y al final triunfa.
|
|
34
|
+
|
|
35
|
+
## TIPO CON UN PROBLEMA
|
|
36
|
+
|
|
37
|
+
Este género se define así: “Una persona común se ve envuelta en circunstancias extraordinarias”. Nos atrae porque nos identificamos con alguien “normal” desde el inicio. En un comienzo de “día cualquiera”, irrumpe algo fuera de lo común: terroristas toman un edificio (*Die Hard*), llegan nazis (*Schindler’s List*), aparece un robot del futuro que amenaza a la protagonista y a su hijo no nacido (*The Terminator*), o un barco choca con un iceberg y se hunde sin botes suficientes (*Titanic*).
|
|
38
|
+
Estos son problemas grandes y primarios.
|
|
39
|
+
|
|
40
|
+
¿Como los enfrenta una persona común?
|
|
41
|
+
|
|
42
|
+
Como *Monster in the House*, este género tiene dos partes: un tipo cualquiera (hombre o mujer) y un problema que debe vencer sacando fuerzas de sí mismo. Mientras más común sea el protagonista, más grande debe ser el desafío.
|
|
43
|
+
|
|
44
|
+
En *Breakdown*, Kurt Russell no tiene poderes ni entrenamiento; su objetivo es simple y universal: salvar a la esposa que ama. Más que la habilidad del héroe, lo que sostiene la historia es el tamaño del reto: cuanto peor sea el villano, mayor será el heroísmo. El protagonista triunfa al usar su individualidad para superar fuerzas mucho más poderosas.
|
|
45
|
+
|
|
46
|
+
## RITES OF PASSAGE
|
|
47
|
+
Recuerda la pubertad incómoda y a esa chica que te gustaba y ni sabía que existías. O la fiesta de tus 40, cuando tu esposo te pidió el divorcio. Estas transiciones nos tocan porque casi todos las hemos vivido. Las historias de “dolores de crecimiento” se sienten intensas por ser etapas sensibles; nos humanizan y dan pie a relatos conmovedores o incluso graciosos (como la crisis de mediana edad en _10_ con Dudley Moore). Ya sea drama o comedia, las historias de “Ritos de paso” comparten el mismo tipo y las mismas reglas.
|
|
48
|
+
Todas las películas tratan del cambio, pero los **ritos de paso** se enfocan en el dolor causado por una fuerza externa: la vida. El “monstruo” suele ser invisible o innombrable, y el héroe tarda en reconocerlo. Historias sobre adicciones, pubertad, crisis de mediana edad, vejez, rupturas o duelo comparten algo: todos entienden lo que ocurre excepto quien lo vive, y solo la experiencia trae la salida.
|
|
49
|
+
|
|
50
|
+
Sea comedia o drama, el monstruo aparece sin aviso y el relato sigue el descubrimiento gradual de su naturaleza. Al final, la victoria llega al **rendirse** ante fuerzas mayores y aceptar nuestra humanidad. La moraleja es siempre la misma: _¡Así es la vida!_
|
|
51
|
+
Si tu idea puede considerarse una historia de Rito de Paso, estas películas son aptas para proyectarse. Como las etapas de aceptación descritas en _On Death and Dying_ de Elizabeth Kübler-Ross, la estructura se traza en la aceptación a regañadientes del héroe ante fuerzas de la naturaleza que no puede controlar ni comprender, y el triunfo llega cuando finalmente logra sonreír.
|
|
52
|
+
|
|
53
|
+
## AMOR ENTRE AMIGOS
|
|
54
|
+
La historia clásica de “compañeros” es, en gran medida, un producto del cine. Aunque existen antecedentes como *Don Quijote*, el formato despegó con la pantalla: al no poder recurrir al monólogo interior, se creó un segundo personaje para que el protagonista tuviera con quién reaccionar y debatir los temas clave.
|
|
55
|
+
|
|
56
|
+
Así nació el “buddy movie”, que se volvió un básico: dos personajes conversando y enfrentando el mundo juntos, porque las historias de “yo y mi mejor amigo” son universales y fácilmente comprensibles.
|
|
57
|
+
|
|
58
|
+
El secreto es que un buen buddy movie suele ser una historia de amor disfrazada; y, a la inversa, muchas historias de amor funcionan como buddy movies con potencial sexual. Películas como *Bringing Up Baby*, *Pat and Mike*, *Woman of the Year*, *Two Weeks Notice* y *How to Lose a Guy in 10 Days* son, por género, versiones más sofisticadas de Laurel y Har
|
|
59
|
+
Hay películas donde uno de los amigos usa falda, pero las reglas son las mismas: drama o comedia, con sexo o sin él. Al inicio, los “amigos” se odian, pero la aventura revela que se necesitan; son mitades incompletas de un todo. Aceptarlo genera más conflicto: ¿quién soporta necesitar a alguien?
|
|
60
|
+
En el momento de **Todo está perdido** (más en el Capítulo Cuatro), hacia el final de estas historias, parece haber separación, pelea o un “adiós y que te vaya bien”, pero en realidad no es eso. Son dos personas que no soportan vivir tan bien sin la otra y deben rendir el ego para ganar. Cuando cae el telón, lo han logrado.
|
|
61
|
+
|
|
62
|
+
A menudo, como en _Rain Man_, uno es el héroe y cambia casi todo (Tom Cruise), mientras el otro funciona como catalizador y cambia poco o nada (Dustin Hoffman). La discusión suele reducirse a: _¿De quién es la historia?_ En _Lethal Weapon_, en gran medida es la de Danny Glover; Mel Gibson impulsa el cambio. Aunque Mel deja de ser suicida, la transformación que más importa es la de Danny. Estas historias de “catalizador”, donde alguien llega, impacta y se va, son un subgrupo clave del Buddy Love. Muchas historias de “niño y su perro” funcionan así, incluida _E.T._
|
|
63
|
+
Si estás escribiendo una película de colegas o una historia de amor, en drama o comedia, debes conocer la estructura Buddy Love. Al ver varias, notarás que comparten patrones muy similares. No es plagio: es narrativa efectiva, y esos momentos se repiten porque funcionan.
|
|
64
|
+
|
|
65
|
+
## WHYDUNIT
|
|
66
|
+
|
|
67
|
+
Sabemos que existen la codicia y el crimen, pero el “quién” rara vez importa tanto como el “por qué”. Un buen Whydunit no trata de que el héroe cambie, sino de que el público descubra algo inesperado y a menudo oscuro sobre la naturaleza humana, respondiendo la pregunta central: ¿por qué?
|
|
68
|
+
_Chinatown_ quizá sea el mejor *Whydunit* y un referente de gran guion: cada revisión revela capas nuevas. Como en _China Syndrome_, _All the President’s Men_, _JFK_ o _Mystic River_, estas historias exploran el lado oscuro. Las reglas son simples: el público es el detective. Aunque haya un sustituto en pantalla que investigue, somos nosotros quienes ordenamos la información y quedamos impactados por lo que descubrimos.
|
|
69
|
+
|
|
70
|
+
Si tu película trata de este tipo de revelación, estudia los grandes *Whydunits*: cómo un personaje nos representa y cómo la pesquisa del lado oscuro de la humanidad termina siendo una pesquisa sobre nosotros mismos. Eso hace un buen *Whydunit*: vuelve la radiografía hacia el espectador y pregunta: “¿Somos *nosotros* así de malvados?”
|
|
71
|
+
|
|
72
|
+
## EL TONTO TRIUNFANTE
|
|
73
|
+
El “Tonto” ha sido un personaje clave en mitos y leyendas. Por fuera parece el Idiota del Pueblo, pero al mirarlo mejor suele ser el más sabio. Su condición de desvalido le da anonimato y hace que otros lo subestimen, permitiéndole destacar al final.
|
|
74
|
+
|
|
75
|
+
En el cine, esta figura viene de Chaplin, Keaton y Lloyd: hombres pequeños y pasados por alto que triunfan por suerte, valentía y por no rendirse. En el cine moderno, ejemplos como _Dave_, _Being There_, _Amadeus_ y _Forrest Gump_ muestran cómo la tradición evoluciona.
|
|
76
|
+
|
|
77
|
+
El principio de “El Tonto Triunfante” enfrenta al Tonto con un villano más poderoso, a menudo del “establishment”. Ver cómo un supuesto “idiota” vence a quienes la sociedad considera ganadores da esperanza y ridiculiza las estructuras que tomamos demasiado en serio; ningún poder es intocable.
|
|
78
|
+
Un filme de “Fool Triumphant” se basa en dos elementos: un perdedor subestimado, visto como inútil en la introducción, y una institución contra la que choca. A menudo lo acompaña un “insider” que entiende el engaño y no puede creer que funcione; suele llevarse la peor parte del slapstick por intentar intervenir.
|
|
79
|
+
|
|
80
|
+
Los “Fools” especiales, en comedias o dramas, muestran la vida del marginado. Como todos nos sentimos así a veces, estas historias ofrecen el placer vicario de ver al outsider triunfar.
|
|
81
|
+
|
|
82
|
+
## INSTITUCIONALIZADO
|
|
83
|
+
¿Dónde estaríamos sin los demás? Cuando nos unimos por una causa común, aparecen las tensiones entre sacrificar los objetivos de unos pocos por los de la mayoría. El género que llamo “Institucionalizado” cuenta historias sobre grupos, instituciones y “familias”. Estas narrativas honran a la institución, pero también revelan el costo de perder la identidad dentro de ella.
|
|
84
|
+
|
|
85
|
+
_One Flew Over the Cuckoo’s Nest_ trata sobre pacientes psiquiátricos; _American Beauty_, sobre suburbios modernos; _M*A*S*H_, sobre el ejército estadounidense; y _The Godfather_, sobre una familia mafiosa. En cada caso, un personaje destacado expone como engañoso el objetivo del grupo (Jack Nicholson, Kevin Spacey, Donald Sutherland y Al Pacino).
|
|
86
|
+
Llamo a estas historias **Institucionalizadas** porque la dinámica del grupo suele ser irracional e incluso autodestructiva. “Suicide Is Painless”, tema de _M*A*S*H_, trata menos de la locura de la guerra que de la mentalidad de rebaño. Al ponernos un uniforme —militar o simbólico— cedemos parte de nuestra identidad. Estas películas exploran los pros y contras de anteponer el grupo al individuo: una lealtad “primitiva” que a veces contradice el sentido común o la supervivencia, pero que repetimos desde siempre. Ver a otros librar ese conflicto explica por qué el género es tan popular y tan visceral.
|
|
87
|
+
|
|
88
|
+
A menudo se narra desde la perspectiva de un recién llegado: es el espectador, alguien nuevo en el grupo, guiado por otro más experimentado. Jane Fonda en _9 to 5_ y Tom Hulce en _Animal House_ son ejemplos. En mundos con tecnología, jerga o reglas poco familiares, estos personajes sirven para hacer preguntas (“¿Cómo funciona eso?”) y transmitir la información necesaria, mostrando ese entorno “loco” al público.
|
|
89
|
+
En el fondo, estas historias se reducen a una pregunta: ¿quién está más loco, yo o ellos? Para entender lo insensato que puede ser sacrificarse por el grupo basta con ver el rostro de Al Pacino al final de _El padrino 2_: se destruye por “la familia” y la “tradición”, y el resultado es devastador. Impacta como el giro final de _American Beauty_ y refleja la expresión vacía de Jack Nicholson en _Atrapado sin salida_. Es, en esencia, el mismo mensaje contado de formas distintas.
|
|
90
|
+
|
|
91
|
+
Funcionan porque siguen las reglas y nos dan lo mismo… pero diferente.
|
|
92
|
+
|
|
93
|
+
## SUPERHÉROE
|
|
94
|
+
|
|
95
|
+
El género “Superhéroe” es lo opuesto a “Tipo con un problema”: una persona extraordinaria cae en un mundo ordinario. Como Gulliver atado por los liliputienses, la historia nos pide humanizar a un ser superior, sentir empatía y entender lo que significa lidiar con “gente pequeña” como nosotros. Por eso tantos geeks y adolescentes se identifican: saben lo que es sentirse incomprendidos.
|
|
96
|
+
Este género va más allá de hombres con capa y mallas; no se limita a Marvel o DC. _Gladiator_ y _A Beautiful Mind_ muestran “superhéroes” humanos enfrentados a la mediocridad: el verdadero obstáculo son las mentes pequeñas que los rodean, incapaces de entenderlos. _Frankenstein_, _Dracula_ y _X-Men_ comparten esa idea. En el fondo, los relatos de superhéroes tratan de ser “diferente”: alguien con una visión única que provoca celos y rechazo, una sensación que cualquiera puede reconocer al ser desestimado por pensar distinto.
|
|
97
|
+
La dificultad de sentir simpatía por millonarios como Bruce Wayne o genios como Russell Crowe se resuelve al subrayar el dolor que acompaña esas ventajas. No es fácil ser Bruce Wayne: vive torturado. Y aunque la terapia sería más barata, es admirable porque renuncia a su comodidad para ayudar a la comunidad.
|
|
98
|
+
|
|
99
|
+
Esto explica por qué suele funcionar la primera película de una saga de superhéroes y las siguientes no (como _Robocop 2_): el mito de origen enfatiza la empatía por su conflicto, pero después se olvida reconstruirla y mostrarnos de nuevo su lado humano. (_Spider-Man 2_ evita ese error y fue un éxito.)
|
|
100
|
+
|
|
101
|
+
En realidad, nunca entenderemos del todo al superhéroe; nuestra conexión nace de la simpatía por ser _mal_ entendido. Por eso este tipo de historias perdura: impulsa nuestras fantasías sobre el potencial, pero las equilibra con realidad.
|
|
102
|
+
|
|
103
|
+
## EL PEQUEÑO Y SUCIO SECRETO DE HOLLYWOOD
|
|
104
|
+
Tras revisar estos géneros, es fácil notar por qué tantas películas se parecen estructuralmente y pensar que hay “plagio”. Y no estás tan equivocado.
|
|
105
|
+
|
|
106
|
+
Mira _Point Break_ y luego _Fast and Furious_: casi la misma historia, pero con surf vs. autos. Compara _The Matrix_ con _Monsters, Inc._: también comparten estructura. Hay muchos casos así.
|
|
107
|
+
|
|
108
|
+
A veces la copia es consciente; otras, coincidencia. Pero con frecuencia ocurre porque las plantillas narrativas funcionan y se repiten: son ejemplos de narración eficaz y, a menudo, éxitos. ¿De verdad alguien se queja de que _Fast and Furious_ tome los mismos beats de _Point Break_? Probablemente casi nadie lo nota.
|
|
109
|
+
Mi punto es: funciona, y por una razón. Las leyes del storytelling se aplican siempre. Tu trabajo es aprender por qué funciona y cómo encajan sus piezas. Si parece que estás copiando, no lo hagas; si suena a cliché, dale un giro; si es familiar, busca una forma nueva. Entiende por qué te atraen el cliché y lo conocido: las reglas existen por algo. Cuando dejes de sentirte limitado, verás lo liberadoras que son. La verdadera originalidad empieza cuando sabes de qué te estás alejando.
|
package/demo/short.js
CHANGED
package/demo/story.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Clara Velásquez: Todo apunta a que usaste esa vieja computadora para dominar la red, ¿verdad?
|
|
2
|
+
Ramón "El Gnomo" Herrera: ¿Dominar? Si apenas y sé enchufarla bien, Clara, pero no subestimes lo que una mente rápida puede hacer.
|
|
3
|
+
Diana Salazar: Rápido o lento, lo que importa es el control. Y alguien ha estado jugando sucio en esta partida.
|
|
4
|
+
Clara Velásquez: Ramón, encontré archivos con tu firma y experimentos de código que podrían hundir a cualquier rival.
|
|
5
|
+
Ramón "El Gnomo" Herrera: Tú quieres que crea que yo soy el hacker estrella detrás de todo este lío... ¿y si es alguien más haciéndolo parecer así?
|
|
6
|
+
Diana Salazar: Eso es justamente lo que quiero saber. ¿Quién tiene acceso a esta red desde fuera?
|
|
7
|
+
Clara Velásquez: Solo un ingeniero con mi nivel podía infiltrarse sin dejar rastro. Tengo evidencia de una copia de mi proyecto en el sistema.
|
|
8
|
+
Ramón "El Gnomo" Herrera: ¿Y si el verdadero troll es la propia Diana? Alguien con interés en desbaratar ambos bandos...
|
|
9
|
+
Diana Salazar: ¿Y qué ganaría yo haciendo eso? Solo busco proteger mi causa, no hundir la red por curiosidad.
|
|
10
|
+
Clara Velásquez: Entonces dime, Diana, ¿por qué tus movimientos siempre terminan beneficiando a la corporación contaminante para la que espías?
|
|
11
|
+
Ramón "El Gnomo" Herrera: Esperen, eso no cuadra con lo que he visto. Claramente hay una mano invisible manejando esto...
|
|
12
|
+
Diana Salazar: Justo cuando pensé que estaba perdiendo el control, revelo que todos tenemos un fragmento de culpa, pero no el control total.
|
|
13
|
+
Clara Velásquez: Entonces, ¿quién queda? Alguien aquí ha estado falsificando pruebas desde el principio, y esa persona está en esta habitación.
|
|
14
|
+
Ramón "El Gnomo" Herrera: Y si no somos nosotros, ¿qué significa eso para el juego que creíamos entender?
|
|
15
|
+
Diana Salazar: Significa que la verdadera partida apenas comienza, y esta vez, nadie puede confiar en nadie.
|
package/demo/stream.js
CHANGED
package/demo/together.js
CHANGED
package/demo/tokens-simple.js
CHANGED
package/demo/tokens.js
CHANGED
package/demo/verbose.js
CHANGED
package/index.js
CHANGED
|
@@ -37,6 +37,7 @@ const MODEL_PRICING = {
|
|
|
37
37
|
'claude-3-5-haiku-20241022': [0.80, 4.00],
|
|
38
38
|
'claude-haiku-4-5-20251001': [1.00, 5.00],
|
|
39
39
|
// Google
|
|
40
|
+
'gemini-3.1-pro-preview':[2.00, 12.00],
|
|
40
41
|
'gemini-3-pro-preview': [2.00, 12.00],
|
|
41
42
|
'gemini-3-flash-preview': [0.50, 3.00],
|
|
42
43
|
'gemini-2.5-pro': [1.25, 10.00],
|
|
@@ -341,6 +342,9 @@ class ModelMix {
|
|
|
341
342
|
gemini25flash({ options = {}, config = {} } = {}) {
|
|
342
343
|
return this.attach('gemini-2.5-flash', new MixGoogle({ options, config }));
|
|
343
344
|
}
|
|
345
|
+
gemini31pro({ options = {}, config = {} } = {}) {
|
|
346
|
+
return this.attach('gemini-3.1-pro-preview', new MixGoogle({ options, config }));
|
|
347
|
+
}
|
|
344
348
|
gemini3pro({ options = {}, config = {} } = {}) {
|
|
345
349
|
return this.attach('gemini-3-pro-preview', new MixGoogle({ options, config }));
|
|
346
350
|
}
|
|
@@ -889,11 +893,14 @@ class ModelMix {
|
|
|
889
893
|
providerInstance.streamCallback = this.streamCallback;
|
|
890
894
|
}
|
|
891
895
|
|
|
896
|
+
const startTime = Date.now();
|
|
892
897
|
const result = await providerInstance.create({ options: currentOptions, config: currentConfig });
|
|
898
|
+
const elapsedMs = Date.now() - startTime;
|
|
893
899
|
|
|
894
|
-
// Calculate cost based on model pricing
|
|
895
900
|
if (result.tokens) {
|
|
896
901
|
result.tokens.cost = ModelMix.calculateCost(currentModelKey, result.tokens);
|
|
902
|
+
const elapsedSec = elapsedMs / 1000;
|
|
903
|
+
result.tokens.speed = elapsedSec > 0 ? Math.round(result.tokens.output / elapsedSec) : 0;
|
|
897
904
|
}
|
|
898
905
|
|
|
899
906
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
@@ -935,7 +942,7 @@ class ModelMix {
|
|
|
935
942
|
// debug level 2: Readable summary of output
|
|
936
943
|
if (currentConfig.debug >= 2) {
|
|
937
944
|
const tokenInfo = result.tokens
|
|
938
|
-
? ` ${result.tokens.input} → ${result.tokens.output} tok` + (result.tokens.cost != null ? ` $${result.tokens.cost.toFixed(4)}` : '')
|
|
945
|
+
? ` ${result.tokens.input} → ${result.tokens.output} tok` + (result.tokens.speed ? ` ${result.tokens.speed} t/s` : '') + (result.tokens.cost != null ? ` $${result.tokens.cost.toFixed(4)}` : '')
|
|
939
946
|
: '';
|
|
940
947
|
console.log(`✓${tokenInfo}\n${ModelMix.formatOutputSummary(result, currentConfig.debug).trim()}`);
|
|
941
948
|
}
|
package/package.json
CHANGED
package/skills/modelmix/SKILL.md
CHANGED
|
@@ -1,41 +1,50 @@
|
|
|
1
1
|
---
|
|
2
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
|
|
3
|
+
description: Instructions for using the ModelMix Node.js library to interact with multiple AI LLM providers through a unified interface. Use when writing code that calls AI models (OpenAI, Anthropic, Google, Groq, Perplexity, Grok, MiniMax, Fireworks, Together, Lambda, Cerebras, OpenRouter, Ollama, LM Studio), chaining models with fallback, getting structured JSON from LLMs, adding MCP tools, streaming responses, managing multi-provider AI workflows, round-robin load balancing, or rate limiting API requests in Node.js. Also use when the user mentions "modelmix", "ModelMix", asks to "call an LLM", "query a model", "add AI to my app", or wants to integrate any supported provider.
|
|
4
|
+
metadata:
|
|
5
|
+
tags: [llm, ai, openai, anthropic, google, groq, perplexity, grok, mcp, streaming, json-output]
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# ModelMix Library Skill
|
|
7
9
|
|
|
8
10
|
## Overview
|
|
9
11
|
|
|
10
|
-
ModelMix is a Node.js library
|
|
12
|
+
ModelMix is a Node.js library providing 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, custom local tools, rate limiting, and token tracking.
|
|
11
13
|
|
|
12
14
|
Use this skill when:
|
|
13
15
|
- Integrating one or more AI models into a Node.js project
|
|
14
|
-
- Chaining models with automatic fallback
|
|
16
|
+
- Chaining models with automatic fallback or round-robin
|
|
15
17
|
- Extracting structured JSON from LLMs
|
|
16
18
|
- Adding MCP tools or custom tools to models
|
|
19
|
+
- Streaming responses from any provider
|
|
17
20
|
- Working with templates and file-based prompts
|
|
21
|
+
- Tracking token usage and costs
|
|
18
22
|
|
|
19
|
-
Do NOT use
|
|
23
|
+
Do NOT use for:
|
|
20
24
|
- Python or non-Node.js projects
|
|
21
25
|
- Direct HTTP calls to LLM APIs (use ModelMix instead)
|
|
22
26
|
|
|
23
|
-
##
|
|
27
|
+
## Quick Reference
|
|
24
28
|
|
|
29
|
+
- [Installation](#installation)
|
|
30
|
+
- [Creating an instance](#creating-an-instance)
|
|
31
|
+
- [Attaching models](#attaching-models)
|
|
25
32
|
- [Get a text response](#get-a-text-response)
|
|
26
33
|
- [Get structured JSON](#get-structured-json)
|
|
27
34
|
- [Stream a response](#stream-a-response)
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
35
|
+
- [Extract a code block](#extract-a-code-block)
|
|
36
|
+
- [Get raw response (tokens, thinking, tool calls)](#get-raw-response)
|
|
37
|
+
- [Access full response with lastRaw](#access-full-response-with-lastraw)
|
|
30
38
|
- [Add images](#add-images)
|
|
31
|
-
- [
|
|
39
|
+
- [Templates with placeholders](#templates-with-placeholders)
|
|
32
40
|
- [Round-robin load balancing](#round-robin-load-balancing)
|
|
33
|
-
- [MCP integration
|
|
34
|
-
- [Custom local tools
|
|
35
|
-
- [Rate limiting
|
|
36
|
-
- [Debug mode](#debug-mode)
|
|
37
|
-
- [Use free-tier models](#use-free-tier-models)
|
|
41
|
+
- [MCP integration](#mcp-integration)
|
|
42
|
+
- [Custom local tools](#custom-local-tools)
|
|
43
|
+
- [Rate limiting](#rate-limiting)
|
|
38
44
|
- [Conversation history](#conversation-history)
|
|
45
|
+
- [Debug mode](#debug-mode)
|
|
46
|
+
- [Free-tier models](#free-tier-models)
|
|
47
|
+
- [Multi-provider routing](#multi-provider-routing)
|
|
39
48
|
|
|
40
49
|
## Installation
|
|
41
50
|
|
|
@@ -54,49 +63,77 @@ import { ModelMix } from 'modelmix';
|
|
|
54
63
|
### Creating an Instance
|
|
55
64
|
|
|
56
65
|
```javascript
|
|
57
|
-
// Static factory (preferred)
|
|
58
66
|
const model = ModelMix.new();
|
|
59
67
|
|
|
60
|
-
// With global options
|
|
61
68
|
const model = ModelMix.new({
|
|
62
69
|
options: { max_tokens: 4096, temperature: 0.7 },
|
|
63
70
|
config: {
|
|
64
71
|
system: "You are a helpful assistant.",
|
|
65
|
-
max_history: 5,
|
|
66
|
-
debug: 0,
|
|
67
|
-
roundRobin: false
|
|
72
|
+
max_history: 5, // -1 = unlimited, 0 = none (default), N = keep last N
|
|
73
|
+
debug: 0, // 0=silent, 1=minimal, 2=summary, 3=full, 4=verbose
|
|
74
|
+
roundRobin: false // false=fallback, true=rotate models
|
|
68
75
|
}
|
|
69
76
|
});
|
|
70
77
|
```
|
|
71
78
|
|
|
72
|
-
### Attaching Models
|
|
79
|
+
### Attaching Models
|
|
73
80
|
|
|
74
|
-
Chain shorthand methods to attach providers. First model is primary; others are fallbacks:
|
|
81
|
+
Chain shorthand methods to attach providers. First model is primary; others are fallbacks (or rotated if `roundRobin: true`):
|
|
75
82
|
|
|
76
83
|
```javascript
|
|
77
84
|
const model = ModelMix.new()
|
|
78
85
|
.sonnet46() // primary
|
|
79
|
-
.gpt52()
|
|
86
|
+
.gpt52() // fallback 1
|
|
80
87
|
.gemini3flash() // fallback 2
|
|
81
88
|
.addText("Hello!")
|
|
82
89
|
```
|
|
83
90
|
|
|
84
|
-
If `
|
|
91
|
+
If `sonnet46` fails, it automatically tries `gpt52`, then `gemini3flash`.
|
|
85
92
|
|
|
86
93
|
## Available Model Shorthands
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
### OpenAI
|
|
96
|
+
`gpt52()` `gpt52chat()` `gpt51()` `gpt5()` `gpt5mini()` `gpt5nano()` `gpt45()` `gpt41()` `gpt41mini()` `gpt41nano()` `o3()` `o4mini()`
|
|
97
|
+
|
|
98
|
+
### Anthropic
|
|
99
|
+
`opus46()` `opus45()` `opus41()` `sonnet46()` `sonnet45()` `sonnet4()` `sonnet37()` `haiku45()` `haiku35()`
|
|
100
|
+
|
|
101
|
+
Thinking variants: append `think` — e.g. `opus46think()` `sonnet46think()` `sonnet45think()` `sonnet4think()` `sonnet37think()` `opus45think()` `opus41think()` `haiku45think()`
|
|
102
|
+
|
|
103
|
+
### Google
|
|
104
|
+
`gemini3pro()` `gemini3flash()` `gemini25pro()` `gemini25flash()`
|
|
105
|
+
|
|
106
|
+
### Grok
|
|
107
|
+
`grok4()` `grok41()` `grok41think()` `grok3()` `grok3mini()`
|
|
108
|
+
|
|
109
|
+
### Perplexity
|
|
110
|
+
`sonar()` `sonarPro()`
|
|
111
|
+
|
|
112
|
+
### Groq
|
|
113
|
+
`scout()` `maverick()`
|
|
114
|
+
|
|
115
|
+
### Together
|
|
116
|
+
`qwen3()` `kimiK2()` `kimiK2think()` `kimiK25think()` `gptOss()`
|
|
117
|
+
|
|
118
|
+
### MiniMax
|
|
119
|
+
`minimaxM25()` `minimaxM21()` `minimaxM2()` `minimaxM2Stable()`
|
|
120
|
+
|
|
121
|
+
### Fireworks
|
|
122
|
+
`deepseekV32()` `GLM5()` `GLM47()`
|
|
123
|
+
|
|
124
|
+
### Cerebras
|
|
125
|
+
`GLM46()`
|
|
126
|
+
|
|
127
|
+
### OpenRouter
|
|
128
|
+
`GLM45()`
|
|
129
|
+
|
|
130
|
+
### Multi-provider (auto-fallback across free/paid tiers)
|
|
131
|
+
`deepseekR1()` `hermes3()` `scout()` `maverick()` `kimiK2()` `GLM47()`
|
|
98
132
|
|
|
99
|
-
|
|
133
|
+
### Local
|
|
134
|
+
`lmstudio()` — for LM Studio local models
|
|
135
|
+
|
|
136
|
+
Each method accepts optional `{ options, config }` to override per-model settings.
|
|
100
137
|
|
|
101
138
|
## Common Tasks
|
|
102
139
|
|
|
@@ -116,35 +153,30 @@ const result = await ModelMix.new()
|
|
|
116
153
|
.gpt5mini()
|
|
117
154
|
.addText("Name and capital of 3 South American countries.")
|
|
118
155
|
.json(
|
|
119
|
-
{ countries: [{ name: "", capital: "" }] },
|
|
120
|
-
{ countries: [{ name: "country name", capital: "in uppercase" }] },
|
|
121
|
-
{ addNote: true }
|
|
156
|
+
{ countries: [{ name: "", capital: "" }] },
|
|
157
|
+
{ countries: [{ name: "country name", capital: "in uppercase" }] },
|
|
158
|
+
{ addNote: true }
|
|
122
159
|
);
|
|
123
|
-
// result.countries → [{ name: "Brazil", capital: "BRASILIA" }, ...]
|
|
124
160
|
```
|
|
125
161
|
|
|
126
162
|
`json()` signature: `json(schemaExample, schemaDescription?, { addSchema, addExample, addNote }?)`
|
|
127
163
|
|
|
128
164
|
#### Enhanced descriptors
|
|
129
165
|
|
|
130
|
-
Descriptions can be
|
|
166
|
+
Descriptions can be strings or descriptor objects with metadata:
|
|
131
167
|
|
|
132
168
|
```javascript
|
|
133
169
|
const result = await model.json(
|
|
134
170
|
{ name: 'martin', age: 22, sex: 'Male' },
|
|
135
171
|
{
|
|
136
172
|
name: { description: 'Name of the actor', required: false },
|
|
137
|
-
age: 'Age of the actor',
|
|
173
|
+
age: 'Age of the actor',
|
|
138
174
|
sex: { description: 'Gender', enum: ['Male', 'Female', null] }
|
|
139
175
|
}
|
|
140
176
|
);
|
|
141
177
|
```
|
|
142
178
|
|
|
143
|
-
Descriptor properties:
|
|
144
|
-
- `description` (string) — field description
|
|
145
|
-
- `required` (boolean, default `true`) — if `false`: removed from required array, type becomes nullable
|
|
146
|
-
- `enum` (array) — allowed values; if includes `null`, type auto-becomes nullable
|
|
147
|
-
- `default` (any) — default value
|
|
179
|
+
Descriptor properties: `description` (string), `required` (boolean, default true — if false, field becomes nullable), `enum` (array — if includes null, type auto-becomes nullable), `default` (any).
|
|
148
180
|
|
|
149
181
|
#### Array auto-wrap
|
|
150
182
|
|
|
@@ -166,7 +198,19 @@ await ModelMix.new()
|
|
|
166
198
|
});
|
|
167
199
|
```
|
|
168
200
|
|
|
169
|
-
###
|
|
201
|
+
### Extract a code block
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
const code = await ModelMix.new()
|
|
205
|
+
.gpt5mini()
|
|
206
|
+
.addText("Write a hello world function in JavaScript.")
|
|
207
|
+
.block();
|
|
208
|
+
// Returns only the content inside the first code block
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`block()` accepts `{ addSystemExtra }` (default true) — adds system instructions that tell the model to wrap output in a code block.
|
|
212
|
+
|
|
213
|
+
### Get raw response
|
|
170
214
|
|
|
171
215
|
```javascript
|
|
172
216
|
const raw = await ModelMix.new()
|
|
@@ -176,15 +220,15 @@ const raw = await ModelMix.new()
|
|
|
176
220
|
// raw.message, raw.think, raw.tokens, raw.toolCalls, raw.response
|
|
177
221
|
```
|
|
178
222
|
|
|
179
|
-
### Access full response
|
|
223
|
+
### Access full response with lastRaw
|
|
180
224
|
|
|
181
|
-
After calling `message()`, `json()`, `block()`, or `stream()`, use `lastRaw` to access the complete response
|
|
225
|
+
After calling `message()`, `json()`, `block()`, or `stream()`, use `lastRaw` to access the complete response:
|
|
182
226
|
|
|
183
227
|
```javascript
|
|
184
228
|
const model = ModelMix.new().gpt5mini().addText("Hello!");
|
|
185
229
|
const text = await model.message();
|
|
186
230
|
console.log(model.lastRaw.tokens);
|
|
187
|
-
// { input: 122, output: 86, total: 541, cost: 0.000319 }
|
|
231
|
+
// { input: 122, output: 86, total: 541, cost: 0.000319, speed: 38 }
|
|
188
232
|
console.log(model.lastRaw.think); // reasoning content (if available)
|
|
189
233
|
console.log(model.lastRaw.response); // raw API response
|
|
190
234
|
```
|
|
@@ -193,13 +237,16 @@ console.log(model.lastRaw.response); // raw API response
|
|
|
193
237
|
|
|
194
238
|
```javascript
|
|
195
239
|
const model = ModelMix.new().sonnet45();
|
|
196
|
-
model.addImage('./photo.jpg');
|
|
197
|
-
model.addImageFromUrl('https://example.com/img.png');
|
|
240
|
+
model.addImage('./photo.jpg'); // from file
|
|
241
|
+
model.addImageFromUrl('https://example.com/img.png'); // from URL
|
|
242
|
+
model.addImageFromBuffer(imageBuffer); // from Buffer
|
|
198
243
|
model.addText('Describe this image.');
|
|
199
244
|
const description = await model.message();
|
|
200
245
|
```
|
|
201
246
|
|
|
202
|
-
|
|
247
|
+
All image methods accept an optional second argument `{ role }` (default `"user"`).
|
|
248
|
+
|
|
249
|
+
### Templates with placeholders
|
|
203
250
|
|
|
204
251
|
```javascript
|
|
205
252
|
const model = ModelMix.new().gpt5mini();
|
|
@@ -221,12 +268,11 @@ const pool = ModelMix.new({ config: { roundRobin: true } })
|
|
|
221
268
|
.sonnet45()
|
|
222
269
|
.gemini3flash();
|
|
223
270
|
|
|
224
|
-
// Each call rotates to the next model
|
|
225
271
|
const r1 = await pool.new().addText("Request 1").message();
|
|
226
272
|
const r2 = await pool.new().addText("Request 2").message();
|
|
227
273
|
```
|
|
228
274
|
|
|
229
|
-
### MCP integration
|
|
275
|
+
### MCP integration
|
|
230
276
|
|
|
231
277
|
```javascript
|
|
232
278
|
const model = ModelMix.new({ config: { max_history: 10 } }).gpt5nano();
|
|
@@ -238,7 +284,7 @@ console.log(await model.message());
|
|
|
238
284
|
|
|
239
285
|
Requires `BRAVE_API_KEY` in `.env` for Brave Search MCP.
|
|
240
286
|
|
|
241
|
-
### Custom local tools
|
|
287
|
+
### Custom local tools
|
|
242
288
|
|
|
243
289
|
```javascript
|
|
244
290
|
const model = ModelMix.new({ config: { max_history: 10 } }).gpt5mini();
|
|
@@ -259,7 +305,18 @@ model.addText("What's the weather in Tokyo?");
|
|
|
259
305
|
console.log(await model.message());
|
|
260
306
|
```
|
|
261
307
|
|
|
262
|
-
|
|
308
|
+
Register multiple tools at once:
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
model.addTools([
|
|
312
|
+
{ tool: { name: "tool_a", description: "...", inputSchema: {...} }, callback: async (args) => {...} },
|
|
313
|
+
{ tool: { name: "tool_b", description: "...", inputSchema: {...} }, callback: async (args) => {...} }
|
|
314
|
+
]);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Manage tools: `model.removeTool("tool_a")` and `model.listTools()` → `{ local, mcp }`.
|
|
318
|
+
|
|
319
|
+
### Rate limiting
|
|
263
320
|
|
|
264
321
|
```javascript
|
|
265
322
|
const model = ModelMix.new({
|
|
@@ -272,20 +329,31 @@ const model = ModelMix.new({
|
|
|
272
329
|
}).gpt5mini();
|
|
273
330
|
```
|
|
274
331
|
|
|
332
|
+
### Conversation history
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
const chat = ModelMix.new({ config: { max_history: 10 } }).gpt5mini();
|
|
336
|
+
chat.addText("My name is Martin.");
|
|
337
|
+
await chat.message();
|
|
338
|
+
chat.addText("What's my name?");
|
|
339
|
+
const reply = await chat.message(); // "Martin"
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`max_history`: 0 = no history (default), N = keep last N exchanges, -1 = unlimited.
|
|
343
|
+
|
|
275
344
|
### Debug mode
|
|
276
345
|
|
|
277
346
|
```javascript
|
|
278
347
|
const model = ModelMix.new({
|
|
279
|
-
config: { debug: 2 } // 0=silent, 1=minimal, 2=summary, 3=full
|
|
348
|
+
config: { debug: 2 } // 0=silent, 1=minimal, 2=summary, 3=full, 4=verbose
|
|
280
349
|
}).gpt5mini();
|
|
281
350
|
```
|
|
282
351
|
|
|
283
|
-
For full debug output, also set
|
|
352
|
+
For full debug output, also set: `DEBUG=ModelMix* node script.js`
|
|
284
353
|
|
|
285
|
-
###
|
|
354
|
+
### Free-tier models
|
|
286
355
|
|
|
287
356
|
```javascript
|
|
288
|
-
// These use providers with free quotas (OpenRouter, Groq, Cerebras)
|
|
289
357
|
const model = ModelMix.new()
|
|
290
358
|
.gptOss()
|
|
291
359
|
.kimiK2()
|
|
@@ -295,48 +363,61 @@ const model = ModelMix.new()
|
|
|
295
363
|
console.log(await model.message());
|
|
296
364
|
```
|
|
297
365
|
|
|
298
|
-
|
|
366
|
+
These use providers with free quotas (OpenRouter, Groq, Cerebras). If one runs out of quota, ModelMix falls back to the next.
|
|
367
|
+
|
|
368
|
+
### Multi-provider routing
|
|
369
|
+
|
|
370
|
+
Some model shorthands register the same model across multiple providers for maximum resilience. Control which providers are enabled via the `mix` parameter:
|
|
299
371
|
|
|
300
372
|
```javascript
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
373
|
+
const model = ModelMix.new({
|
|
374
|
+
mix: {
|
|
375
|
+
openrouter: true, // default: true
|
|
376
|
+
cerebras: true, // default: true
|
|
377
|
+
groq: true, // default: true
|
|
378
|
+
together: false, // default: false
|
|
379
|
+
lambda: false, // default: false
|
|
380
|
+
minimax: false, // default: false
|
|
381
|
+
fireworks: false // default: false
|
|
382
|
+
}
|
|
383
|
+
}).deepseekR1();
|
|
306
384
|
```
|
|
307
385
|
|
|
308
386
|
## Agent Usage Rules
|
|
309
387
|
|
|
310
|
-
-
|
|
311
|
-
- Use `ModelMix.new()` static factory
|
|
388
|
+
- Check `package.json` for `modelmix` before running `npm install`.
|
|
389
|
+
- Use `ModelMix.new()` static factory (not `new ModelMix()`).
|
|
312
390
|
- Store API keys in `.env` and load with `dotenv/config` or `process.loadEnvFile()`. Never hardcode keys.
|
|
313
391
|
- Chain models for resilience: primary model first, fallbacks after.
|
|
314
|
-
- When using MCP tools or `addTool()`, set `max_history` to at least 3.
|
|
315
|
-
- Use `.json()` for structured output instead of parsing text manually. Use descriptor objects `{ description, required, enum, default }`
|
|
392
|
+
- When using MCP tools or `addTool()`, set `max_history` to at least 3 — tool call/response pairs consume history slots.
|
|
393
|
+
- Use `.json()` for structured output instead of parsing text manually. Use descriptor objects `{ description, required, enum, default }` for richer schema control.
|
|
316
394
|
- Use `.message()` for simple text, `.raw()` when you need tokens/thinking/toolCalls.
|
|
317
395
|
- For thinking models, append `think` to the method name (e.g. `sonnet45think()`).
|
|
318
396
|
- Template placeholders use `{key}` syntax in both system prompts and user messages.
|
|
319
|
-
- The library uses CommonJS internally
|
|
320
|
-
-
|
|
397
|
+
- The library uses CommonJS internally but supports ESM import via `{ ModelMix }`.
|
|
398
|
+
- GPT-5+ models automatically use `max_completion_tokens` instead of `max_tokens`.
|
|
399
|
+
- o-series models (o3, o4mini) automatically strip `max_tokens` and `temperature` since those APIs don't support them.
|
|
400
|
+
- `addText()`, `addImage()`, `addImageFromUrl()`, and `addImageFromBuffer()` all accept `{ role }` as second argument (default `"user"`).
|
|
321
401
|
|
|
322
402
|
## API Quick Reference
|
|
323
403
|
|
|
324
404
|
| Method | Returns | Description |
|
|
325
405
|
| --- | --- | --- |
|
|
326
|
-
| `.addText(text)` | `this` | Add user message |
|
|
327
|
-
| `.addTextFromFile(path)` | `this` | Add user message from file |
|
|
406
|
+
| `.addText(text, {role?})` | `this` | Add user message |
|
|
407
|
+
| `.addTextFromFile(path, {role?})` | `this` | Add user message from file |
|
|
328
408
|
| `.setSystem(text)` | `this` | Set system prompt |
|
|
329
409
|
| `.setSystemFromFile(path)` | `this` | Set system prompt from file |
|
|
330
|
-
| `.addImage(path)` | `this` | Add image from file |
|
|
331
|
-
| `.addImageFromUrl(url)` | `this` | Add image from URL or data URI |
|
|
410
|
+
| `.addImage(path, {role?})` | `this` | Add image from file |
|
|
411
|
+
| `.addImageFromUrl(url, {role?})` | `this` | Add image from URL or data URI |
|
|
412
|
+
| `.addImageFromBuffer(buffer, {role?})` | `this` | Add image from Buffer |
|
|
332
413
|
| `.replace({})` | `this` | Set placeholder replacements |
|
|
333
414
|
| `.replaceKeyFromFile(key, path)` | `this` | Replace placeholder with file content |
|
|
334
415
|
| `.message()` | `Promise<string>` | Get text response |
|
|
335
|
-
| `.json(example, desc?, opts?)` | `Promise<object\|array>` | Get structured JSON
|
|
416
|
+
| `.json(example, desc?, opts?)` | `Promise<object\|array>` | Get structured JSON |
|
|
336
417
|
| `.raw()` | `Promise<{message, think, toolCalls, tokens, response}>` | Full response |
|
|
337
|
-
| `.lastRaw` | `object \| null` | Full response from last
|
|
418
|
+
| `.lastRaw` | `object \| null` | Full response from last call |
|
|
338
419
|
| `.stream(callback)` | `Promise` | Stream response |
|
|
339
|
-
| `.block()` | `Promise<string>` | Extract code block from response |
|
|
420
|
+
| `.block({addSystemExtra?})` | `Promise<string>` | Extract code block from response |
|
|
340
421
|
| `.addMCP(package)` | `Promise` | Add MCP server tools |
|
|
341
422
|
| `.addTool(def, callback)` | `this` | Register custom local tool |
|
|
342
423
|
| `.addTools([{tool, callback}])` | `this` | Register multiple tools |
|
|
@@ -345,6 +426,30 @@ const reply = await chat.message(); // "Martin"
|
|
|
345
426
|
| `.new()` | `ModelMix` | Clone instance sharing models |
|
|
346
427
|
| `.attach(key, provider)` | `this` | Attach custom provider |
|
|
347
428
|
|
|
429
|
+
## Available Provider Classes
|
|
430
|
+
|
|
431
|
+
`MixOpenAI` `MixAnthropic` `MixGoogle` `MixPerplexity` `MixGroq` `MixTogether` `MixGrok` `MixOpenRouter` `MixOllama` `MixLMStudio` `MixCustom` `MixCerebras` `MixFireworks` `MixMiniMax` `MixLambda`
|
|
432
|
+
|
|
433
|
+
## Troubleshooting
|
|
434
|
+
|
|
435
|
+
**Model fails with "API key not found"**
|
|
436
|
+
The provider's API key env var is not set. Add it to `.env` and ensure it loads before ModelMix runs. Each provider looks for its standard env var (e.g. `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`).
|
|
437
|
+
|
|
438
|
+
**Tool calls not working**
|
|
439
|
+
Set `max_history` to at least 3. Tool call/response pairs are stored in history and the model needs to see them to complete the conversation loop.
|
|
440
|
+
|
|
441
|
+
**JSON response parsing fails**
|
|
442
|
+
Add `{ addNote: true }` to the `json()` options — this injects instructions about JSON escaping that prevent common parsing errors. For complex schemas, also try `{ addExample: true }`.
|
|
443
|
+
|
|
444
|
+
**Model returns empty or truncated response**
|
|
445
|
+
Increase `max_tokens` in options. Default is 8192 but some tasks need more. For GPT-5+ models, `max_completion_tokens` is used automatically.
|
|
446
|
+
|
|
447
|
+
**Rate limit errors**
|
|
448
|
+
Configure Bottleneck: `config: { bottleneck: { maxConcurrent: 2, minTime: 2000 } }`. This throttles requests to stay within provider limits.
|
|
449
|
+
|
|
450
|
+
**MCP server fails to connect**
|
|
451
|
+
Ensure the MCP package is installed (`npm install @modelcontextprotocol/server-brave-search`) and required env vars are set. Call `addMCP()` with `await` — it's async.
|
|
452
|
+
|
|
348
453
|
## References
|
|
349
454
|
|
|
350
455
|
- [GitHub Repository](https://github.com/clasen/ModelMix)
|