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.
Files changed (45) hide show
  1. package/README.md +116 -18
  2. package/dist/index.d.mts +146 -0
  3. package/dist/index.d.ts +146 -0
  4. package/dist/index.js +1242 -0
  5. package/dist/index.mjs +1211 -0
  6. package/package.json +32 -58
  7. package/chain/AGENT_STEP_README.md +0 -102
  8. package/chain/README.md +0 -257
  9. package/chain/WORKFLOW_README.md +0 -85
  10. package/chain/agent-step-example.js +0 -232
  11. package/chain/docs/AGENT.md +0 -126
  12. package/chain/docs/GRAPH.md +0 -490
  13. package/chain/examples.js +0 -314
  14. package/chain/index.js +0 -31
  15. package/chain/lib/agent.js +0 -338
  16. package/chain/lib/flow/agent-step.js +0 -119
  17. package/chain/lib/flow/edge.js +0 -24
  18. package/chain/lib/flow/flow.js +0 -76
  19. package/chain/lib/flow/graph.js +0 -331
  20. package/chain/lib/flow/index.js +0 -7
  21. package/chain/lib/flow/step.js +0 -63
  22. package/chain/lib/memory/in-memory.js +0 -117
  23. package/chain/lib/memory/index.js +0 -36
  24. package/chain/lib/memory/lance-memory.js +0 -225
  25. package/chain/lib/memory/sqlite-memory.js +0 -309
  26. package/chain/simple-agent-step-example.js +0 -168
  27. package/chain/workflow-example-usage.js +0 -70
  28. package/chain/workflow-example.json +0 -59
  29. package/core/README.md +0 -485
  30. package/core/cli.js +0 -275
  31. package/core/config.yaml +0 -149
  32. package/core/docs/BASIC_USAGE.md +0 -62
  33. package/core/docs/CLI.md +0 -104
  34. package/core/docs/GET_STARTED.md +0 -129
  35. package/core/docs/GUARDRAILS_GUIDE.md +0 -734
  36. package/core/docs/README.md +0 -47
  37. package/core/docs/ROUTER_GUIDE.md +0 -199
  38. package/core/docs/SERVER_MODE.md +0 -358
  39. package/core/index.js +0 -115
  40. package/core/logger.js +0 -115
  41. package/core/providers/ollama.js +0 -128
  42. package/core/providers/openai.js +0 -112
  43. package/core/providers/openrouter.js +0 -206
  44. package/core/router.js +0 -252
  45. 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
- };