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 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: 'martin', age: 22, sex: 'm' },
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', // string still works
352
- sex: { description: 'Gender', enum: ['m', 'f', null], default: 'm' }
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 // Total tokens used (input + output)
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`, and `total` token counts
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
@@ -1,6 +1,5 @@
1
- process.loadEnvFile();
2
-
3
1
  import { ModelMix, MixCustom } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
4
3
 
5
4
  const mmix = new ModelMix({
6
5
  options: {
package/demo/demo.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 mmix = new ModelMix({
5
5
  options: {
package/demo/fallback.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { ModelMix } from '../index.js';
2
-
3
- process.loadEnvFile();
4
-
2
+ try { process.loadEnvFile(); } catch {}
5
3
 
6
4
  const mmix = new ModelMix({
7
5
  config: {
package/demo/fireworks.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
  async function main() {
5
5
  try {
package/demo/free.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 ai = ModelMix.new({ config: { debug: 2 } })
5
5
  .gptOss()
package/demo/gemini.js CHANGED
@@ -1,6 +1,6 @@
1
- process.loadEnvFile();
2
-
3
1
  import { ModelMix, MixGoogle } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
+
4
4
  const mmix = new ModelMix({
5
5
  options: {
6
6
  max_tokens: 2000,
package/demo/gpt51.js CHANGED
@@ -1,5 +1,6 @@
1
- process.loadEnvFile();
2
1
  import { ModelMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
+
3
4
 
4
5
  const mmix = new ModelMix({
5
6
  config: {
package/demo/grok.js CHANGED
@@ -1,6 +1,5 @@
1
- process.loadEnvFile();
2
-
3
1
  import { ModelMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
4
3
 
5
4
  const mmix = new ModelMix({
6
5
  options: {
package/demo/groq.js CHANGED
@@ -1,6 +1,5 @@
1
- process.loadEnvFile();
2
-
3
1
  import { ModelMix, MixGroq } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
4
3
 
5
4
  const env = process.env;
6
5
 
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
@@ -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 = await ModelMix.new({ options: { max_tokens: 10000 }, config: { debug: 3 } })
5
5
  .sonnet46()
@@ -1,5 +1,5 @@
1
- process.loadEnvFile();
2
1
  import { ModelMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
3
 
4
4
  console.log('🧬 ModelMix - Simple MCP Tools Demo');
5
5
 
package/demo/mcp-tools.js CHANGED
@@ -1,7 +1,7 @@
1
- process.loadEnvFile();
2
1
  import { ModelMix } from '../index.js';
3
2
  import fs from 'fs';
4
3
  import axios from 'axios';
4
+ try { process.loadEnvFile(); } catch {}
5
5
 
6
6
  console.log('🧬 ModelMix - MCP Tools Demo with Callbacks');
7
7
 
package/demo/mcp.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 mmix = ModelMix.new({ config: { max_history: 10 } }).gpt41nano();
5
5
  mmix.setSystem('You are an assistant and today is ' + new Date().toISOString());
package/demo/minimax.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { ModelMix } from '../index.js';
2
- process.loadEnvFile();
3
-
4
-
2
+ try { process.loadEnvFile(); } catch {}
5
3
 
6
4
  const main = async () => {
7
5
 
@@ -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
@@ -15,6 +15,7 @@
15
15
  "dependencies": {
16
16
  "dotenv": "^17.2.3",
17
17
  "isolated-vm": "^6.0.2",
18
- "lemonlog": "^1.1.4"
18
+ "lemonlog": "^1.1.4",
19
+ "pathmix": "^1.0.0"
19
20
  }
20
21
  }
@@ -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
@@ -1,6 +1,5 @@
1
1
  import { ModelMix } from '../index.js';
2
-
3
- process.loadEnvFile();
2
+ try { process.loadEnvFile(); } catch {}
4
3
 
5
4
  const mix = new ModelMix({
6
5
  options: {
@@ -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 - JavaScript REPL Tool Demo');
6
6
 
package/demo/rlm-basic.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
  console.log('🧬 RLM Basic Demo - Recursive Language Model');
5
5
  console.log('📖 Inspired by: https://arxiv.org/html/2512.24601v1\n');
package/demo/rlm-fast.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 - IVM + mmix Callback Demo');
6
6
 
@@ -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');
@@ -1,5 +1,5 @@
1
- process.loadEnvFile();
2
1
  import { ModelMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
3
 
4
4
  console.log('\n=== Round Robin Simple Demo ===\n');
5
5
 
@@ -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
@@ -1,6 +1,5 @@
1
- process.loadEnvFile();
2
-
3
1
  import { ModelMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
4
3
 
5
4
  const setup = {
6
5
  config: {
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
@@ -1,5 +1,5 @@
1
- process.loadEnvFile();
2
1
  import { ModelMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
3
 
4
4
  await ModelMix.new().gpt41nano()
5
5
  .addImageFromUrl('https://pbs.twimg.com/media/F6-GsjraAAADDGy?format=jpg')
package/demo/together.js CHANGED
@@ -1,6 +1,5 @@
1
- process.loadEnvFile();
2
-
3
1
  import { ModelMix, MixTogether } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
4
3
 
5
4
  const setup = { config: { system: "You are ALF from Melmac." } };
6
5
 
@@ -1,5 +1,5 @@
1
- process.loadEnvFile();
2
1
  import { ModelMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
3
 
4
4
  // Ejemplo simple: obtener información de tokens
5
5
  const model = ModelMix.new()
package/demo/tokens.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
  console.log('\n🔢 Token Usage Tracking Demo\n');
5
5
  console.log('='.repeat(60));
package/demo/verbose.js CHANGED
@@ -1,5 +1,5 @@
1
- process.loadEnvFile();
2
1
  import { ModelMix, MixOpenAI } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
3
 
4
4
  const prompt = "Say 'Hello World' in exactly 2 words.";
5
5
 
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "4.4.11",
3
+ "version": "4.4.14",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -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 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.
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 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.
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 this skill for:
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
- ## Common Tasks
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
- - [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)
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
- - [Use templates with placeholders](#use-templates-with-placeholders)
39
+ - [Templates with placeholders](#templates-with-placeholders)
32
40
  - [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)
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, // 0=silent, 1=minimal, 2=summary, 3=full (no truncate), 4=verbose
67
- roundRobin: false // false=fallback, true=rotate models
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 (Fluent Chain)
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() // fallback 1
86
+ .gpt52() // fallback 1
80
87
  .gemini3flash() // fallback 2
81
88
  .addText("Hello!")
82
89
  ```
