modelmix 1.2.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -50
- package/demo/custom.mjs +28 -0
- package/demo/demo.mjs +27 -16
- package/demo/node_modules/.package-lock.json +29 -0
- package/demo/node_modules/debug/LICENSE +20 -0
- package/demo/node_modules/debug/README.md +481 -0
- package/demo/node_modules/debug/node_modules/ms/index.js +162 -0
- package/demo/node_modules/debug/node_modules/ms/license.md +21 -0
- package/demo/node_modules/debug/node_modules/ms/package.json +37 -0
- package/demo/node_modules/debug/node_modules/ms/readme.md +60 -0
- package/demo/node_modules/debug/package.json +59 -0
- package/demo/node_modules/debug/src/browser.js +269 -0
- package/demo/node_modules/debug/src/common.js +274 -0
- package/demo/node_modules/debug/src/index.js +10 -0
- package/demo/node_modules/debug/src/node.js +263 -0
- package/demo/node_modules/lemonlog/README.md +133 -0
- package/demo/node_modules/lemonlog/demo/demo.js +31 -0
- package/demo/node_modules/lemonlog/index.js +94 -0
- package/demo/node_modules/lemonlog/package.json +31 -0
- package/demo/package-lock.json +30 -0
- package/demo/package.json +1 -0
- package/demo/stream.mjs +87 -0
- package/index.js +189 -70
- package/package.json +6 -2
package/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
-
const fs = require('fs')
|
|
2
|
+
const fs = require('fs');
|
|
3
3
|
const mime = require('mime-types');
|
|
4
4
|
|
|
5
5
|
class ModelMix {
|
|
6
6
|
constructor(args = { options: {}, config: {} }) {
|
|
7
7
|
this.models = {};
|
|
8
8
|
this.defaultOptions = {
|
|
9
|
-
model: 'gpt-4o',
|
|
10
9
|
max_tokens: 2000,
|
|
11
10
|
temperature: 1,
|
|
12
11
|
top_p: 1,
|
|
@@ -22,7 +21,7 @@ class ModelMix {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
attach(modelInstance) {
|
|
25
|
-
const key = modelInstance.
|
|
24
|
+
const key = modelInstance.config.prefix.join("_");
|
|
26
25
|
this.models[key] = modelInstance;
|
|
27
26
|
modelInstance.queue = [];
|
|
28
27
|
modelInstance.active_requests = 0;
|
|
@@ -37,7 +36,12 @@ class ModelMix {
|
|
|
37
36
|
throw new Error(`Model with prefix matching ${modelKey} is not attached.`);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
const options = {
|
|
39
|
+
const options = {
|
|
40
|
+
...this.defaultOptions,
|
|
41
|
+
...modelEntry.options,
|
|
42
|
+
...overOptions,
|
|
43
|
+
model: modelKey
|
|
44
|
+
};
|
|
41
45
|
const config = { ...this.config, ...modelEntry.config };
|
|
42
46
|
|
|
43
47
|
return new MessageHandler(this, modelEntry, options, config);
|
|
@@ -91,9 +95,9 @@ class MessageHandler {
|
|
|
91
95
|
return this;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
addImage(filePath, config = { role: "user" }) {
|
|
95
99
|
try {
|
|
96
|
-
const imageBuffer =
|
|
100
|
+
const imageBuffer = fs.readFileSync(filePath);
|
|
97
101
|
const mimeType = mime.lookup(filePath);
|
|
98
102
|
|
|
99
103
|
if (!mimeType || !mimeType.startsWith('image/')) {
|
|
@@ -125,13 +129,20 @@ class MessageHandler {
|
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
async message() {
|
|
132
|
+
this.options.stream = false;
|
|
128
133
|
const response = await this.execute();
|
|
129
134
|
return response.message;
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
async raw() {
|
|
133
|
-
|
|
134
|
-
return
|
|
138
|
+
this.options.stream = false;
|
|
139
|
+
return this.execute();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async stream(callback) {
|
|
143
|
+
this.options.stream = true;
|
|
144
|
+
this.modelEntry.streamCallback = callback;
|
|
145
|
+
return this.execute();
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
groupByRoles(messages) {
|
|
@@ -147,10 +158,9 @@ class MessageHandler {
|
|
|
147
158
|
}
|
|
148
159
|
|
|
149
160
|
async execute() {
|
|
150
|
-
|
|
151
161
|
this.messages = this.groupByRoles(this.messages);
|
|
152
162
|
|
|
153
|
-
if (this.messages.length === 0) {
|
|
163
|
+
if (this.messages.length === 0) {
|
|
154
164
|
throw new Error("No user messages have been added. Use addMessage(prompt) to add a message.");
|
|
155
165
|
}
|
|
156
166
|
|
|
@@ -171,30 +181,112 @@ class MessageHandler {
|
|
|
171
181
|
}
|
|
172
182
|
}
|
|
173
183
|
|
|
174
|
-
class
|
|
175
|
-
constructor(
|
|
176
|
-
this.
|
|
184
|
+
class MixCustom {
|
|
185
|
+
constructor(args = { config: {}, options: {}, headers: {} }) {
|
|
186
|
+
this.config = this.getDefaultConfig(args.config);
|
|
187
|
+
this.options = this.getDefaultOptions(args.options);
|
|
188
|
+
this.headers = this.getDefaultHeaders(args.headers);
|
|
189
|
+
this.streamCallback = null; // Definimos streamCallback aquí
|
|
190
|
+
}
|
|
177
191
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
192
|
+
getDefaultOptions(customOptions) {
|
|
193
|
+
return {
|
|
194
|
+
...customOptions
|
|
195
|
+
};
|
|
196
|
+
}
|
|
184
197
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
198
|
+
getDefaultConfig(customConfig) {
|
|
199
|
+
return {
|
|
200
|
+
url: '',
|
|
201
|
+
apiKey: '',
|
|
202
|
+
prefix: [],
|
|
203
|
+
...customConfig
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getDefaultHeaders(customHeaders) {
|
|
208
|
+
return {
|
|
209
|
+
'accept': 'application/json',
|
|
210
|
+
'content-type': 'application/json',
|
|
211
|
+
'authorization': `Bearer ${this.config.apiKey}`,
|
|
212
|
+
...customHeaders
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async create(args = { config: {}, options: {} }) {
|
|
217
|
+
|
|
218
|
+
if (args.options.stream) {
|
|
219
|
+
return this.processStream(await axios.post(this.config.url, args.options, {
|
|
220
|
+
headers: this.headers,
|
|
221
|
+
responseType: 'stream'
|
|
222
|
+
}));
|
|
223
|
+
} else {
|
|
224
|
+
return this.processResponse(await axios.post(this.config.url, args.options, {
|
|
225
|
+
headers: this.headers
|
|
226
|
+
}));
|
|
189
227
|
}
|
|
190
228
|
}
|
|
191
229
|
|
|
192
|
-
|
|
230
|
+
processStream(response) {
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
let raw = [];
|
|
233
|
+
let message = '';
|
|
234
|
+
let buffer = '';
|
|
235
|
+
|
|
236
|
+
response.data.on('data', chunk => {
|
|
237
|
+
buffer += chunk.toString();
|
|
238
|
+
|
|
239
|
+
let boundary;
|
|
240
|
+
while ((boundary = buffer.indexOf('\n')) !== -1) {
|
|
241
|
+
const dataStr = buffer.slice(0, boundary).trim();
|
|
242
|
+
buffer = buffer.slice(boundary + 1);
|
|
243
|
+
|
|
244
|
+
const firstBraceIndex = dataStr.indexOf('{');
|
|
245
|
+
if (dataStr === '[DONE]' || firstBraceIndex === -1) continue;
|
|
246
|
+
|
|
247
|
+
const jsonStr = dataStr.slice(firstBraceIndex);
|
|
248
|
+
try {
|
|
249
|
+
const data = JSON.parse(jsonStr);
|
|
250
|
+
if (this.streamCallback) {
|
|
251
|
+
const delta = this.extractDelta(data);
|
|
252
|
+
message += delta;
|
|
253
|
+
this.streamCallback({ response: data, message, delta });
|
|
254
|
+
raw.push(data);
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error('Error parsing JSON:', error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
response.data.on('end', () => resolve({ response: raw, message: message.trim() }));
|
|
263
|
+
response.data.on('error', reject);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
extractDelta(data) {
|
|
268
|
+
if (data.choices && data.choices[0].delta.content) return data.choices[0].delta.content;
|
|
269
|
+
return '';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
processResponse(response) {
|
|
273
|
+
return { response: response.data, message: response.data.choices[0].message.content };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
193
276
|
|
|
277
|
+
class MixOpenAI extends MixCustom {
|
|
278
|
+
getDefaultConfig(customConfig) {
|
|
279
|
+
return {
|
|
280
|
+
...super.getDefaultConfig(customConfig),
|
|
281
|
+
url: 'https://api.openai.com/v1/chat/completions',
|
|
282
|
+
prefix: ['gpt']
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
create(args = { config: {}, options: {} }) {
|
|
194
287
|
args.options.messages = [{ role: 'system', content: args.config.system }, ...args.options.messages || []];
|
|
195
288
|
args.options.messages = this.convertMessages(args.options.messages);
|
|
196
|
-
|
|
197
|
-
return { response, message: response.choices[0].message.content };
|
|
289
|
+
return super.create(args);
|
|
198
290
|
}
|
|
199
291
|
|
|
200
292
|
convertMessages(messages) {
|
|
@@ -218,65 +310,92 @@ class OpenAIModel {
|
|
|
218
310
|
}
|
|
219
311
|
}
|
|
220
312
|
|
|
221
|
-
class
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
this.config = {
|
|
230
|
-
prefix: ["claude"],
|
|
231
|
-
max_request: 1,
|
|
232
|
-
...args.config || {}
|
|
233
|
-
}
|
|
313
|
+
class MixAnthropic extends MixCustom {
|
|
314
|
+
getDefaultConfig(customConfig) {
|
|
315
|
+
return {
|
|
316
|
+
...super.getDefaultConfig(customConfig),
|
|
317
|
+
url: 'https://api.anthropic.com/v1/messages',
|
|
318
|
+
prefix: ['claude']
|
|
319
|
+
};
|
|
234
320
|
}
|
|
235
321
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
322
|
+
getDefaultHeaders() {
|
|
323
|
+
return {
|
|
324
|
+
...super.getDefaultHeaders(),
|
|
325
|
+
'x-api-key': this.config.apiKey,
|
|
326
|
+
'anthropic-version': '2023-06-01'
|
|
327
|
+
};
|
|
328
|
+
}
|
|
239
329
|
|
|
240
|
-
|
|
241
|
-
|
|
330
|
+
extractDelta(data) {
|
|
331
|
+
if (data.delta && data.delta.text) return data.delta.text;
|
|
332
|
+
return '';
|
|
333
|
+
}
|
|
242
334
|
|
|
243
|
-
|
|
335
|
+
processResponse(response) {
|
|
336
|
+
return { response: response.data, message: response.data.content[0].text };
|
|
244
337
|
}
|
|
245
338
|
}
|
|
246
339
|
|
|
247
|
-
class
|
|
248
|
-
|
|
249
|
-
|
|
340
|
+
class MixPerplexity extends MixCustom {
|
|
341
|
+
getDefaultConfig(customConfig) {
|
|
342
|
+
return {
|
|
343
|
+
...super.getDefaultConfig(customConfig),
|
|
250
344
|
url: 'https://api.perplexity.ai/chat/completions',
|
|
251
|
-
|
|
252
|
-
prefix: ["pplx", "llama", "mixtral"],
|
|
253
|
-
max_request: 1,
|
|
254
|
-
...args.config
|
|
345
|
+
prefix: ['pplx', 'llama', 'mixtral']
|
|
255
346
|
};
|
|
347
|
+
}
|
|
256
348
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
349
|
+
create(args = { config: {}, options: {} }) {
|
|
350
|
+
args.options.messages = [{ role: 'system', content: args.config.system }, ...args.options.messages || []];
|
|
351
|
+
return super.create(args);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
class MixOllama extends MixCustom {
|
|
356
|
+
|
|
357
|
+
getDefaultOptions(customOptions) {
|
|
358
|
+
return {
|
|
359
|
+
options: customOptions,
|
|
264
360
|
};
|
|
265
361
|
}
|
|
266
362
|
|
|
267
|
-
|
|
363
|
+
extractDelta(data) {
|
|
364
|
+
if (data.message && data.message.content) return data.message.content;
|
|
365
|
+
return '';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
create(args = { config: {}, options: {} }) {
|
|
369
|
+
|
|
370
|
+
args.options.messages = this.convertMessages(args.options.messages);
|
|
268
371
|
args.options.messages = [{ role: 'system', content: args.config.system }, ...args.options.messages || []];
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
headers: {
|
|
272
|
-
'accept': 'application/json',
|
|
273
|
-
'authorization': `Bearer ${this.config.bearer}`,
|
|
274
|
-
'content-type': 'application/json'
|
|
275
|
-
}
|
|
276
|
-
});
|
|
372
|
+
return super.create(args);
|
|
373
|
+
}
|
|
277
374
|
|
|
278
|
-
|
|
375
|
+
processResponse(response) {
|
|
376
|
+
return { response: response.data, message: response.data.message.content.trim() };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
convertMessages(messages) {
|
|
380
|
+
return messages.map(entry => {
|
|
381
|
+
let content = '';
|
|
382
|
+
let images = [];
|
|
383
|
+
|
|
384
|
+
entry.content.forEach(item => {
|
|
385
|
+
if (item.type === 'text') {
|
|
386
|
+
content += item.text + ' ';
|
|
387
|
+
} else if (item.type === 'image') {
|
|
388
|
+
images.push(item.source.data);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
role: entry.role,
|
|
394
|
+
content: content.trim(),
|
|
395
|
+
images: images
|
|
396
|
+
};
|
|
397
|
+
});
|
|
279
398
|
}
|
|
280
399
|
}
|
|
281
400
|
|
|
282
|
-
module.exports = {
|
|
401
|
+
module.exports = { MixCustom, ModelMix, MixAnthropic, MixOpenAI, MixPerplexity, MixOllama };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelmix",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "🧬 ModelMix - Unified API for Diverse AI Language Models.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
"llama",
|
|
21
21
|
"mixtral",
|
|
22
22
|
"nlp",
|
|
23
|
-
"chat"
|
|
23
|
+
"chat",
|
|
24
|
+
"multimodal",
|
|
25
|
+
"omni",
|
|
26
|
+
"4o",
|
|
27
|
+
"ollama"
|
|
24
28
|
],
|
|
25
29
|
"author": "Martin Clasen",
|
|
26
30
|
"license": "MIT",
|