modelmix 5.0.2 → 5.0.3
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 +109 -7
- package/RLM_PLUGIN_SPEC.md +465 -0
- package/demo/gemini.js +3 -4
- package/demo/short.js +1 -1
- package/effort.js +2 -0
- package/index.d.ts +61 -1
- package/index.js +333 -45
- package/package.json +7 -4
- package/plugins/rlm/index.d.ts +194 -0
- package/plugins/rlm/index.js +25 -0
- package/plugins/rlm/lib/budget.js +153 -0
- package/plugins/rlm/lib/isolated-vm-sandbox.js +90 -0
- package/plugins/rlm/lib/markdown.js +156 -0
- package/plugins/rlm/lib/planner-prompt.js +137 -0
- package/plugins/rlm/lib/plugin.js +203 -0
- package/plugins/rlm/lib/runtime.js +146 -0
- package/plugins/rlm/lib/variable-descriptors.js +228 -0
- package/plugins/rlm/lib/worker-catalog.js +70 -0
- package/plugins/rlm/package.json +32 -0
- package/plugins/rlm/prompts/partials/processing-rules.md +8 -0
- package/plugins/rlm/prompts/planner.md +53 -0
- package/plugins/rlm/test/budget.test.js +86 -0
- package/plugins/rlm/test/fixtures/book.md +24 -0
- package/plugins/rlm/test/isolated-vm-sandbox.test.js +114 -0
- package/plugins/rlm/test/markdown.test.js +64 -0
- package/plugins/rlm/test/planner-template.test.js +140 -0
- package/plugins/rlm/test/plugin-contract.test.js +182 -0
- package/plugins/rlm/test/rlm-e2e.test.js +338 -0
- package/plugins/rlm/test/variable-descriptors.test.js +170 -0
- package/plugins/rlm/test/worker-catalog.test.js +104 -0
- package/pnpm-workspace.yaml +6 -0
- package/skills/modelmix/SKILL.md +22 -3
- package/test/effort.test.js +14 -1
- package/test/live.mcp.js +6 -6
- package/test/live.test.js +2 -2
- package/test/plugins.test.js +356 -0
- package/test/tokens.test.js +37 -5
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ const setup = {
|
|
|
69
69
|
const model = await ModelMix.new(setup)
|
|
70
70
|
.sonnet5() // (main model) Anthropic claude-sonnet-5
|
|
71
71
|
.gpt56luna() // (fallback 2) OpenAI gpt-5.6-luna
|
|
72
|
-
.
|
|
72
|
+
.gemini37flash() // (fallback 3) Google gemini-3.7-flash
|
|
73
73
|
.grok46() // (fallback 4) Grok grok-4.6
|
|
74
74
|
.addText("What's your name?");
|
|
75
75
|
|
|
@@ -101,6 +101,91 @@ This pattern allows you to:
|
|
|
101
101
|
- Track token usage across all providers
|
|
102
102
|
- Keep your code clean and maintainable
|
|
103
103
|
|
|
104
|
+
## 🔌 Instance Plugins
|
|
105
|
+
|
|
106
|
+
Plugins wrap one ModelMix instance without changing global behavior. They run in registration order after templates are rendered and before provider-specific request conversion:
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
const metrics = {
|
|
110
|
+
name: 'metrics',
|
|
111
|
+
async execute(context, next) {
|
|
112
|
+
const startedAt = Date.now();
|
|
113
|
+
const result = await next();
|
|
114
|
+
return { ...result, elapsedMs: Date.now() - startedAt };
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const model = ModelMix.new()
|
|
119
|
+
.gpt56luna()
|
|
120
|
+
.use(metrics)
|
|
121
|
+
.addText('Summarize this request.');
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A plugin may edit `context.request`, call `next()`, or return a complete ModelMix result itself. It can also create history-free child executions with `context.invoke()` and choose plugin inheritance:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const child = await context.invoke({
|
|
128
|
+
systemFile: './prompts/extract-entities.md',
|
|
129
|
+
assign: { outputLanguage: 'Spanish' },
|
|
130
|
+
messages: [{ role: 'user', content: section }],
|
|
131
|
+
plugins: { exclude: ['recursive-plugin'] },
|
|
132
|
+
history: false
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Supported policies are `'inherit'`, `'none'`, `{ include: [...] }`, and `{ exclude: [...] }`. Child metadata exposes `executionId`, `parentExecutionId`, and `depth` to middleware. `.new()` inherits registered plugins but not message history.
|
|
137
|
+
|
|
138
|
+
Child `systemFile` templates use the same EJS engine, `assign()` data contract, and relative Markdown includes as ordinary ModelMix templates. Use either `system` or `systemFile`, not both.
|
|
139
|
+
|
|
140
|
+
### Recursive Language Model plugin
|
|
141
|
+
|
|
142
|
+
The separately publishable `@modelmix/rlm` workspace package keeps document parsing, planner prompts, and `isolated-vm` out of the core `modelmix` dependency tree. It requires Node.js 22 or newer.
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
const { ModelMix } = require('modelmix');
|
|
146
|
+
const { rlm } = require('@modelmix/rlm');
|
|
147
|
+
|
|
148
|
+
const fast = ModelMix.new().gpt41mini();
|
|
149
|
+
|
|
150
|
+
const result = await ModelMix.new()
|
|
151
|
+
.gpt56luna()
|
|
152
|
+
.use(rlm({
|
|
153
|
+
maxDepth: 2,
|
|
154
|
+
documents: {
|
|
155
|
+
book: {
|
|
156
|
+
format: 'markdown',
|
|
157
|
+
content: markdownBook
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
workers: {
|
|
161
|
+
fast: {
|
|
162
|
+
model: fast,
|
|
163
|
+
intelligence: 2,
|
|
164
|
+
cost: 1,
|
|
165
|
+
speed: 4,
|
|
166
|
+
description: 'Translation, extraction, and simple transformations'
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
limits: {
|
|
170
|
+
maxQueryBytes: 64 * 1024,
|
|
171
|
+
sandboxMemoryBytes: 64 * 1024 * 1024,
|
|
172
|
+
maxConcurrentQueries: 4,
|
|
173
|
+
maxCalls: 100,
|
|
174
|
+
maxOutputBytes: 8 * 1024 * 1024,
|
|
175
|
+
maxGeneratedTokens: 100000,
|
|
176
|
+
maxWallTimeMs: 120000
|
|
177
|
+
}
|
|
178
|
+
}))
|
|
179
|
+
.addText('Translate this book to neutral Latin American Spanish.')
|
|
180
|
+
.message();
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Markdown headings become stable nested sections, lists expose item arrays, and the original source order remains reconstructable. The planner receives only a content-free variable manifest: paths, types, array item counts, serialized UTF-8 byte estimates, string lengths, line and paragraph counts, structural summaries, and partition hints. The document values enter only the isolated sandbox, where generated JavaScript can inspect `variables` and call registered workers through `query()`.
|
|
184
|
+
|
|
185
|
+
A worker normally supplies `model: anotherModelMixInstance`. To offer the current parent chain under a name, register it with `useParent: true` instead; defining both is rejected.
|
|
186
|
+
|
|
187
|
+
Planner instructions live in Markdown templates under `plugins/rlm/prompts/`. The plugin supplies manifests and limits through ModelMix `assign()` and loads the system prompt through `systemFile`, so relative includes work and runtime values are rendered exactly once.
|
|
188
|
+
|
|
104
189
|
## 🎛️ Unified Effort Scale
|
|
105
190
|
|
|
106
191
|
Control reasoning depth with one ModelMix policy value (`-1` adaptive, or `0`–`100`). It lives **outside** native `options` and is mapped to each provider’s effort API at request time.
|
|
@@ -120,13 +205,26 @@ ModelMix.new().effort(-1).minimaxM3().addText('...').message();
|
|
|
120
205
|
|--|------|-------|-------|-------|--------|------|
|
|
121
206
|
| OpenAI | `none` | `low` | `medium` | `high` | `xhigh` | — |
|
|
122
207
|
| Anthropic | `low` | `medium` | `high` | `xhigh` | `max` | adaptive |
|
|
123
|
-
| Gemini 3
|
|
208
|
+
| Gemini 3+ | `minimal` | `low` | `medium` | `high` | — | dynamic |
|
|
124
209
|
| DeepSeek V4 | off | `low`↑ | `high`↑ | `high`↑ | `max`↑ | — |
|
|
125
210
|
| MiniMax M3 | off | adaptive | adaptive | adaptive | adaptive | adaptive |
|
|
126
211
|
|
|
127
|
-
|
|
212
|
+
### Provider-specific behavior
|
|
213
|
+
|
|
214
|
+
- **Gemini:** Gemini 3+ uses bands 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.7 Flash clamps these bands to `low` / `low` / `medium` / `high`; `-1` leaves its native `medium` default unchanged. Gemini 2.5 maps 0–100 to `thinkingBudget`.
|
|
215
|
+
- **DeepSeek:** `↑` means thinking is enabled; `off` means it is disabled.
|
|
216
|
+
- **MiniMax:** `off` maps to `thinking.disabled`; `adaptive` maps to `thinking.type=adaptive`.
|
|
217
|
+
- **Anthropic:** Claude 5, Fable, Opus 4.6+, and Sonnet 4.6+ use adaptive thinking with `output_config.effort`. Sonnet 4.5 and Haiku 4.5 use `thinking.type=enabled` with `budget_tokens`.
|
|
218
|
+
- **Grok 4.6:** 0–39 / 40–59 / 60–79 / 80–100 map to `low` / `medium` / `high` / `xhigh`. Without effort, Grok uses its native `high` default.
|
|
128
219
|
|
|
129
|
-
|
|
220
|
+
`-1` uses the provider's adaptive or dynamic mode when available; otherwise it is a no-op. Effort levels are clamped to each model's supported range.
|
|
221
|
+
|
|
222
|
+
### Migrating from thinking shorthands
|
|
223
|
+
|
|
224
|
+
The former `*think()` methods were removed. Use `.effort(n).<model>()` with `0`–`100` or `-1` instead.
|
|
225
|
+
|
|
226
|
+
- **Kimi:** use `kimiK25()` or `kimiK26()`.
|
|
227
|
+
- **Grok 4.20:** `.grok420()` selects the non-reasoning model. Use `.effort(20+).grok420()` or `.effort(-1).grok420()` to select the reasoning model.
|
|
130
228
|
|
|
131
229
|
## 🔧 Model Context Protocol (MCP) Integration
|
|
132
230
|
|
|
@@ -188,7 +286,8 @@ Here's a comprehensive list of available methods:
|
|
|
188
286
|
| `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00/\$15.00][2] |
|
|
189
287
|
| `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00/\$5.00][2] |
|
|
190
288
|
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00/\$12.00][3] |
|
|
191
|
-
| `
|
|
289
|
+
| `gemini37flash()` | Google | gemini-3.7-flash | [\$0.75/\$3.75][3] |
|
|
290
|
+
| `gemini36flash()` | Google | gemini-3.6-flash | [\$0.75/\$3.75][3] |
|
|
192
291
|
| `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75/\$4.50][3] |
|
|
193
292
|
| `gemini35flashLite()`| Google | gemini-3.5-flash-lite | [\$0.30/\$2.50][3] |
|
|
194
293
|
| `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25/\$1.50][3] |
|
|
@@ -213,6 +312,8 @@ Here's a comprehensive list of available methods:
|
|
|
213
312
|
| `kimiK25()` | Together | Kimi-K2.5 | [\$0.50/\$2.80][7] |
|
|
214
313
|
| `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95/\$4.00][10] |
|
|
215
314
|
|
|
315
|
+
Gemini 3.7 Flash and 3.6 Flash use Google's introductory standard pricing through December 31, 2026; standard rates double on January 1, 2027.
|
|
316
|
+
|
|
216
317
|
[1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
|
|
217
318
|
[2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
|
|
218
319
|
[3]: https://ai.google.dev/gemini-api/docs/pricing "Google AI for Developers"
|
|
@@ -661,6 +762,7 @@ Every response from `raw()` now includes a `tokens` object with the following st
|
|
|
661
762
|
tokens: {
|
|
662
763
|
input: 1200, // Total input tokens, including cache reads and writes
|
|
663
764
|
output: 50, // Number of output tokens
|
|
765
|
+
thinking: 0, // Internal reasoning tokens reported separately
|
|
664
766
|
total: 1250, // Total tokens used
|
|
665
767
|
cached: 1024, // Input tokens read from cache
|
|
666
768
|
cacheWrite: 0, // Input tokens written to cache
|
|
@@ -696,7 +798,7 @@ console.log(model.lastRaw.tokens);
|
|
|
696
798
|
// Same normalized token and cost structure returned by raw()
|
|
697
799
|
```
|
|
698
800
|
|
|
699
|
-
`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).
|
|
801
|
+
`thinking` contains internal reasoning tokens when a provider reports them separately; cost calculation bills them at the output rate. `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).
|
|
700
802
|
|
|
701
803
|
### GPT-5.6 prompt caching
|
|
702
804
|
|
|
@@ -878,7 +980,7 @@ new ModelMix(args = { options: {}, config: {} })
|
|
|
878
980
|
- `message`: The text response from the model
|
|
879
981
|
- `think`: Reasoning/thinking content (if available)
|
|
880
982
|
- `toolCalls`: Array of tool calls made by the model (if any)
|
|
881
|
-
- `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)
|
|
983
|
+
- `tokens`: Normalized token counts (`input`, `output`, `thinking`, `total`, `cached`, `cacheWrite`, `cacheWrite5m`, `cacheWrite1h`, `uncachedInput`, `cacheHitRate`), cache economics (`cacheSavings`, `cacheWritePremium`, `breakEvenHits`), plus `cost`, `costBreakdown` (USD), and `speed` (output tokens/sec)
|
|
882
984
|
- `response`: The raw API response
|
|
883
985
|
- `stream(callback)`: Sends the message and streams the response, invoking the callback with each streamed part.
|
|
884
986
|
- `json(schemaExample, descriptions = {}, options = {})`: Forces the model to return a response in a specific JSON format.
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# ModelMix Plugin Architecture and RLM Plugin Specification
|
|
2
|
+
|
|
3
|
+
Status: living draft
|
|
4
|
+
|
|
5
|
+
This document records the product and engineering decisions for adding a generic
|
|
6
|
+
plugin architecture to ModelMix and implementing Recursive Language Model (RLM)
|
|
7
|
+
behavior in a separately installed plugin. Confirmed decisions are normative.
|
|
8
|
+
Items marked **Open** are not implementation-ready and will be resolved before
|
|
9
|
+
development starts.
|
|
10
|
+
|
|
11
|
+
## 1. Goals
|
|
12
|
+
|
|
13
|
+
### Generic ModelMix plugin architecture
|
|
14
|
+
|
|
15
|
+
- Let a plugin extend one `ModelMix` instance without affecting other instances.
|
|
16
|
+
- Let plugins inspect or transform a prepared request before provider execution.
|
|
17
|
+
- Let plugins wrap an execution as middleware and either call the next handler or
|
|
18
|
+
return the final result themselves.
|
|
19
|
+
- Let plugins start child ModelMix executions with explicit control over plugin
|
|
20
|
+
inheritance.
|
|
21
|
+
- Keep plugin-specific dependencies and behavior out of the ModelMix package.
|
|
22
|
+
|
|
23
|
+
### RLM plugin
|
|
24
|
+
|
|
25
|
+
- Let applications submit prompts and documents that would otherwise consume a
|
|
26
|
+
very large context window.
|
|
27
|
+
- Keep bulk document content outside the planner model's context.
|
|
28
|
+
- Represent structured input as programmatically accessible variables.
|
|
29
|
+
- Let a planner model generate a program that inspects those variables,
|
|
30
|
+
decomposes an abstract task, and invokes other language models recursively.
|
|
31
|
+
- Let the planner choose faster, cheaper, or more capable workers for each
|
|
32
|
+
subtask using an explicit worker catalog.
|
|
33
|
+
- Execute model-generated code in a restricted environment with controlled
|
|
34
|
+
resource usage.
|
|
35
|
+
|
|
36
|
+
Translation is an example workload, not a special RLM operation. The same
|
|
37
|
+
mechanism must support summarization, extraction, analysis, rewriting,
|
|
38
|
+
classification, synthesis, and other tasks.
|
|
39
|
+
|
|
40
|
+
## 2. Non-goals
|
|
41
|
+
|
|
42
|
+
- ModelMix will not contain Markdown parsing, code isolation, RLM prompts, worker
|
|
43
|
+
selection policy, or recursive orchestration logic.
|
|
44
|
+
- The ModelMix package will not depend on `isolated-vm` or the parser libraries
|
|
45
|
+
selected by the RLM plugin.
|
|
46
|
+
- ModelMix will not assign universal intelligence rankings to models.
|
|
47
|
+
- The initial implementation will not silently activate plugins globally.
|
|
48
|
+
|
|
49
|
+
## 3. Package boundary
|
|
50
|
+
|
|
51
|
+
The feature is split into two deliverables:
|
|
52
|
+
|
|
53
|
+
1. **ModelMix core:** generic plugin registration, middleware execution, child
|
|
54
|
+
invocation, execution metadata, and TypeScript declarations.
|
|
55
|
+
2. **RLM plugin:** document structuring, planner instructions, worker catalog,
|
|
56
|
+
recursion policy, sandbox execution, limits, and RLM-specific diagnostics.
|
|
57
|
+
|
|
58
|
+
`@modelmix/rlm` is used as a working package name in examples and is not yet a
|
|
59
|
+
confirmed publication name.
|
|
60
|
+
|
|
61
|
+
## 4. Instance-scoped registration
|
|
62
|
+
|
|
63
|
+
A plugin is installed separately and activated explicitly on an instance:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
const { ModelMix } = require('modelmix');
|
|
67
|
+
const { rlm } = require('@modelmix/rlm');
|
|
68
|
+
|
|
69
|
+
const mmix = ModelMix.new()
|
|
70
|
+
.gpt56luna()
|
|
71
|
+
.use(rlm({
|
|
72
|
+
maxDepth: 3
|
|
73
|
+
}));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Confirmed behavior:
|
|
77
|
+
|
|
78
|
+
- `.use(plugin)` affects only that instance and instances that inherit from it.
|
|
79
|
+
- Registering a plugin does not change global ModelMix behavior.
|
|
80
|
+
- Instances without plugins retain their existing execution behavior.
|
|
81
|
+
|
|
82
|
+
## 5. Generic middleware contract
|
|
83
|
+
|
|
84
|
+
A plugin can wrap the complete ModelMix execution:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const plugin = {
|
|
88
|
+
name: 'example',
|
|
89
|
+
|
|
90
|
+
async execute(context, next) {
|
|
91
|
+
// Option A: inspect or modify the request, then continue.
|
|
92
|
+
return next();
|
|
93
|
+
|
|
94
|
+
// Option B: perform custom work and return a complete result without
|
|
95
|
+
// invoking the provider handler or later middleware.
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The execution context needs a provider-neutral request representation:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
interface PluginExecutionContext {
|
|
104
|
+
request: {
|
|
105
|
+
system: string;
|
|
106
|
+
messages: ChatMessage[];
|
|
107
|
+
options: ModelMixOptions;
|
|
108
|
+
config: ModelMixConfig;
|
|
109
|
+
outputMode: 'message' | 'json' | 'block' | 'raw' | 'stream';
|
|
110
|
+
};
|
|
111
|
+
execution: {
|
|
112
|
+
executionId: string;
|
|
113
|
+
parentExecutionId: string | null;
|
|
114
|
+
depth: number;
|
|
115
|
+
};
|
|
116
|
+
invoke(input: ChildInvocation): Promise<ModelMixResult>;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Confirmed behavior:
|
|
121
|
+
|
|
122
|
+
- Calling `next()` continues the middleware chain and eventually invokes the
|
|
123
|
+
selected provider.
|
|
124
|
+
- A plugin can short-circuit the chain and return the final result.
|
|
125
|
+
- A short-circuit result must satisfy the normal `ModelMixResult` contract so
|
|
126
|
+
that `.message()`, `.json()`, `.block()`, and `.raw()` remain consistent.
|
|
127
|
+
- RLM-specific concepts must not appear in this core interface.
|
|
128
|
+
|
|
129
|
+
**Open:** exact mutability rules for `context.request`, middleware ordering,
|
|
130
|
+
duplicate plugin names, registration-time lifecycle hooks, teardown, and error
|
|
131
|
+
hooks.
|
|
132
|
+
|
|
133
|
+
## 6. Child invocation and plugin inheritance
|
|
134
|
+
|
|
135
|
+
Plugins can start independent child executions through `context.invoke()`:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
const result = await context.invoke({
|
|
139
|
+
system: 'Extract the named entities from this section.',
|
|
140
|
+
messages: [{ role: 'user', content: section }],
|
|
141
|
+
plugins: 'inherit',
|
|
142
|
+
history: false
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Confirmed defaults:
|
|
147
|
+
|
|
148
|
+
- Child executions inherit all plugins, including the plugin that created the
|
|
149
|
+
child invocation.
|
|
150
|
+
- Child executions never inherit conversation history by default.
|
|
151
|
+
- A child receives only the system prompt, messages, tools, options, and other
|
|
152
|
+
inputs explicitly supplied for that invocation.
|
|
153
|
+
- The runtime records `executionId`, `parentExecutionId`, and `depth` across the
|
|
154
|
+
execution tree.
|
|
155
|
+
- `.new()` inherits registered plugins but starts without message history,
|
|
156
|
+
consistent with its current instance-creation behavior.
|
|
157
|
+
|
|
158
|
+
The generic API must support selective inheritance:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
plugins: 'inherit'
|
|
162
|
+
plugins: 'none'
|
|
163
|
+
plugins: { exclude: ['rlm'] }
|
|
164
|
+
plugins: { include: ['metrics'] }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Open:** whether `history: true` will be supported at all. RLM child calls will
|
|
168
|
+
always use `history: false`, even if the generic architecture later permits
|
|
169
|
+
other plugins to request history explicitly.
|
|
170
|
+
|
|
171
|
+
## 7. Recursive depth
|
|
172
|
+
|
|
173
|
+
RLM uses an explicitly configured recursion limit:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
rlm({ maxDepth: 3 })
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The current depth definition is:
|
|
180
|
+
|
|
181
|
+
| Depth | Meaning |
|
|
182
|
+
|---:|---|
|
|
183
|
+
| 0 | Initial planning execution |
|
|
184
|
+
| 1 | First recursive decomposition |
|
|
185
|
+
| 2 | Second recursive decomposition |
|
|
186
|
+
| 3 | Last RLM-enabled decomposition |
|
|
187
|
+
| 4 | Direct leaf execution with RLM excluded |
|
|
188
|
+
|
|
189
|
+
`maxDepth` is required. Registration must fail early when it is absent, invalid,
|
|
190
|
+
or negative. There is no implicit fallback value.
|
|
191
|
+
|
|
192
|
+
Current working decision: when the next invocation would exceed `maxDepth`, the
|
|
193
|
+
runtime excludes only RLM and sends the selected fragment directly to the chosen
|
|
194
|
+
worker. Other inherited plugins remain active.
|
|
195
|
+
|
|
196
|
+
## 8. Worker catalog and model selection
|
|
197
|
+
|
|
198
|
+
The initial planner must be able to choose among named workers with different
|
|
199
|
+
capability, cost, and speed characteristics. Each worker is a ModelMix instance
|
|
200
|
+
and can therefore contain its own provider fallback chain.
|
|
201
|
+
|
|
202
|
+
Proposed configuration:
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
const workers = {
|
|
206
|
+
fast: {
|
|
207
|
+
model: ModelMix.new().gpt41nano(),
|
|
208
|
+
intelligence: 1,
|
|
209
|
+
cost: 1,
|
|
210
|
+
speed: 5,
|
|
211
|
+
description: 'Extraction, classification, and simple transformations'
|
|
212
|
+
},
|
|
213
|
+
balanced: {
|
|
214
|
+
model: ModelMix.new().gpt41mini(),
|
|
215
|
+
intelligence: 3,
|
|
216
|
+
cost: 2,
|
|
217
|
+
speed: 4,
|
|
218
|
+
description: 'General analysis and writing'
|
|
219
|
+
},
|
|
220
|
+
expert: {
|
|
221
|
+
model: ModelMix.new().gpt56luna(),
|
|
222
|
+
intelligence: 5,
|
|
223
|
+
cost: 5,
|
|
224
|
+
speed: 2,
|
|
225
|
+
description: 'Complex reasoning and final synthesis'
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const mmix = ModelMix.new()
|
|
230
|
+
.gpt56luna()
|
|
231
|
+
.use(rlm({ maxDepth: 3, workers }));
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Confirmed requirements:
|
|
235
|
+
|
|
236
|
+
- The planner receives a manifest containing worker names and decision metadata,
|
|
237
|
+
not ModelMix objects, credentials, or provider internals.
|
|
238
|
+
- The generated program selects a worker by its registered name.
|
|
239
|
+
- Different subtasks in the same execution can use different workers.
|
|
240
|
+
- Independent calls can use different workers concurrently.
|
|
241
|
+
- Worker intelligence is developer-supplied rather than inferred by ModelMix.
|
|
242
|
+
- ModelMix pricing data can supplement the relative cost rating when pricing is
|
|
243
|
+
available.
|
|
244
|
+
- A worker can define its own fallback chain.
|
|
245
|
+
- RLM supports both an explicit worker pool and inherited use of the parent
|
|
246
|
+
instance's model chain.
|
|
247
|
+
|
|
248
|
+
Proposed sandbox API:
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
const entities = await query({
|
|
252
|
+
worker: 'fast',
|
|
253
|
+
system: 'Extract named entities.',
|
|
254
|
+
message: chapter
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Open:** exact rating scale, whether the parent chain appears as a `default`
|
|
259
|
+
worker, worker capability tags, monetary price representation, and enforcement
|
|
260
|
+
of budgets for cost, calls, generated tokens, wall time, and concurrency.
|
|
261
|
+
|
|
262
|
+
## 9. RLM input representation
|
|
263
|
+
|
|
264
|
+
The planner must not receive the full large document. It receives:
|
|
265
|
+
|
|
266
|
+
- the user's task;
|
|
267
|
+
- a compact description of the external data environment;
|
|
268
|
+
- variable names, types, sizes, and structural relationships;
|
|
269
|
+
- the worker manifest;
|
|
270
|
+
- the callable sandbox API;
|
|
271
|
+
- execution limits and output requirements.
|
|
272
|
+
|
|
273
|
+
The document remains available only inside the sandbox as structured variables.
|
|
274
|
+
For Markdown input, the intended semantic mapping is:
|
|
275
|
+
|
|
276
|
+
- headings define named sections and hierarchy;
|
|
277
|
+
- chapter-level sections become individually addressable variables;
|
|
278
|
+
- bullet and numbered lists become arrays when their structure permits it;
|
|
279
|
+
- nested headings become nested objects or an equivalent traversable tree;
|
|
280
|
+
- prose content remains external and is described by metadata such as character
|
|
281
|
+
count rather than copied into the planner prompt;
|
|
282
|
+
- ordering and enough source metadata are preserved to reconstruct an output in
|
|
283
|
+
the original document order.
|
|
284
|
+
|
|
285
|
+
For example, a ten-chapter book should expose ten addressable chapter values
|
|
286
|
+
instead of one opaque string. Generated code can select, inspect, partition, and
|
|
287
|
+
process those values without loading the entire book into the planner context.
|
|
288
|
+
|
|
289
|
+
### Variable metadata
|
|
290
|
+
|
|
291
|
+
The planner receives a content-free descriptor for every external variable. The
|
|
292
|
+
descriptor is operational input, not debug-only information: generated programs
|
|
293
|
+
use it together with the execution limits to decide whether to process a value
|
|
294
|
+
directly, batch array items, or split large strings at semantic boundaries.
|
|
295
|
+
|
|
296
|
+
Confirmed metadata:
|
|
297
|
+
|
|
298
|
+
- every variable has a stable path, type, and estimated serialized size;
|
|
299
|
+
- sizes use UTF-8 bytes of the JSON-serialized value so they are deterministic
|
|
300
|
+
across JavaScript runtimes; strings also expose raw UTF-8 bytes;
|
|
301
|
+
- strings expose Unicode character, UTF-16 code-unit, line, and paragraph counts;
|
|
302
|
+
- arrays expose item count, element-type distribution, total serialized size,
|
|
303
|
+
and minimum, maximum, and average serialized item size;
|
|
304
|
+
- homogeneous string arrays expose aggregate character, byte, line, and
|
|
305
|
+
paragraph statistics;
|
|
306
|
+
- object arrays expose a content-free field manifest with presence, type, and
|
|
307
|
+
string-size statistics instead of listing every item;
|
|
308
|
+
- objects expose key count and recursively described properties;
|
|
309
|
+
- descriptors never contain source strings, samples, credentials, ModelMix
|
|
310
|
+
objects, or provider configuration;
|
|
311
|
+
- non-JSON-serializable values and circular references fail before planning.
|
|
312
|
+
|
|
313
|
+
The planner prompt includes the sandbox memory limit and maximum query payload
|
|
314
|
+
size beside the descriptor. It must instruct generated code to compare those
|
|
315
|
+
limits with variable and item sizes, split oversized strings at paragraph or
|
|
316
|
+
other semantic boundaries, batch compatible array items, process independent
|
|
317
|
+
work concurrently within the configured limit, and preserve source order when
|
|
318
|
+
reassembling output.
|
|
319
|
+
|
|
320
|
+
RLM prompts are developer-controlled Markdown templates stored in the plugin
|
|
321
|
+
package. They use ModelMix's existing EJS contract and are rendered through
|
|
322
|
+
`assign()` plus `setSystemFromFile()` on the planner execution. The plugin does
|
|
323
|
+
not own a second template renderer. Manifest JSON, limits, worker metadata, and
|
|
324
|
+
planning hints are assigned data, rendered once, and may not recursively execute
|
|
325
|
+
EJS contained in variable paths or other runtime values. Relative Markdown
|
|
326
|
+
includes use ModelMix's normal file-template resolution.
|
|
327
|
+
|
|
328
|
+
The byte count estimates serialized transfer size rather than the JavaScript
|
|
329
|
+
engine's heap usage. Runtime heap consumption is implementation-dependent and
|
|
330
|
+
is enforced separately by the sandbox memory limit.
|
|
331
|
+
|
|
332
|
+
**Open:** exact variable naming, duplicate headings, introductory content,
|
|
333
|
+
heading depth, code blocks, tables, links, frontmatter, mixed content, malformed
|
|
334
|
+
Markdown, non-Markdown prompts, and round-trip fidelity.
|
|
335
|
+
|
|
336
|
+
## 10. RLM execution flow
|
|
337
|
+
|
|
338
|
+
The intended high-level flow is:
|
|
339
|
+
|
|
340
|
+
1. ModelMix renders its system and message templates normally.
|
|
341
|
+
2. The RLM middleware receives the prepared provider-neutral request.
|
|
342
|
+
3. RLM separates the task instructions from large data-bearing content.
|
|
343
|
+
4. RLM parses structured content and creates the external variable environment.
|
|
344
|
+
5. RLM assigns the variable manifest, workers, callable API, limits, and output
|
|
345
|
+
requirements to its Markdown planner template and lets ModelMix render it.
|
|
346
|
+
6. The initial model generates an executable orchestration program.
|
|
347
|
+
7. RLM validates and runs the program inside the restricted sandbox.
|
|
348
|
+
8. The program inspects variables and calls named workers through `query()`.
|
|
349
|
+
9. Independent subtasks run concurrently when allowed by configured limits.
|
|
350
|
+
10. Recursive calls re-enter RLM without history until the depth boundary.
|
|
351
|
+
11. The program combines intermediate results and returns the final value.
|
|
352
|
+
12. RLM converts that value into a normal `ModelMixResult`.
|
|
353
|
+
|
|
354
|
+
## 11. Isolation and security boundary
|
|
355
|
+
|
|
356
|
+
The RLM package owns the sandbox implementation and its dependency on
|
|
357
|
+
`isolated-vm` or a future replacement.
|
|
358
|
+
|
|
359
|
+
Required properties:
|
|
360
|
+
|
|
361
|
+
- Generated code cannot access Node.js globals, the filesystem, environment
|
|
362
|
+
variables, network APIs, credentials, or ModelMix instances.
|
|
363
|
+
- Only serializable document variables and explicitly registered callbacks enter
|
|
364
|
+
the sandbox.
|
|
365
|
+
- The only LLM operation exposed to generated code is the validated `query()`
|
|
366
|
+
interface.
|
|
367
|
+
- Worker names and invocation arguments are validated outside the sandbox.
|
|
368
|
+
- Memory, execution time, recursion, calls, concurrency, and output size are
|
|
369
|
+
enforceable outside model instructions.
|
|
370
|
+
- Sandbox disposal occurs on success, failure, timeout, or cancellation.
|
|
371
|
+
|
|
372
|
+
**Open:** concrete limit configuration and whether generated code is accepted
|
|
373
|
+
directly or parsed against a restricted JavaScript subset before execution.
|
|
374
|
+
|
|
375
|
+
## 12. Output modes
|
|
376
|
+
|
|
377
|
+
Because a plugin may complete an execution, RLM must preserve the caller's
|
|
378
|
+
chosen output mode:
|
|
379
|
+
|
|
380
|
+
- `.message()` returns a string.
|
|
381
|
+
- `.block()` returns extracted block content according to the existing contract.
|
|
382
|
+
- `.json()` returns data satisfying the requested schema.
|
|
383
|
+
- `.raw()` returns a complete `ModelMixResult`.
|
|
384
|
+
- `.stream()` requires an explicit streaming design.
|
|
385
|
+
|
|
386
|
+
**Open:** whether the first RLM version supports streaming. Recursive parallel
|
|
387
|
+
work does not naturally form one ordered token stream, so the likely initial
|
|
388
|
+
contract is either buffered final output or explicit rejection of `.stream()`.
|
|
389
|
+
|
|
390
|
+
## 13. Observability and accounting
|
|
391
|
+
|
|
392
|
+
An RLM result should make the execution tree inspectable without exposing prompt
|
|
393
|
+
content by default. Candidate metadata includes:
|
|
394
|
+
|
|
395
|
+
- planner and worker calls;
|
|
396
|
+
- selected worker per call;
|
|
397
|
+
- parent/child execution identifiers and depth;
|
|
398
|
+
- elapsed time and concurrency;
|
|
399
|
+
- input, output, cached, and cache-write tokens;
|
|
400
|
+
- estimated cost per call and aggregate cost;
|
|
401
|
+
- termination reason, including depth or budget boundaries.
|
|
402
|
+
|
|
403
|
+
**Open:** the public shape of this metadata and how it integrates with
|
|
404
|
+
`lastRaw.tokens`, debug levels, and plugins that also collect metrics.
|
|
405
|
+
|
|
406
|
+
## 14. Verification requirements
|
|
407
|
+
|
|
408
|
+
### ModelMix core
|
|
409
|
+
|
|
410
|
+
- Plugins are isolated per instance.
|
|
411
|
+
- `.new()` inherits plugins without inheriting history.
|
|
412
|
+
- Middleware executes in deterministic order.
|
|
413
|
+
- A plugin can transform a request and call `next()`.
|
|
414
|
+
- A plugin can return a complete result without calling `next()`.
|
|
415
|
+
- Child invocations support inherit, none, include, and exclude policies.
|
|
416
|
+
- Execution identifiers and depth are correct across nested calls.
|
|
417
|
+
- Plugin failures do not silently fall through to providers.
|
|
418
|
+
- Existing behavior remains unchanged when no plugins are registered.
|
|
419
|
+
- All supported non-streaming output modes preserve their contracts.
|
|
420
|
+
|
|
421
|
+
### RLM package
|
|
422
|
+
|
|
423
|
+
- Markdown fixtures produce stable semantic variable environments.
|
|
424
|
+
- Large content is absent from the planner request.
|
|
425
|
+
- The planner can choose among named workers.
|
|
426
|
+
- Independent queries execute concurrently within configured limits.
|
|
427
|
+
- Recursive calls contain no conversation history.
|
|
428
|
+
- The depth boundary excludes RLM and terminates recursion.
|
|
429
|
+
- Invalid workers and malformed generated code fail clearly.
|
|
430
|
+
- Sandbox access to filesystem, environment, network, and Node APIs is denied.
|
|
431
|
+
- Time, memory, call, concurrency, and output limits are enforced.
|
|
432
|
+
- Results and accounting aggregate planner and worker executions.
|
|
433
|
+
- A large-document fixture covers an abstract operation end to end with mocked
|
|
434
|
+
providers; translation can be one example but not the only tested task.
|
|
435
|
+
|
|
436
|
+
## 15. Evidence from the current laboratory
|
|
437
|
+
|
|
438
|
+
The prototypes under `demo/lab/` establish the initial direction:
|
|
439
|
+
|
|
440
|
+
- `rlm-translate.js` describes external data to a planner, generates an async
|
|
441
|
+
JavaScript program, exposes an LLM callback, and executes the program in
|
|
442
|
+
`isolated-vm`.
|
|
443
|
+
- `rlm-story.js` demonstrates parallel and sequential query planning over an
|
|
444
|
+
abstract task without document input.
|
|
445
|
+
- The current translation prototype exposes the entire Markdown document as one
|
|
446
|
+
`input` string and chunks it by character count. The production plugin must
|
|
447
|
+
replace this with semantic document structure and enforced runtime policy.
|
|
448
|
+
|
|
449
|
+
The generic hook belongs after ModelMix has rendered templates into a request
|
|
450
|
+
snapshot and before provider-specific message conversion. This preserves one
|
|
451
|
+
neutral plugin contract across OpenAI, Anthropic, Google, and other providers.
|
|
452
|
+
|
|
453
|
+
## 16. Decisions still required before implementation
|
|
454
|
+
|
|
455
|
+
1. Hard budgets and what happens when each budget is exhausted.
|
|
456
|
+
2. Exact worker metadata and selection contract.
|
|
457
|
+
3. Middleware ordering and plugin lifecycle.
|
|
458
|
+
4. Request mutability and validation between middleware stages.
|
|
459
|
+
5. Markdown-to-variable mapping and non-Markdown behavior.
|
|
460
|
+
6. Separation of task instructions from data-bearing prompt content.
|
|
461
|
+
7. Tools available to child executions.
|
|
462
|
+
8. Output schema propagation and final-result validation.
|
|
463
|
+
9. Streaming and cancellation behavior.
|
|
464
|
+
10. Metrics, token accounting, and debug representation.
|
|
465
|
+
11. Package name and supported module formats.
|
package/demo/gemini.js
CHANGED
|
@@ -12,9 +12,9 @@ const mmix = new ModelMix({
|
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
// Using
|
|
16
|
-
console.log("\n" + '--------|
|
|
17
|
-
const flash = await mmix.
|
|
15
|
+
// Using Gemini 3.7 Flash with the built-in method
|
|
16
|
+
console.log("\n" + '--------| gemini37flash() |--------');
|
|
17
|
+
const flash = await mmix.gemini37flash()
|
|
18
18
|
.addText('Hi there! Do you like cats?')
|
|
19
19
|
.message();
|
|
20
20
|
|
|
@@ -41,4 +41,3 @@ const customModel = mmix.new().attach('gemini-2.5-flash', new MixGoogle());
|
|
|
41
41
|
|
|
42
42
|
const custom = await customModel.addText('Tell me a short joke about cats.').message();
|
|
43
43
|
console.log(custom);
|
|
44
|
-
|
package/demo/short.js
CHANGED
|
@@ -11,7 +11,7 @@ const setup = {
|
|
|
11
11
|
const mmix = await ModelMix.new(setup)
|
|
12
12
|
.sonnet46() // (main model) Anthropic claude-sonnet-4-6
|
|
13
13
|
.gpt56luna() // (fallback 1) OpenAI gpt-5.6-luna
|
|
14
|
-
.
|
|
14
|
+
.gemini37flash() // (fallback 2) Google gemini-3.7-flash
|
|
15
15
|
.gpt41nano() // (fallback 3) OpenAI gpt-4.1-nano
|
|
16
16
|
.grok46() // (fallback 4) Grok grok-4.6
|
|
17
17
|
.addText("What's your name?");
|