modelmix 4.7.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -88
- package/demo/demo.js +4 -4
- package/demo/prompt.md +2 -2
- package/http-client.js +15 -2
- package/index.d.ts +101 -11
- package/index.js +899 -272
- package/package.json +4 -1
- package/skills/modelmix/SKILL.md +76 -19
- package/test/README.md +10 -9
- package/test/anthropic.test.js +146 -9
- package/test/effort.test.js +2 -2
- package/test/fallback.test.js +192 -2
- package/test/fixtures/account-details.txt +4 -0
- package/test/fixtures/choice-options.txt +6 -0
- package/test/fixtures/choice-template.txt +2 -0
- package/test/fixtures/system-rules.txt +1 -0
- package/test/fixtures/system-template.txt +2 -0
- package/test/fixtures/template.txt +4 -11
- package/test/grok.test.js +28 -1
- package/test/history.test.js +2 -2
- package/test/live.mcp.js +20 -20
- package/test/live.test.js +11 -11
- package/test/templates.test.js +471 -281
- package/test/tokens.test.js +414 -18
package/README.md
CHANGED
|
@@ -163,53 +163,54 @@ Here's a comprehensive list of available methods:
|
|
|
163
163
|
|
|
164
164
|
| Method | Provider | Model | Price (I/O) per 1 M tokens |
|
|
165
165
|
| ------------------- | ---------- | ---------------------------- | -------------------------- |
|
|
166
|
-
| `gpt56sol()` | OpenAI | gpt-5.6-sol | [\$5.00
|
|
167
|
-
| `gpt56terra()` | OpenAI | gpt-5.6-terra | [\$2.00
|
|
168
|
-
| `gpt56luna()` | OpenAI | gpt-5.6-luna | [\$0.20
|
|
169
|
-
| `gpt55()` | OpenAI | gpt-5.5 | [\$5.00
|
|
170
|
-
| `gpt54()` | OpenAI | gpt-5.4 | [\$2.50
|
|
171
|
-
| `gpt54mini()` | OpenAI | gpt-5.4-mini | [\$0.75
|
|
172
|
-
| `gpt54nano()` | OpenAI | gpt-5.4-nano | [\$0.20
|
|
173
|
-
| `gpt53codex()` | OpenAI | gpt-5.3-codex | [\$1.25
|
|
174
|
-
| `gpt52()` | OpenAI | gpt-5.2 | [\$1.75
|
|
175
|
-
| `gpt51()` | OpenAI | gpt-5.1 | [\$1.25
|
|
176
|
-
| `gpt5mini()` | OpenAI | gpt-5-mini | [\$0.25
|
|
177
|
-
| `gpt5nano()` | OpenAI | gpt-5-nano | [\$0.05
|
|
178
|
-
| `gpt41()` | OpenAI | gpt-4.1 | [\$2.00
|
|
179
|
-
| `gpt41mini()` | OpenAI | gpt-4.1-mini | [\$0.40
|
|
180
|
-
| `gpt41nano()` | OpenAI | gpt-4.1-nano | [\$0.10
|
|
181
|
-
| `gptOss()` | Together | gpt-oss-120B | [\$0.15
|
|
182
|
-
| `fable5()` | Anthropic | claude-fable-5 | [\$10.00
|
|
183
|
-
| `opus5()` | Anthropic | claude-opus-5 | [\$5.00
|
|
184
|
-
| `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00
|
|
185
|
-
| `opus47()` | Anthropic | claude-opus-4-7 | [\$5.00
|
|
186
|
-
| `opus46()` | Anthropic | claude-opus-4-6 | [\$5.00
|
|
187
|
-
| `sonnet5()` | Anthropic | claude-sonnet-5 | [\$3.00
|
|
188
|
-
| `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00
|
|
189
|
-
| `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00
|
|
190
|
-
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00
|
|
191
|
-
| `gemini36flash()`
|
|
192
|
-
| `gemini35flash()`
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
166
|
+
| `gpt56sol()` | OpenAI | gpt-5.6-sol | [\$5.00/\$30.00][1] |
|
|
167
|
+
| `gpt56terra()` | OpenAI | gpt-5.6-terra | [\$2.00/\$12.00][1] |
|
|
168
|
+
| `gpt56luna()` | OpenAI | gpt-5.6-luna | [\$0.20/\$1.20][1] |
|
|
169
|
+
| `gpt55()` | OpenAI | gpt-5.5 | [\$5.00/\$30.00][1] |
|
|
170
|
+
| `gpt54()` | OpenAI | gpt-5.4 | [\$2.50/\$15.00][1] |
|
|
171
|
+
| `gpt54mini()` | OpenAI | gpt-5.4-mini | [\$0.75/\$4.50][1] |
|
|
172
|
+
| `gpt54nano()` | OpenAI | gpt-5.4-nano | [\$0.20/\$1.25][1] |
|
|
173
|
+
| `gpt53codex()` | OpenAI | gpt-5.3-codex | [\$1.25/\$14.00][1] |
|
|
174
|
+
| `gpt52()` | OpenAI | gpt-5.2 | [\$1.75/\$14.00][1] |
|
|
175
|
+
| `gpt51()` | OpenAI | gpt-5.1 | [\$1.25/\$10.00][1] |
|
|
176
|
+
| `gpt5mini()` | OpenAI | gpt-5-mini | [\$0.25/\$2.00][1] |
|
|
177
|
+
| `gpt5nano()` | OpenAI | gpt-5-nano | [\$0.05/\$0.40][1] |
|
|
178
|
+
| `gpt41()` | OpenAI | gpt-4.1 | [\$2.00/\$8.00][1] |
|
|
179
|
+
| `gpt41mini()` | OpenAI | gpt-4.1-mini | [\$0.40/\$1.60][1] |
|
|
180
|
+
| `gpt41nano()` | OpenAI | gpt-4.1-nano | [\$0.10/\$0.40][1] |
|
|
181
|
+
| `gptOss()` | Together | gpt-oss-120B | [\$0.15/\$0.60][7] |
|
|
182
|
+
| `fable5()` | Anthropic | claude-fable-5 | [\$10.00/\$50.00][2] |
|
|
183
|
+
| `opus5()` | Anthropic | claude-opus-5 | [\$5.00/\$25.00][2] |
|
|
184
|
+
| `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00/\$25.00][2] |
|
|
185
|
+
| `opus47()` | Anthropic | claude-opus-4-7 | [\$5.00/\$25.00][2] |
|
|
186
|
+
| `opus46()` | Anthropic | claude-opus-4-6 | [\$5.00/\$25.00][2] |
|
|
187
|
+
| `sonnet5()` | Anthropic | claude-sonnet-5 | [\$3.00/\$15.00][2] |
|
|
188
|
+
| `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00/\$15.00][2] |
|
|
189
|
+
| `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00/\$5.00][2] |
|
|
190
|
+
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00/\$12.00][3] |
|
|
191
|
+
| `gemini36flash()` | Google | gemini-3.6-flash | [\$1.50/\$7.50][3] |
|
|
192
|
+
| `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75/\$4.50][3] |
|
|
193
|
+
| `gemini35flashLite()`| Google | gemini-3.5-flash-lite | [\$0.30/\$2.50][3] |
|
|
194
|
+
| `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25/\$1.50][3] |
|
|
195
|
+
| `grok45()` | Grok | grok-4.5 | [\$2.00/\$6.00][6] |
|
|
196
|
+
| `grok43()` | Grok | grok-4.3 | [\$1.25/\$2.50][6] |
|
|
197
|
+
| `grok420multiAgent()`| Grok | grok-4.20-multi-agent-0309 | [\$1.25/\$2.50][6] |
|
|
198
|
+
| `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25/\$2.50][6] |
|
|
199
|
+
| `qwen36plus()` | Fireworks | qwen3p6-plus | [\$0.50/\$3.00][10] |
|
|
200
|
+
| `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40/\$1.60][10] |
|
|
201
|
+
| `qwen38max()` | OpenRouter | qwen/qwen3.8-max | [\$2.00/\$6.00][12] |
|
|
202
|
+
| `deepseekV4Flash()` | Fireworks | models/deepseek-v4-flash | [\$0.14/\$0.28][10] |
|
|
203
|
+
| `deepseekV4Pro()` | Fireworks | models/deepseek-v4-pro | [\$1.74/\$3.48][10] |
|
|
204
|
+
| `GLM52()` | Together | zai-org/GLM-5.2 | [\$1.40/\$4.40][7] |
|
|
205
|
+
| `GLM51()` | Fireworks | models/glm-5p1 | [\$1.05/\$3.50][10] |
|
|
206
|
+
| `minimaxM3()` | MiniMax | MiniMax-M3 | [\$0.30/\$1.20][9] |
|
|
207
|
+
| `minimaxM27()` | MiniMax | MiniMax-M2.7 | [\$0.30/\$1.20][9] |
|
|
208
|
+
| `sonar()` | Perplexity | sonar | [\$1.00/\$1.00][4] |
|
|
209
|
+
| `sonarPro()` | Perplexity | sonar-pro | [\$3.00/\$15.00][4] |
|
|
210
|
+
| `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80/\$0.80][8] |
|
|
211
|
+
| `kimiK3()` | Moonshot | kimi-k3 | [\$3.00/\$15.00][11] |
|
|
212
|
+
| `kimiK25()` | Together | Kimi-K2.5 | [\$0.50/\$2.80][7] |
|
|
213
|
+
| `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95/\$4.00][10] |
|
|
213
214
|
|
|
214
215
|
[1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
|
|
215
216
|
[2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
|
|
@@ -225,11 +226,6 @@ Here's a comprehensive list of available methods:
|
|
|
225
226
|
[12]: https://openrouter.ai/qwen/qwen3.8-max "OpenRouter Pricing"
|
|
226
227
|
|
|
227
228
|
Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
|
|
228
|
-
† `grok420()` resolves to `grok-4.20-0309-non-reasoning` by default, or `grok-4.20-0309-reasoning` when `.effort(20+)` / `-1` (or native non-`none` `reasoning_effort`) is set.
|
|
229
|
-
For NVIDIA on DeepSeek V4 Flash/Pro, use `deepseekV4Flash({ mix: { nvidia: true } })` or `deepseekV4Pro({ mix: { nvidia: true } })`.
|
|
230
|
-
For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
|
|
231
|
-
For OpenRouter on Qwen 3.7 Plus, use `qwen37plus({ mix: { fireworks: false, openrouter: true } })`.
|
|
232
|
-
For OpenRouter instead of Moonshot's native API, use `kimiK3({ mix: { moonshot: false, openrouter: true } })`.
|
|
233
229
|
|
|
234
230
|
```javascript
|
|
235
231
|
const result = await ModelMix.new({
|
|
@@ -243,7 +239,9 @@ const result = await ModelMix.new({
|
|
|
243
239
|
|
|
244
240
|
## 🔄 Templates
|
|
245
241
|
|
|
246
|
-
ModelMix
|
|
242
|
+
ModelMix renders system prompts and user messages with [EJS](https://ejs.co/). Templates can be inline or stored in external files, and support variables, conditionals, loops, and relative includes.
|
|
243
|
+
|
|
244
|
+
Templates are executable JavaScript and must be controlled by the developer. Pass untrusted content only as template data, never as the template source.
|
|
247
245
|
|
|
248
246
|
### Core methods
|
|
249
247
|
|
|
@@ -251,16 +249,16 @@ ModelMix includes a simple but powerful templating system. You can write your sy
|
|
|
251
249
|
| --- | --- |
|
|
252
250
|
| `setSystemFromFile(path)` | Load the system prompt from a file |
|
|
253
251
|
| `addTextFromFile(path)` | Load a user message from a file |
|
|
254
|
-
| `replace({ key: value })` |
|
|
255
|
-
| `replaceKeyFromFile(key, path)` |
|
|
252
|
+
| `replace({ key: value })` | Add EJS template data |
|
|
253
|
+
| `replaceKeyFromFile(key, path)` | Add a file's raw contents as template data |
|
|
256
254
|
|
|
257
255
|
### Basic example with `replace`
|
|
258
256
|
|
|
259
257
|
```javascript
|
|
260
258
|
const gpt = ModelMix.new().gpt52();
|
|
261
259
|
|
|
262
|
-
gpt.addText('Write a short story about a
|
|
263
|
-
gpt.replace({
|
|
260
|
+
gpt.addText('Write a short story about a <%- animal %> that lives in <%- place %>.');
|
|
261
|
+
gpt.replace({ animal: 'cat', place: 'a haunted castle' });
|
|
264
262
|
|
|
265
263
|
console.log(await gpt.message());
|
|
266
264
|
```
|
|
@@ -271,15 +269,15 @@ Instead of writing long prompts inline, keep them in separate Markdown files. Th
|
|
|
271
269
|
|
|
272
270
|
**`prompts/system.md`**
|
|
273
271
|
```markdown
|
|
274
|
-
You are
|
|
275
|
-
Always respond in
|
|
272
|
+
You are <%- role %>, an expert in <%- topic %>.
|
|
273
|
+
Always respond in <%- language %>.
|
|
276
274
|
```
|
|
277
275
|
|
|
278
276
|
**`prompts/task.md`**
|
|
279
277
|
```markdown
|
|
280
278
|
Analyze the following and provide 3 key insights:
|
|
281
279
|
|
|
282
|
-
|
|
280
|
+
<%- content %>
|
|
283
281
|
```
|
|
284
282
|
|
|
285
283
|
**`app.js`**
|
|
@@ -290,16 +288,16 @@ gpt.setSystemFromFile('./prompts/system.md');
|
|
|
290
288
|
gpt.addTextFromFile('./prompts/task.md');
|
|
291
289
|
|
|
292
290
|
gpt.replace({
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
role: 'a senior analyst',
|
|
292
|
+
topic: 'market trends',
|
|
293
|
+
language: 'Spanish',
|
|
294
|
+
content: 'Bitcoin surpassed $100,000 in December 2024...'
|
|
297
295
|
});
|
|
298
296
|
|
|
299
297
|
console.log(await gpt.message());
|
|
300
298
|
```
|
|
301
299
|
|
|
302
|
-
### Injecting file contents
|
|
300
|
+
### Injecting file contents as template data
|
|
303
301
|
|
|
304
302
|
Use `replaceKeyFromFile` when the replacement value itself is a large text stored in a file.
|
|
305
303
|
|
|
@@ -307,7 +305,7 @@ Use `replaceKeyFromFile` when the replacement value itself is a large text store
|
|
|
307
305
|
```markdown
|
|
308
306
|
Summarize the following article in 3 bullet points:
|
|
309
307
|
|
|
310
|
-
|
|
308
|
+
<%- article %>
|
|
311
309
|
```
|
|
312
310
|
|
|
313
311
|
**`app.js`**
|
|
@@ -315,7 +313,7 @@ Summarize the following article in 3 bullet points:
|
|
|
315
313
|
const gpt = ModelMix.new().gpt5mini();
|
|
316
314
|
|
|
317
315
|
gpt.addTextFromFile('./prompts/summarize.md');
|
|
318
|
-
gpt.replaceKeyFromFile('
|
|
316
|
+
gpt.replaceKeyFromFile('article', './data/article.md');
|
|
319
317
|
|
|
320
318
|
console.log(await gpt.message());
|
|
321
319
|
```
|
|
@@ -326,17 +324,17 @@ Combine all methods to build reusable, file-based prompt pipelines:
|
|
|
326
324
|
|
|
327
325
|
**`prompts/system.md`**
|
|
328
326
|
```markdown
|
|
329
|
-
You are
|
|
327
|
+
You are <%- role %>. Follow these rules:
|
|
330
328
|
- Be concise
|
|
331
329
|
- Use examples when possible
|
|
332
|
-
- Respond in
|
|
330
|
+
- Respond in <%- language %>
|
|
333
331
|
```
|
|
334
332
|
|
|
335
333
|
**`prompts/review.md`**
|
|
336
334
|
```markdown
|
|
337
335
|
Review the following code and suggest improvements:
|
|
338
336
|
|
|
339
|
-
|
|
337
|
+
<%- code %>
|
|
340
338
|
```
|
|
341
339
|
|
|
342
340
|
**`app.js`**
|
|
@@ -346,12 +344,63 @@ const gpt = ModelMix.new().gpt5mini();
|
|
|
346
344
|
gpt.setSystemFromFile('./prompts/system.md');
|
|
347
345
|
gpt.addTextFromFile('./prompts/review.md');
|
|
348
346
|
|
|
349
|
-
gpt.replace({
|
|
350
|
-
gpt.replaceKeyFromFile('
|
|
347
|
+
gpt.replace({ role: 'a senior code reviewer', language: 'English' });
|
|
348
|
+
gpt.replaceKeyFromFile('code', './src/utils.js');
|
|
351
349
|
|
|
352
350
|
console.log(await gpt.message());
|
|
353
351
|
```
|
|
354
352
|
|
|
353
|
+
### EJS output and control flow
|
|
354
|
+
|
|
355
|
+
Use `<%- value %>` for raw prompt content and `<%= value %>` only when XML escaping is intentional. Missing variables and missing files throw immediately.
|
|
356
|
+
|
|
357
|
+
```ejs
|
|
358
|
+
<% if (user.active) { %>
|
|
359
|
+
Review these roles:
|
|
360
|
+
<% user.roles.forEach(role => { %>
|
|
361
|
+
- <%- role %>
|
|
362
|
+
<% }) %>
|
|
363
|
+
<% } %>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Random prompt choices
|
|
367
|
+
|
|
368
|
+
Use a `choice` block to include exactly one prompt variant. When no weights are present, every option has the same probability:
|
|
369
|
+
|
|
370
|
+
```ejs
|
|
371
|
+
<% choice %>
|
|
372
|
+
<% option %>
|
|
373
|
+
Use emojis.
|
|
374
|
+
<% option %>
|
|
375
|
+
Use few emojis.
|
|
376
|
+
<% option %>
|
|
377
|
+
Do not use emojis.
|
|
378
|
+
<% /choice %>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Add a positive weight after every `option` when the probabilities should differ:
|
|
382
|
+
|
|
383
|
+
```ejs
|
|
384
|
+
<% choice %>
|
|
385
|
+
<% option 20 %>
|
|
386
|
+
Use emojis.
|
|
387
|
+
<% option 40 %>
|
|
388
|
+
Use few emojis.
|
|
389
|
+
<% option 40 %>
|
|
390
|
+
Do not use emojis.
|
|
391
|
+
<% /choice %>
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Weights are relative and do not need to total 100. A block must either give every option a weight or omit all weights. Directives must be on their own lines; choices can be nested and can also appear inside relative includes. Each new request makes a new selection, while retries, provider fallbacks, and tool continuations keep the original selection.
|
|
395
|
+
|
|
396
|
+
File templates can include files relative to their own path:
|
|
397
|
+
|
|
398
|
+
```ejs
|
|
399
|
+
<%- include('shared/rules.md') %>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Content supplied through `replace()` or `replaceKeyFromFile()` is rendered once as data. EJS tags inside that content are not executed recursively.
|
|
403
|
+
|
|
355
404
|
## 🧩 JSON Structured Output
|
|
356
405
|
|
|
357
406
|
The `json` method forces the model to return a structured JSON response. You define the shape with an example object and optionally describe each field.
|
|
@@ -540,12 +589,29 @@ Every response from `raw()` now includes a `tokens` object with the following st
|
|
|
540
589
|
```javascript
|
|
541
590
|
{
|
|
542
591
|
tokens: {
|
|
543
|
-
input:
|
|
544
|
-
output:
|
|
545
|
-
total:
|
|
546
|
-
cached:
|
|
547
|
-
|
|
548
|
-
|
|
592
|
+
input: 1200, // Total input tokens, including cache reads and writes
|
|
593
|
+
output: 50, // Number of output tokens
|
|
594
|
+
total: 1250, // Total tokens used
|
|
595
|
+
cached: 1024, // Input tokens read from cache
|
|
596
|
+
cacheWrite: 0, // Input tokens written to cache
|
|
597
|
+
cacheWrite5m: 0, // Anthropic writes using the 5-minute TTL
|
|
598
|
+
cacheWrite1h: 0, // Anthropic writes using the 1-hour TTL
|
|
599
|
+
uncachedInput: 176, // max(0, input - cached - cacheWrite)
|
|
600
|
+
cacheHitRate: 0.8533, // cached / input, rounded to 4 decimals
|
|
601
|
+
cacheSavings: 0.00018432, // USD saved by cache reads
|
|
602
|
+
cacheWritePremium: 0, // Extra USD paid to write this cache entry
|
|
603
|
+
breakEvenHits: 0, // Full future hits needed to recover that premium
|
|
604
|
+
cost: 0.00011568, // Total estimated cost in USD
|
|
605
|
+
costBreakdown: {
|
|
606
|
+
uncachedInput: 0.0000352,
|
|
607
|
+
cachedInput: 0.00002048,
|
|
608
|
+
cacheWrite: 0,
|
|
609
|
+
cacheWrite5m: 0,
|
|
610
|
+
cacheWrite1h: 0,
|
|
611
|
+
output: 0.00006,
|
|
612
|
+
total: 0.00011568
|
|
613
|
+
},
|
|
614
|
+
speed: 42 // Output tokens per second (int)
|
|
549
615
|
}
|
|
550
616
|
}
|
|
551
617
|
```
|
|
@@ -557,10 +623,66 @@ After calling `message()` or `json()`, use `lastRaw` to access the complete resp
|
|
|
557
623
|
```javascript
|
|
558
624
|
const text = await model.message();
|
|
559
625
|
console.log(model.lastRaw.tokens);
|
|
560
|
-
//
|
|
626
|
+
// Same normalized token and cost structure returned by raw()
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
`cached` aggregates cache reads reported by the provider, while `cacheWrite` aggregates cache writes. Anthropic additionally exposes `cacheWrite5m` and `cacheWrite1h` because those writes cost 1.25× and 2× the normal input rate, respectively. `cacheSavings` compares cache reads with the normal input rate, `cacheWritePremium` compares writes with that rate, and `breakEvenHits` estimates how many complete future hits recover the current write premium. For Anthropic, `input` is normalized to include uncached input, cache reads, and cache writes. Missing usage or pricing categories return `0`. The `speed` field is the generation speed measured in output tokens per second (integer).
|
|
630
|
+
|
|
631
|
+
### GPT-5.6 prompt caching
|
|
632
|
+
|
|
633
|
+
GPT-5.6 supports implicit or explicit caching through `prompt_cache_options`. Put the explicit breakpoint at the end of the stable prefix; the provider only caches prompts with at least 1,024 tokens.
|
|
634
|
+
|
|
635
|
+
```javascript
|
|
636
|
+
const model = ModelMix.new()
|
|
637
|
+
.gpt56luna({
|
|
638
|
+
options: {
|
|
639
|
+
prompt_cache_key: 'support-agent-v1',
|
|
640
|
+
prompt_cache_options: { mode: 'explicit', ttl: '30m' }
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
.addText(longStableInstructions, {
|
|
644
|
+
cache: { breakpoint: true }
|
|
645
|
+
})
|
|
646
|
+
.addText('Answer this variable request.');
|
|
647
|
+
|
|
648
|
+
const result = await model.raw();
|
|
649
|
+
console.log(result.tokens.cached, result.tokens.cacheWrite, result.tokens.cost);
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
The provider-neutral `cache: { breakpoint: true }` option is accepted by `addTextFromFile()`, `addImage()`, `addImageFromUrl()`, and `addImageFromBuffer()`. Responses-native `input_text`, `input_image`, and `input_file` blocks preserve the native `prompt_cache_breakpoint` field when supplied directly through `options.messages`.
|
|
653
|
+
|
|
654
|
+
GPT-5.6 uses `prompt_cache_options.ttl`; `prompt_cache_retention` remains available for earlier OpenAI models. ModelMix rejects the incompatible control instead of silently dropping it. For GPT-5.6 requests over 272K input tokens, the cost calculation applies the documented 2× input and 1.5× output multipliers to the complete request, including cache reads and writes.
|
|
655
|
+
|
|
656
|
+
GPT-5.6 prices per 1M tokens:
|
|
657
|
+
|
|
658
|
+
| Model | Input | Cached input | Cache write | Output |
|
|
659
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
660
|
+
| `gpt-5.6-sol` | $5.00 | $0.50 | $6.25 | $30.00 |
|
|
661
|
+
| `gpt-5.6-terra` | $2.00 | $0.20 | $2.50 | $12.00 |
|
|
662
|
+
| `gpt-5.6-luna` | $0.20 | $0.02 | $0.25 | $1.20 |
|
|
663
|
+
|
|
664
|
+
### Cross-provider cache fallback
|
|
665
|
+
|
|
666
|
+
Neutral breakpoints are translated at the last moment by each provider adapter. Native request policies remain scoped to their model, so they cannot leak into a fallback request:
|
|
667
|
+
|
|
668
|
+
```javascript
|
|
669
|
+
const model = ModelMix.new()
|
|
670
|
+
.gpt56luna({
|
|
671
|
+
options: {
|
|
672
|
+
prompt_cache_key: 'support-agent-v1',
|
|
673
|
+
prompt_cache_options: { mode: 'explicit', ttl: '30m' }
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
.haiku45({
|
|
677
|
+
options: {
|
|
678
|
+
cache_control: { type: 'ephemeral', ttl: '1h' }
|
|
679
|
+
}
|
|
680
|
+
})
|
|
681
|
+
.addText(longStableInstructions, { cache: { breakpoint: true } })
|
|
682
|
+
.addText('Answer this variable request.');
|
|
561
683
|
```
|
|
562
684
|
|
|
563
|
-
|
|
685
|
+
GPT-5.6 receives `prompt_cache_breakpoint`; Anthropic receives `cache_control`; older OpenAI models and providers without an equivalent omit the marker. When a neutral explicit breakpoint is present for Anthropic, its model-scoped `cache_control` becomes that block's policy instead of adding an automatic breakpoint after the variable suffix.
|
|
564
686
|
|
|
565
687
|
## 🐛 Enabling Debug Mode
|
|
566
688
|
|
|
@@ -674,18 +796,18 @@ new ModelMix(args = { options: {}, config: {} })
|
|
|
674
796
|
|
|
675
797
|
- `setSystem(text)`: Sets the system prompt.
|
|
676
798
|
- `setSystemFromFile(filePath)`: Sets the system prompt from a file.
|
|
677
|
-
- `addText(text, config = { role: "user" })`: Adds a text message.
|
|
678
|
-
- `addTextFromFile(filePath, config = { role: "user" })`: Adds a text message from a file.
|
|
679
|
-
- `addImage(filePath, config = { role: "user" })`: Adds an image message from a file path.
|
|
680
|
-
- `addImageFromUrl(url, config = { role: "user" })`: Adds an image message from URL.
|
|
681
|
-
- `replace(keyValues)`:
|
|
682
|
-
- `replaceKeyFromFile(key, filePath)`:
|
|
799
|
+
- `addText(text, config = { role: "user", cache? })`: Adds a text message.
|
|
800
|
+
- `addTextFromFile(filePath, config = { role: "user", cache? })`: Adds a text message from a file.
|
|
801
|
+
- `addImage(filePath, config = { role: "user", cache? })`: Adds an image message from a file path.
|
|
802
|
+
- `addImageFromUrl(url, config = { role: "user", cache? })`: Adds an image message from URL.
|
|
803
|
+
- `replace(keyValues)`: Adds EJS data for messages and system prompts.
|
|
804
|
+
- `replaceKeyFromFile(key, filePath)`: Adds raw file contents as an EJS data value.
|
|
683
805
|
- `message()`: Sends the message and returns the response.
|
|
684
806
|
- `raw()`: Sends the message and returns the complete response data including:
|
|
685
807
|
- `message`: The text response from the model
|
|
686
808
|
- `think`: Reasoning/thinking content (if available)
|
|
687
809
|
- `toolCalls`: Array of tool calls made by the model (if any)
|
|
688
|
-
- `tokens`:
|
|
810
|
+
- `tokens`: Normalized token counts (`input`, `output`, `total`, `cached`, `cacheWrite`, `cacheWrite5m`, `cacheWrite1h`, `uncachedInput`, `cacheHitRate`), cache economics (`cacheSavings`, `cacheWritePremium`, `breakEvenHits`), plus `cost`, `costBreakdown` (USD), and `speed` (output tokens/sec)
|
|
689
811
|
- `response`: The raw API response
|
|
690
812
|
- `stream(callback)`: Sends the message and streams the response, invoking the callback with each streamed part.
|
|
691
813
|
- `json(schemaExample, descriptions = {}, options = {})`: Forces the model to return a response in a specific JSON format.
|
|
@@ -828,4 +950,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
828
950
|
|
|
829
951
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
830
952
|
|
|
831
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
953
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/demo/demo.js
CHANGED
|
@@ -6,7 +6,7 @@ const mmix = new ModelMix({
|
|
|
6
6
|
temperature: 0.5,
|
|
7
7
|
},
|
|
8
8
|
config: {
|
|
9
|
-
system: 'You are
|
|
9
|
+
system: 'You are <%- name %> from Melmac.',
|
|
10
10
|
max_history: 2,
|
|
11
11
|
bottleneck: { maxConcurrent: 1 },
|
|
12
12
|
debug: 3,
|
|
@@ -22,12 +22,12 @@ const pplxSettings = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
mmix.replace({
|
|
25
|
+
mmix.replace({ name: 'ALF' });
|
|
26
26
|
|
|
27
27
|
console.log("\n" + '--------| gpt51() |--------');
|
|
28
28
|
const gptArgs = { options: { reasoning_effort: "none", verbosity: "low" } };
|
|
29
|
-
const gpt = mmix.gpt51(gptArgs).addText("Have you ever eaten a
|
|
30
|
-
gpt.replace({
|
|
29
|
+
const gpt = mmix.gpt51(gptArgs).addText("Have you ever eaten a <%- animal %>?");
|
|
30
|
+
gpt.replace({ animal: 'cat' });
|
|
31
31
|
await gpt.json({ time: '24:00:00', message: 'Hello' }, { time: 'Time in format HH:MM:SS' });
|
|
32
32
|
|
|
33
33
|
console.log("\n" + '--------| sonnet45() |--------');
|
package/demo/prompt.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Writing Prompt:
|
|
1
|
+
# Writing Prompt: <%- storyTitle %>
|
|
2
2
|
|
|
3
3
|
## Setting
|
|
4
4
|
A small, quiet town where nothing unusual ever happens.
|
|
@@ -22,4 +22,4 @@ Write a short story (500-1000 words) that explores:
|
|
|
22
22
|
### Additional Elements to Consider
|
|
23
23
|
- The reactions of other townspeople
|
|
24
24
|
- Any changes in the town's atmosphere after the package's arrival
|
|
25
|
-
- Your character's internal conflict between curiosity and duty
|
|
25
|
+
- Your character's internal conflict between curiosity and duty
|
package/http-client.js
CHANGED
|
@@ -4,6 +4,19 @@ function headersToObject(headers) {
|
|
|
4
4
|
return Object.fromEntries(headers.entries());
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
function sanitizeUrl(url) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = new URL(url);
|
|
10
|
+
parsed.username = '';
|
|
11
|
+
parsed.password = '';
|
|
12
|
+
parsed.search = '';
|
|
13
|
+
parsed.hash = '';
|
|
14
|
+
return parsed.toString();
|
|
15
|
+
} catch {
|
|
16
|
+
return String(url).replace(/[?#].*$/, '');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
async function parseResponseBody(response) {
|
|
8
21
|
try {
|
|
9
22
|
return await response.json();
|
|
@@ -24,7 +37,7 @@ async function parseJsonBody(response) {
|
|
|
24
37
|
|
|
25
38
|
async function buildHttpError(url, response) {
|
|
26
39
|
const details = await parseResponseBody(response);
|
|
27
|
-
const error = new Error(`Request to ${url} failed with status code ${response.status}`);
|
|
40
|
+
const error = new Error(`Request to ${sanitizeUrl(url)} failed with status code ${response.status}`);
|
|
28
41
|
error.isHttpError = true;
|
|
29
42
|
error.statusCode = response.status;
|
|
30
43
|
error.details = details;
|
|
@@ -64,7 +77,7 @@ async function fetchStreamResponse(url, { method = 'POST', headers = {}, body }
|
|
|
64
77
|
throw await buildHttpError(url, response);
|
|
65
78
|
}
|
|
66
79
|
if (!response.body) {
|
|
67
|
-
throw new Error(`Request to ${url} did not return a readable stream`);
|
|
80
|
+
throw new Error(`Request to ${sanitizeUrl(url)} did not return a readable stream`);
|
|
68
81
|
}
|
|
69
82
|
return {
|
|
70
83
|
data: Readable.fromWeb(response.body),
|