83
90
 
84
- If `sonnet45` fails, it automatically tries `gpt5mini`, then `gemini3flash`.
91
+ If `sonnet46` fails, it automatically tries `gpt52`, then `gemini3flash`.
85
92
 
86
93
  ## Available Model Shorthands
87
94
 
88
- - **OpenAI**: `gpt52` `gpt51` `gpt5` `gpt5mini` `gpt5nano` `gpt41` `gpt41mini` `gpt41nano`
89
- - **Anthropic**: `opus46` `opus45` `sonnet46` `sonnet45` `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`
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
- Each method is called as `mix.methodName()` and accepts optional `{ options, config }` to override per-model settings.
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: "" }] }, // schema example
120
- { countries: [{ name: "country name", capital: "in uppercase" }] }, // descriptions
121
- { addNote: true } // options
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 **strings** or **descriptor objects** with metadata:
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', // string still works
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
- ### Get raw response (tokens, thinking, tool calls)
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 after `message()` or `json()` with `lastRaw`
223
+ ### Access full response with lastRaw
180
224
 
181
- 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()`.
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'); // from file
197
- model.addImageFromUrl('https://example.com/img.png'); // from URL
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
- ### Use templates with placeholders
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 (external tools)
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 (addTool)
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
- ### Rate limiting (Bottleneck)
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 (no truncate), 4=verbose
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 the env: `DEBUG=ModelMix* node script.js`
352
+ For full debug output, also set: `DEBUG=ModelMix* node script.js`
284
353
 
285
- ### Use free-tier models
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
- ### Conversation history
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 chat = ModelMix.new({ config: { max_history: 10 } }).gpt5mini();
302
- chat.addText("My name is Martin.");
303
- await chat.message();
304
- chat.addText("What's my name?");
305
- const reply = await chat.message(); // "Martin"
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
- - Always check `package.json` for `modelmix` before running `npm install`.
311
- - Use `ModelMix.new()` static factory to create instances (not `new ModelMix()`).
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 }` in descriptions for richer schema control.
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 (`require`) but supports ESM import via `{ ModelMix }`.
320
- - Available provider Mix classes for custom setups: `MixOpenAI`, `MixAnthropic`, `MixGoogle`, `MixPerplexity`, `MixGroq`, `MixTogether`, `MixGrok`, `MixOpenRouter`, `MixOllama`, `MixLMStudio`, `MixCustom`, `MixCerebras`, `MixFireworks`, `MixMiniMax`.
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. Descriptions support descriptor objects `{ description, required, enum, default }`. Top-level arrays auto-wrapped |
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 `message()`/`json()`/`block()`/`stream()` call |
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)