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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { describeVariables } = require('./variable-descriptors');
|
|
3
|
+
|
|
4
|
+
const PLANNER_SYSTEM_TEMPLATE = path.resolve(__dirname, '../prompts/planner.md');
|
|
5
|
+
|
|
6
|
+
function positiveInteger(value, name) {
|
|
7
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
8
|
+
throw new TypeError(`${name} must be a positive integer.`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function planningHint(name, descriptor, maxQueryBytes) {
|
|
14
|
+
const payloadBytes = descriptor.utf8Bytes ?? descriptor.estimatedBytes;
|
|
15
|
+
if (payloadBytes <= maxQueryBytes) {
|
|
16
|
+
return {
|
|
17
|
+
path: name,
|
|
18
|
+
strategy: 'direct',
|
|
19
|
+
reason: 'The complete serialized value fits within one query payload.'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (descriptor.type === 'string') {
|
|
23
|
+
return {
|
|
24
|
+
path: name,
|
|
25
|
+
strategy: 'split-string-semantically',
|
|
26
|
+
boundary: 'paragraph',
|
|
27
|
+
reason: 'The string is larger than the maximum query payload.'
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (descriptor.type === 'array') {
|
|
31
|
+
const oversizedFields = [];
|
|
32
|
+
for (const [field, fieldDescriptor] of Object.entries(descriptor.itemShape.properties || {})) {
|
|
33
|
+
if (fieldDescriptor.stringSize?.utf8Bytes.max > maxQueryBytes) oversizedFields.push(field);
|
|
34
|
+
}
|
|
35
|
+
if (descriptor.itemSize.max > maxQueryBytes) {
|
|
36
|
+
return {
|
|
37
|
+
path: name,
|
|
38
|
+
strategy: 'split-oversized-items-semantically',
|
|
39
|
+
boundary: 'paragraph',
|
|
40
|
+
oversizedStringFields: oversizedFields,
|
|
41
|
+
reason: 'At least one array item is larger than the maximum query payload.'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const averageBytes = Math.max(1, descriptor.itemSize.average);
|
|
45
|
+
return {
|
|
46
|
+
path: name,
|
|
47
|
+
strategy: 'batch-array-items',
|
|
48
|
+
suggestedMaxItemsPerQuery: Math.max(1, Math.floor(maxQueryBytes / averageBytes)),
|
|
49
|
+
reason: 'The array is larger than one query payload while individual items fit.'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
path: name,
|
|
54
|
+
strategy: 'select-properties-or-descendants',
|
|
55
|
+
reason: 'The structured value is larger than the maximum query payload.'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function plannerTemplateData({
|
|
60
|
+
variables,
|
|
61
|
+
limits,
|
|
62
|
+
workerManifest,
|
|
63
|
+
outputMode = 'raw',
|
|
64
|
+
outputSchema = null
|
|
65
|
+
}) {
|
|
66
|
+
if (!limits || typeof limits !== 'object' || Array.isArray(limits)) {
|
|
67
|
+
throw new TypeError('limits must be a plain object.');
|
|
68
|
+
}
|
|
69
|
+
const maxQueryBytes = positiveInteger(limits.maxQueryBytes, 'limits.maxQueryBytes');
|
|
70
|
+
const sandboxMemoryBytes = positiveInteger(limits.sandboxMemoryBytes, 'limits.sandboxMemoryBytes');
|
|
71
|
+
const maxConcurrentQueries = positiveInteger(
|
|
72
|
+
limits.maxConcurrentQueries,
|
|
73
|
+
'limits.maxConcurrentQueries'
|
|
74
|
+
);
|
|
75
|
+
const manifest = describeVariables(variables);
|
|
76
|
+
const planningHints = Object.entries(manifest.descriptors)
|
|
77
|
+
.map(([name, descriptor]) => planningHint(name, descriptor, maxQueryBytes));
|
|
78
|
+
if (!workerManifest || typeof workerManifest !== 'object' || Array.isArray(workerManifest)) {
|
|
79
|
+
throw new TypeError('workerManifest must be a plain object.');
|
|
80
|
+
}
|
|
81
|
+
if (!['message', 'json', 'block', 'raw'].includes(outputMode)) {
|
|
82
|
+
throw new TypeError(`Unsupported RLM planner output mode "${outputMode}".`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
variableManifest: JSON.stringify(manifest, null, 2),
|
|
87
|
+
processingLimits: JSON.stringify({
|
|
88
|
+
maxQueryBytes,
|
|
89
|
+
sandboxMemoryBytes,
|
|
90
|
+
maxConcurrentQueries
|
|
91
|
+
}, null, 2),
|
|
92
|
+
planningHints: JSON.stringify(planningHints, null, 2),
|
|
93
|
+
workerManifest: JSON.stringify(workerManifest, null, 2),
|
|
94
|
+
outputRequirements: JSON.stringify({
|
|
95
|
+
mode: outputMode,
|
|
96
|
+
schema: outputSchema
|
|
97
|
+
}, null, 2),
|
|
98
|
+
maxQueryBytes,
|
|
99
|
+
maxConcurrentQueries
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function createPlannerInvocation({
|
|
104
|
+
task,
|
|
105
|
+
variables,
|
|
106
|
+
limits,
|
|
107
|
+
workerManifest,
|
|
108
|
+
outputMode = 'raw',
|
|
109
|
+
outputSchema = null
|
|
110
|
+
}) {
|
|
111
|
+
if (typeof task !== 'string' || task.trim().length === 0) {
|
|
112
|
+
throw new TypeError('task must be a non-empty string.');
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
systemFile: PLANNER_SYSTEM_TEMPLATE,
|
|
116
|
+
assign: plannerTemplateData({
|
|
117
|
+
variables,
|
|
118
|
+
limits,
|
|
119
|
+
workerManifest,
|
|
120
|
+
outputMode,
|
|
121
|
+
outputSchema
|
|
122
|
+
}),
|
|
123
|
+
messages: [{
|
|
124
|
+
role: 'user',
|
|
125
|
+
content: [{ type: 'text', text: task }]
|
|
126
|
+
}],
|
|
127
|
+
plugins: { exclude: ['rlm'] },
|
|
128
|
+
history: false,
|
|
129
|
+
outputMode: 'raw'
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
PLANNER_SYSTEM_TEMPLATE,
|
|
135
|
+
createPlannerInvocation,
|
|
136
|
+
plannerTemplateData
|
|
137
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const { RlmLimitError, validateRuntimeLimits } = require('./budget');
|
|
2
|
+
const { createIsolatedVmSandbox } = require('./isolated-vm-sandbox');
|
|
3
|
+
const { parseMarkdownDocument } = require('./markdown');
|
|
4
|
+
const { createPlannerInvocation } = require('./planner-prompt');
|
|
5
|
+
const {
|
|
6
|
+
RlmExecutionState,
|
|
7
|
+
createQueryRuntime,
|
|
8
|
+
requestTask,
|
|
9
|
+
sumTokens
|
|
10
|
+
} = require('./runtime');
|
|
11
|
+
const { createWorkerCatalog } = require('./worker-catalog');
|
|
12
|
+
|
|
13
|
+
function isPlainObject(value) {
|
|
14
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
15
|
+
const prototype = Object.getPrototypeOf(value);
|
|
16
|
+
return prototype === Object.prototype || prototype === null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validateSandbox(sandbox) {
|
|
20
|
+
if (!sandbox || typeof sandbox !== 'object' || typeof sandbox.execute !== 'function') {
|
|
21
|
+
throw new TypeError('sandbox must define execute({ code, variables, query, limits }).');
|
|
22
|
+
}
|
|
23
|
+
return sandbox;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function validateDocuments(documents) {
|
|
27
|
+
if (documents === undefined) return {};
|
|
28
|
+
if (!isPlainObject(documents)) {
|
|
29
|
+
throw new TypeError('documents must be a plain object.');
|
|
30
|
+
}
|
|
31
|
+
for (const [name, document] of Object.entries(documents)) {
|
|
32
|
+
if (!isPlainObject(document)) {
|
|
33
|
+
throw new TypeError(`documents.${name} must be a plain object.`);
|
|
34
|
+
}
|
|
35
|
+
if (document.format !== 'markdown') {
|
|
36
|
+
throw new TypeError(`documents.${name}.format must be "markdown".`);
|
|
37
|
+
}
|
|
38
|
+
if (typeof document.content !== 'string') {
|
|
39
|
+
throw new TypeError(`documents.${name}.content must be a string.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return documents;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function createExternalVariables(variables, documents) {
|
|
46
|
+
const result = { ...variables };
|
|
47
|
+
for (const [name, document] of Object.entries(documents)) {
|
|
48
|
+
if (Object.prototype.hasOwnProperty.call(result, name)) {
|
|
49
|
+
throw new TypeError(`External variable "${name}" is defined more than once.`);
|
|
50
|
+
}
|
|
51
|
+
result[name] = await parseMarkdownDocument(document.content);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validateGeneratedCode(code) {
|
|
57
|
+
if (typeof code !== 'string' || code.trim().length === 0) {
|
|
58
|
+
throw new TypeError('RLM planner must return non-empty JavaScript code.');
|
|
59
|
+
}
|
|
60
|
+
if (/```|~~~/.test(code)) {
|
|
61
|
+
throw new SyntaxError('RLM planner returned Markdown fences instead of raw JavaScript.');
|
|
62
|
+
}
|
|
63
|
+
return code.trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function serializeResult(value) {
|
|
67
|
+
if (typeof value === 'string') return value;
|
|
68
|
+
let serialized;
|
|
69
|
+
try {
|
|
70
|
+
serialized = JSON.stringify(value);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new TypeError(`RLM sandbox result must be JSON-serializable: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
if (serialized === undefined) {
|
|
75
|
+
throw new TypeError('RLM sandbox result must be JSON-serializable.');
|
|
76
|
+
}
|
|
77
|
+
return serialized;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function executionInput(context, configuredVariables) {
|
|
81
|
+
const invocation = context.request.config.rlmInvocation;
|
|
82
|
+
if (invocation !== undefined) {
|
|
83
|
+
if (!isPlainObject(invocation) || !(invocation.state instanceof RlmExecutionState)) {
|
|
84
|
+
throw new TypeError('Invalid internal RLM invocation state.');
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
state: invocation.state,
|
|
88
|
+
task: invocation.task,
|
|
89
|
+
variables: invocation.variables
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
state: null,
|
|
94
|
+
task: requestTask(context.request),
|
|
95
|
+
variables: configuredVariables
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function runWithTimeout(operation, timeoutMs) {
|
|
100
|
+
let timeout;
|
|
101
|
+
return Promise.race([
|
|
102
|
+
operation(),
|
|
103
|
+
new Promise((_, reject) => {
|
|
104
|
+
timeout = setTimeout(() => reject(new RlmLimitError(
|
|
105
|
+
'maxWallTimeMs',
|
|
106
|
+
'RLM wall-time limit exceeded.'
|
|
107
|
+
)), timeoutMs);
|
|
108
|
+
})
|
|
109
|
+
]).finally(() => clearTimeout(timeout));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function rlm({ maxDepth, variables = {}, documents, workers, limits, sandbox } = {}) {
|
|
113
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 0) {
|
|
114
|
+
throw new TypeError('maxDepth must be a non-negative integer.');
|
|
115
|
+
}
|
|
116
|
+
if (!isPlainObject(variables)) {
|
|
117
|
+
throw new TypeError('variables must be a plain object.');
|
|
118
|
+
}
|
|
119
|
+
const validatedDocuments = validateDocuments(documents);
|
|
120
|
+
const catalog = createWorkerCatalog(workers);
|
|
121
|
+
const validatedLimits = validateRuntimeLimits(limits);
|
|
122
|
+
const sandboxAdapter = validateSandbox(
|
|
123
|
+
sandbox === undefined ? createIsolatedVmSandbox() : sandbox
|
|
124
|
+
);
|
|
125
|
+
const configuredVariables = createExternalVariables(variables, validatedDocuments);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
name: 'rlm',
|
|
129
|
+
async execute(context) {
|
|
130
|
+
if (context.request.outputMode === 'stream') {
|
|
131
|
+
throw new Error('RLM streaming is not supported; use a buffered output mode.');
|
|
132
|
+
}
|
|
133
|
+
const input = executionInput(context, await configuredVariables);
|
|
134
|
+
if (typeof input.task !== 'string' || input.task.trim().length === 0) {
|
|
135
|
+
throw new TypeError('RLM execution requires a non-empty task.');
|
|
136
|
+
}
|
|
137
|
+
if (!isPlainObject(input.variables)) {
|
|
138
|
+
throw new TypeError('RLM execution variables must be a plain object.');
|
|
139
|
+
}
|
|
140
|
+
const state = input.state || new RlmExecutionState(validatedLimits);
|
|
141
|
+
try {
|
|
142
|
+
const plannerStartedAt = Date.now();
|
|
143
|
+
const plannerResult = await state.budget.runPlanner(() => context.invoke(
|
|
144
|
+
createPlannerInvocation({
|
|
145
|
+
task: input.task,
|
|
146
|
+
variables: input.variables,
|
|
147
|
+
limits: validatedLimits,
|
|
148
|
+
workerManifest: catalog.manifest,
|
|
149
|
+
outputMode: context.request.outputMode,
|
|
150
|
+
outputSchema: context.request.config.schema || null
|
|
151
|
+
})
|
|
152
|
+
));
|
|
153
|
+
state.record('planner', plannerResult, {
|
|
154
|
+
worker: null,
|
|
155
|
+
elapsedMs: Date.now() - plannerStartedAt
|
|
156
|
+
});
|
|
157
|
+
const code = validateGeneratedCode(plannerResult.message);
|
|
158
|
+
const query = createQueryRuntime({
|
|
159
|
+
context,
|
|
160
|
+
catalog,
|
|
161
|
+
maxDepth,
|
|
162
|
+
state
|
|
163
|
+
});
|
|
164
|
+
const timeoutMs = Math.max(
|
|
165
|
+
1,
|
|
166
|
+
validatedLimits.maxWallTimeMs - state.budget.snapshot().elapsedMs
|
|
167
|
+
);
|
|
168
|
+
const value = await runWithTimeout(
|
|
169
|
+
() => sandboxAdapter.execute({
|
|
170
|
+
code,
|
|
171
|
+
variables: input.variables,
|
|
172
|
+
query,
|
|
173
|
+
limits: validatedLimits,
|
|
174
|
+
execution: context.execution,
|
|
175
|
+
timeoutMs
|
|
176
|
+
}),
|
|
177
|
+
timeoutMs
|
|
178
|
+
);
|
|
179
|
+
const message = serializeResult(value);
|
|
180
|
+
state.budget.accountFinalOutput(message);
|
|
181
|
+
return {
|
|
182
|
+
message,
|
|
183
|
+
tokens: sumTokens(state.calls),
|
|
184
|
+
rlm: state.diagnostics(context.execution)
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
error.rlm = state.diagnostics(
|
|
188
|
+
context.execution,
|
|
189
|
+
error.limit ? `limit:${error.limit}` : 'error'
|
|
190
|
+
);
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
createExternalVariables,
|
|
199
|
+
rlm,
|
|
200
|
+
serializeResult,
|
|
201
|
+
validateDocuments,
|
|
202
|
+
validateGeneratedCode
|
|
203
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const { createRuntimeBudget } = require('./budget');
|
|
2
|
+
|
|
3
|
+
function textContent(content) {
|
|
4
|
+
if (typeof content === 'string') return content;
|
|
5
|
+
if (!Array.isArray(content)) return '';
|
|
6
|
+
return content
|
|
7
|
+
.filter(part => part?.type === 'text' && typeof part.text === 'string')
|
|
8
|
+
.map(part => part.text)
|
|
9
|
+
.join('\n');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function requestTask(request) {
|
|
13
|
+
return request.messages
|
|
14
|
+
.filter(message => message?.role === 'user')
|
|
15
|
+
.map(message => textContent(message.content))
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.join('\n\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function queryPayloadBytes({ system, message }) {
|
|
21
|
+
return Buffer.byteLength(JSON.stringify({ system, message }), 'utf8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateQueryInput(input) {
|
|
25
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
26
|
+
throw new TypeError('query() expects a plain object.');
|
|
27
|
+
}
|
|
28
|
+
if (typeof input.worker !== 'string' || input.worker.length === 0) {
|
|
29
|
+
throw new TypeError('query.worker must be a non-empty string.');
|
|
30
|
+
}
|
|
31
|
+
if (typeof input.system !== 'string' || input.system.trim().length === 0) {
|
|
32
|
+
throw new TypeError('query.system must be a non-empty string.');
|
|
33
|
+
}
|
|
34
|
+
if (typeof input.message !== 'string') {
|
|
35
|
+
throw new TypeError('query.message must be a string.');
|
|
36
|
+
}
|
|
37
|
+
return input;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sumTokens(calls) {
|
|
41
|
+
const totals = {
|
|
42
|
+
input: 0,
|
|
43
|
+
output: 0,
|
|
44
|
+
total: 0,
|
|
45
|
+
cached: 0,
|
|
46
|
+
cacheWrite: 0,
|
|
47
|
+
cacheWrite5m: 0,
|
|
48
|
+
cacheWrite1h: 0,
|
|
49
|
+
uncachedInput: 0,
|
|
50
|
+
cost: 0
|
|
51
|
+
};
|
|
52
|
+
let found = false;
|
|
53
|
+
for (const call of calls) {
|
|
54
|
+
const tokens = call.tokens;
|
|
55
|
+
if (!tokens) continue;
|
|
56
|
+
found = true;
|
|
57
|
+
for (const key of Object.keys(totals)) {
|
|
58
|
+
if (Number.isFinite(tokens[key])) totals[key] += tokens[key];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return found ? totals : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class RlmExecutionState {
|
|
65
|
+
constructor(limits) {
|
|
66
|
+
this.budget = createRuntimeBudget(limits);
|
|
67
|
+
this.calls = [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
record(kind, result, details = {}, { includeTokens = true } = {}) {
|
|
71
|
+
this.calls.push({
|
|
72
|
+
kind,
|
|
73
|
+
...details,
|
|
74
|
+
execution: result.execution || null,
|
|
75
|
+
tokens: includeTokens ? (result.tokens || null) : null
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
diagnostics(rootExecution, terminationReason = 'completed') {
|
|
80
|
+
return {
|
|
81
|
+
execution: rootExecution,
|
|
82
|
+
calls: this.calls.map(call => ({ ...call })),
|
|
83
|
+
budget: this.budget.snapshot(),
|
|
84
|
+
terminationReason
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createQueryRuntime({ context, catalog, maxDepth, state }) {
|
|
90
|
+
return async rawInput => {
|
|
91
|
+
const startedAt = Date.now();
|
|
92
|
+
const input = validateQueryInput(rawInput);
|
|
93
|
+
const model = catalog.get(input.worker);
|
|
94
|
+
const directLeaf = context.execution.depth >= maxDepth;
|
|
95
|
+
const payloadBytes = queryPayloadBytes(input);
|
|
96
|
+
state.budget.assertQueryPayload(payloadBytes);
|
|
97
|
+
const invocation = directLeaf
|
|
98
|
+
? {
|
|
99
|
+
model,
|
|
100
|
+
system: input.system,
|
|
101
|
+
messages: [{ role: 'user', content: input.message }],
|
|
102
|
+
plugins: { exclude: ['rlm'] },
|
|
103
|
+
history: false,
|
|
104
|
+
outputMode: 'raw'
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
model,
|
|
108
|
+
messages: [{ role: 'user', content: input.system }],
|
|
109
|
+
config: {
|
|
110
|
+
rlmInvocation: {
|
|
111
|
+
state,
|
|
112
|
+
task: input.system,
|
|
113
|
+
variables: { input: input.message }
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
plugins: 'inherit',
|
|
117
|
+
history: false,
|
|
118
|
+
outputMode: 'raw'
|
|
119
|
+
};
|
|
120
|
+
const result = directLeaf
|
|
121
|
+
? await state.budget.runQuery(
|
|
122
|
+
{ payloadBytes },
|
|
123
|
+
() => context.invoke(invocation)
|
|
124
|
+
)
|
|
125
|
+
: await context.invoke(invocation);
|
|
126
|
+
if (typeof result.message !== 'string') {
|
|
127
|
+
throw new TypeError(`RLM worker "${input.worker}" must return a message string.`);
|
|
128
|
+
}
|
|
129
|
+
state.record('worker', result, {
|
|
130
|
+
worker: input.worker,
|
|
131
|
+
directLeaf,
|
|
132
|
+
depthBoundary: directLeaf ? maxDepth : null,
|
|
133
|
+
elapsedMs: Date.now() - startedAt
|
|
134
|
+
}, { includeTokens: directLeaf });
|
|
135
|
+
return result.message;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
RlmExecutionState,
|
|
141
|
+
createQueryRuntime,
|
|
142
|
+
queryPayloadBytes,
|
|
143
|
+
requestTask,
|
|
144
|
+
sumTokens,
|
|
145
|
+
validateQueryInput
|
|
146
|
+
};
|