llmjs2 1.7.1 → 2.0.1
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 +116 -18
- package/dist/index.d.mts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +1242 -0
- package/dist/index.mjs +1211 -0
- package/package.json +32 -58
- package/chain/AGENT_STEP_README.md +0 -102
- package/chain/README.md +0 -257
- package/chain/WORKFLOW_README.md +0 -85
- package/chain/agent-step-example.js +0 -232
- package/chain/docs/AGENT.md +0 -126
- package/chain/docs/GRAPH.md +0 -490
- package/chain/examples.js +0 -314
- package/chain/index.js +0 -31
- package/chain/lib/agent.js +0 -338
- package/chain/lib/flow/agent-step.js +0 -119
- package/chain/lib/flow/edge.js +0 -24
- package/chain/lib/flow/flow.js +0 -76
- package/chain/lib/flow/graph.js +0 -331
- package/chain/lib/flow/index.js +0 -7
- package/chain/lib/flow/step.js +0 -63
- package/chain/lib/memory/in-memory.js +0 -117
- package/chain/lib/memory/index.js +0 -36
- package/chain/lib/memory/lance-memory.js +0 -225
- package/chain/lib/memory/sqlite-memory.js +0 -309
- package/chain/simple-agent-step-example.js +0 -168
- package/chain/workflow-example-usage.js +0 -70
- package/chain/workflow-example.json +0 -59
- package/core/README.md +0 -485
- package/core/cli.js +0 -275
- package/core/config.yaml +0 -149
- package/core/docs/BASIC_USAGE.md +0 -62
- package/core/docs/CLI.md +0 -104
- package/core/docs/GET_STARTED.md +0 -129
- package/core/docs/GUARDRAILS_GUIDE.md +0 -734
- package/core/docs/README.md +0 -47
- package/core/docs/ROUTER_GUIDE.md +0 -199
- package/core/docs/SERVER_MODE.md +0 -358
- package/core/index.js +0 -115
- package/core/logger.js +0 -115
- package/core/providers/ollama.js +0 -128
- package/core/providers/openai.js +0 -112
- package/core/providers/openrouter.js +0 -206
- package/core/router.js +0 -252
- package/core/server.js +0 -203
package/core/router.js
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
const logger = require('./logger');
|
|
2
|
-
const { completion } = require('./completion');
|
|
3
|
-
|
|
4
|
-
function normalizeStrategy(strategy) {
|
|
5
|
-
const supportedStrategies = new Set(['default', 'random', 'sequential']);
|
|
6
|
-
return supportedStrategies.has(strategy) ? strategy : 'default';
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function resolveApiKey(apiKey) {
|
|
10
|
-
if (typeof apiKey === 'string' && apiKey.startsWith('os.environ/')) {
|
|
11
|
-
const envVar = apiKey.replace('os.environ/', '');
|
|
12
|
-
return process.env[envVar] || apiKey;
|
|
13
|
-
}
|
|
14
|
-
return apiKey;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeModelList(modelList) {
|
|
18
|
-
if (!Array.isArray(modelList) || modelList.length === 0) {
|
|
19
|
-
throw new Error('Router requires a non-empty model list');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return modelList.map((model, index) => {
|
|
23
|
-
if (!model || typeof model !== 'object') {
|
|
24
|
-
throw new Error(`Model at index ${index} must be an object`);
|
|
25
|
-
}
|
|
26
|
-
if (!model.model_name) {
|
|
27
|
-
throw new Error(`Model at index ${index} is missing model_name`);
|
|
28
|
-
}
|
|
29
|
-
if (!model.llm_params || typeof model.llm_params !== 'object') {
|
|
30
|
-
throw new Error(`Model '${model.model_name}' is missing llm_params`);
|
|
31
|
-
}
|
|
32
|
-
if (!model.llm_params.model) {
|
|
33
|
-
throw new Error(`Model '${model.model_name}' is missing llm_params.model`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
model_name: model.model_name,
|
|
38
|
-
llm_params: {
|
|
39
|
-
model: model.llm_params.model,
|
|
40
|
-
api_key: resolveApiKey(model.llm_params.api_key),
|
|
41
|
-
api_base: model.llm_params.api_base
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function buildModelsByName(modelList) {
|
|
48
|
-
return modelList.reduce((accumulator, model) => {
|
|
49
|
-
if (!accumulator[model.model_name]) {
|
|
50
|
-
accumulator[model.model_name] = [];
|
|
51
|
-
}
|
|
52
|
-
accumulator[model.model_name].push(model);
|
|
53
|
-
return accumulator;
|
|
54
|
-
}, {});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function router(modelList, strategy = 'default', options = {}) {
|
|
58
|
-
const normalizedModelList = normalizeModelList(modelList);
|
|
59
|
-
const normalizedStrategy = normalizeStrategy(strategy);
|
|
60
|
-
const allowUnsafeGuardrails = Boolean(options.allowUnsafeGuardrails) || process.env.LLMJS2_ALLOW_UNSAFE_GUARDRAILS === 'true';
|
|
61
|
-
const guardrails = [];
|
|
62
|
-
const modelsByName = buildModelsByName(normalizedModelList);
|
|
63
|
-
let sequentialIndex = 0;
|
|
64
|
-
|
|
65
|
-
function getRandomItem(items) {
|
|
66
|
-
const randomIndex = Math.floor(Math.random() * items.length);
|
|
67
|
-
return items[randomIndex];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function getNextSequentialModel() {
|
|
71
|
-
const selectedModel = normalizedModelList[sequentialIndex];
|
|
72
|
-
sequentialIndex = (sequentialIndex + 1) % normalizedModelList.length;
|
|
73
|
-
return selectedModel;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function ensureModelsAvailable() {
|
|
77
|
-
if (normalizedModelList.length === 0) {
|
|
78
|
-
throw new Error('Router has no configured models');
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function selectModel(modelName) {
|
|
83
|
-
ensureModelsAvailable();
|
|
84
|
-
|
|
85
|
-
if (!modelName) {
|
|
86
|
-
return autoSelectModel();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const availableModels = modelsByName[modelName];
|
|
90
|
-
if (!availableModels || availableModels.length === 0) {
|
|
91
|
-
throw new Error(`Model '${modelName}' not found in router configuration`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (availableModels.length === 1) {
|
|
95
|
-
return availableModels[0];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return getRandomItem(availableModels);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function autoSelectModel() {
|
|
102
|
-
ensureModelsAvailable();
|
|
103
|
-
|
|
104
|
-
switch (normalizedStrategy) {
|
|
105
|
-
case 'random':
|
|
106
|
-
return getRandomItem(normalizedModelList);
|
|
107
|
-
case 'sequential':
|
|
108
|
-
case 'default':
|
|
109
|
-
default:
|
|
110
|
-
return getNextSequentialModel();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function normalizeCompletionInput(input = {}) {
|
|
115
|
-
return {
|
|
116
|
-
modelName: input.model,
|
|
117
|
-
messages: input.messages || [],
|
|
118
|
-
stop: input.stop,
|
|
119
|
-
tools: input.tools,
|
|
120
|
-
toolChoice: input.tool_choice ?? input.toolChoice,
|
|
121
|
-
timeout: input.timeout,
|
|
122
|
-
apiKey: input.apiKey,
|
|
123
|
-
host: input.host || input.baseURL || input.baseUrl,
|
|
124
|
-
final: input.final ?? true
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function executeGuardrail(guardrail, processId, data) {
|
|
129
|
-
if (typeof guardrail.code === 'string') {
|
|
130
|
-
if (!allowUnsafeGuardrails) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`Guardrail '${guardrail.name}' uses string code, but unsafe execution is disabled. Set router option allowUnsafeGuardrails=true or LLMJS2_ALLOW_UNSAFE_GUARDRAILS=true to enable.`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const func = new Function('processId', 'data', `return (${guardrail.code})(processId, data)`);
|
|
137
|
-
return await func(processId, data);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (typeof guardrail.code === 'function') {
|
|
141
|
-
return await guardrail.code(processId, data);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
throw new Error(`Invalid guardrail code for '${guardrail.name}'`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function applyGuardrails(mode, processId, data) {
|
|
148
|
-
const modeLabel = mode === 'pre_call' ? 'Pre-call' : 'Post-call';
|
|
149
|
-
let currentValue = data;
|
|
150
|
-
|
|
151
|
-
for (const guardrail of guardrails) {
|
|
152
|
-
if (guardrail.mode !== mode) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const processed = await executeGuardrail(guardrail, processId, currentValue);
|
|
158
|
-
if (processed === null || processed === undefined) {
|
|
159
|
-
throw new Error(`Guardrail '${guardrail.name}' returned null/undefined`);
|
|
160
|
-
}
|
|
161
|
-
currentValue = processed;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
throw new Error(`${modeLabel} guardrail '${guardrail.name}' failed: ${error.message}`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return currentValue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function completionWithModel(modelConfig, input) {
|
|
171
|
-
return completion({
|
|
172
|
-
...input,
|
|
173
|
-
model: modelConfig.llm_params.model,
|
|
174
|
-
apiKey: input.apiKey || modelConfig.llm_params.api_key,
|
|
175
|
-
host: input.host || modelConfig.llm_params.api_base,
|
|
176
|
-
final: false
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function generateProcessId() {
|
|
181
|
-
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
setGuardrails(nextGuardrails) {
|
|
186
|
-
guardrails.length = 0;
|
|
187
|
-
if (Array.isArray(nextGuardrails)) {
|
|
188
|
-
guardrails.push(...nextGuardrails);
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
addGuardrail(guardrail) {
|
|
193
|
-
guardrails.push(guardrail);
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
getAvailableModels() {
|
|
197
|
-
return Object.keys(modelsByName);
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
getModelStats() {
|
|
201
|
-
const stats = {};
|
|
202
|
-
Object.keys(modelsByName).forEach((modelName) => {
|
|
203
|
-
stats[modelName] = modelsByName[modelName].length;
|
|
204
|
-
});
|
|
205
|
-
return stats;
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
selectModel,
|
|
209
|
-
|
|
210
|
-
autoSelectModel,
|
|
211
|
-
|
|
212
|
-
executeGuardrail,
|
|
213
|
-
|
|
214
|
-
async completion(input = {}) {
|
|
215
|
-
const processId = generateProcessId();
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
const normalizedInput = normalizeCompletionInput(input);
|
|
219
|
-
const selectedModel = selectModel(normalizedInput.modelName);
|
|
220
|
-
|
|
221
|
-
logger.debug('Selected model', {
|
|
222
|
-
processId,
|
|
223
|
-
model: selectedModel.llm_params.model,
|
|
224
|
-
modelName: selectedModel.model_name
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const { modelName, ...completionInput } = normalizedInput;
|
|
228
|
-
const preparedInput = {
|
|
229
|
-
model: selectedModel.llm_params.model,
|
|
230
|
-
...completionInput
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const processedInput = await applyGuardrails('pre_call', processId, preparedInput);
|
|
234
|
-
const result = await completionWithModel(selectedModel, processedInput);
|
|
235
|
-
const finalResult = await applyGuardrails('post_call', processId, result);
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
result: finalResult,
|
|
239
|
-
selectedModel: selectedModel.llm_params.model,
|
|
240
|
-
selectedModelName: selectedModel.model_name
|
|
241
|
-
};
|
|
242
|
-
} catch (error) {
|
|
243
|
-
logger.error('Router error', { processId, error: error.message });
|
|
244
|
-
throw error;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
module.exports = {
|
|
251
|
-
router
|
|
252
|
-
};
|
package/core/server.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
const http = require('http');
|
|
2
|
-
const url = require('url');
|
|
3
|
-
const logger = require('./logger');
|
|
4
|
-
|
|
5
|
-
class Server {
|
|
6
|
-
constructor(options = {}) {
|
|
7
|
-
this.port = options.port || process.env.PORT || 3000;
|
|
8
|
-
this.host = options.host || process.env.HOST || 'localhost';
|
|
9
|
-
this.router = options.router;
|
|
10
|
-
this.middlewares = [];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
use(middleware) {
|
|
14
|
-
if (middleware && typeof middleware.completion === 'function') {
|
|
15
|
-
// It's a router
|
|
16
|
-
this.router = middleware;
|
|
17
|
-
} else if (typeof middleware === 'function') {
|
|
18
|
-
// It's middleware
|
|
19
|
-
this.middlewares.push(middleware);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async handleRequest(req, res) {
|
|
24
|
-
try {
|
|
25
|
-
// Set CORS headers
|
|
26
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
27
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
28
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
29
|
-
|
|
30
|
-
if (req.method === 'OPTIONS') {
|
|
31
|
-
res.writeHead(200);
|
|
32
|
-
res.end();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const parsedUrl = url.parse(req.url, true);
|
|
37
|
-
const path = parsedUrl.pathname;
|
|
38
|
-
|
|
39
|
-
// Only handle chat completions endpoint
|
|
40
|
-
if (path !== '/v1/chat/completions' || req.method !== 'POST') {
|
|
41
|
-
this.sendError(res, 404, 'Not Found', 'Endpoint not found');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Parse request body
|
|
46
|
-
const body = await this.parseBody(req);
|
|
47
|
-
|
|
48
|
-
// Validate request
|
|
49
|
-
const validation = this.validateChatRequest(body);
|
|
50
|
-
if (!validation.valid) {
|
|
51
|
-
this.sendError(res, 400, 'Bad Request', validation.error);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
logger.debug('Incoming request', {
|
|
56
|
-
method: req.method,
|
|
57
|
-
url: req.url,
|
|
58
|
-
headers: req.headers,
|
|
59
|
-
bodyLength: JSON.stringify(body).length
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Process the completion request
|
|
63
|
-
const result = await this.processCompletion(body);
|
|
64
|
-
|
|
65
|
-
// Send successful response
|
|
66
|
-
this.sendSuccess(res, result);
|
|
67
|
-
|
|
68
|
-
} catch (error) {
|
|
69
|
-
logger.error('Server error', { error: error.message, url: req.url, method: req.method });
|
|
70
|
-
this.sendError(res, 500, 'Internal Server Error', error.message);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
parseBody(req) {
|
|
75
|
-
return new Promise((resolve, reject) => {
|
|
76
|
-
let body = '';
|
|
77
|
-
req.on('data', chunk => {
|
|
78
|
-
body += chunk.toString();
|
|
79
|
-
});
|
|
80
|
-
req.on('end', () => {
|
|
81
|
-
try {
|
|
82
|
-
resolve(JSON.parse(body));
|
|
83
|
-
} catch (error) {
|
|
84
|
-
reject(new Error('Invalid JSON in request body'));
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
req.on('error', reject);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
validateChatRequest(body) {
|
|
92
|
-
if (!body) {
|
|
93
|
-
return { valid: false, error: 'Request body is required' };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!body.messages || !Array.isArray(body.messages)) {
|
|
97
|
-
return { valid: false, error: 'messages array is required' };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (body.messages.length === 0) {
|
|
101
|
-
return { valid: false, error: 'messages array cannot be empty' };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
for (const message of body.messages) {
|
|
105
|
-
if (!message.role || !['system', 'user', 'assistant', 'tool'].includes(message.role)) {
|
|
106
|
-
return { valid: false, error: 'Each message must have a valid role (system, user, assistant, or tool)' };
|
|
107
|
-
}
|
|
108
|
-
if (typeof message.content !== 'string') {
|
|
109
|
-
return { valid: false, error: 'Each message must have string content' };
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { valid: true };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async processCompletion(body) {
|
|
117
|
-
if (!this.router) {
|
|
118
|
-
throw new Error('No router configured. Use app.use(router) to add a router.');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
logger.debug('Server processing completion request', {
|
|
122
|
-
model: body.model || 'auto-selected',
|
|
123
|
-
messageCount: body.messages?.length || 0
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const routerResponse = await this.router.completion(body);
|
|
127
|
-
const { result, selectedModel } = routerResponse;
|
|
128
|
-
|
|
129
|
-
const assistantMessage = {
|
|
130
|
-
role: 'assistant',
|
|
131
|
-
content: typeof result === 'string' ? result : result.content || '',
|
|
132
|
-
tool_calls: typeof result === 'object' ? result.tool_calls : undefined
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// Include original messages plus assistant response
|
|
136
|
-
const messages = [...(body.messages || []), assistantMessage];
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
id: `chatcmpl-${Date.now()}`,
|
|
140
|
-
object: 'chat.completion',
|
|
141
|
-
created: Math.floor(Date.now() / 1000),
|
|
142
|
-
model: body.model || selectedModel, // Use selected model if auto-selected
|
|
143
|
-
choices: [
|
|
144
|
-
{
|
|
145
|
-
index: 0,
|
|
146
|
-
message: assistantMessage,
|
|
147
|
-
finish_reason: typeof result === 'object' ? (result.finishReason || 'stop') : 'stop'
|
|
148
|
-
}
|
|
149
|
-
],
|
|
150
|
-
usage: typeof result === 'object' ? result.usage : undefined,
|
|
151
|
-
// Kept for backward compatibility with older llmjs2 clients
|
|
152
|
-
messages: messages
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
sendSuccess(res, data) {
|
|
157
|
-
const response = JSON.stringify(data);
|
|
158
|
-
res.writeHead(200, {
|
|
159
|
-
'Content-Type': 'application/json',
|
|
160
|
-
'Content-Length': Buffer.byteLength(response)
|
|
161
|
-
});
|
|
162
|
-
res.end(response);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
sendError(res, statusCode, type, message) {
|
|
166
|
-
const error = {
|
|
167
|
-
error: {
|
|
168
|
-
message: message,
|
|
169
|
-
type: type.toLowerCase().replace(/\s+/g, '_')
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
const response = JSON.stringify(error);
|
|
173
|
-
res.writeHead(statusCode, {
|
|
174
|
-
'Content-Type': 'application/json',
|
|
175
|
-
'Content-Length': Buffer.byteLength(response)
|
|
176
|
-
});
|
|
177
|
-
res.end(response);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
listen(port, host, callback) {
|
|
181
|
-
const actualPort = port || this.port;
|
|
182
|
-
const actualHost = host || this.host;
|
|
183
|
-
|
|
184
|
-
const server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
185
|
-
|
|
186
|
-
server.listen(actualPort, actualHost, () => {
|
|
187
|
-
logger.info('Server started', { host: actualHost, port: actualPort });
|
|
188
|
-
console.log(`🚀 llmjs2 server running on http://${actualHost}:${actualPort}`);
|
|
189
|
-
if (callback) callback();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
return server;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function createApp(options = {}) {
|
|
197
|
-
return new Server(options);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
module.exports = {
|
|
201
|
-
Server,
|
|
202
|
-
app: createApp()
|
|
203
|
-
};
|