modelmix 3.2.2 → 3.3.2
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/MODELS.md +249 -0
- package/README.md +89 -73
- package/demo/custom.mjs +41 -3
- package/demo/demo.mjs +20 -27
- package/demo/fallback.mjs +3 -14
- package/demo/grok.mjs +7 -4
- package/demo/groq.mjs +2 -2
- package/demo/lmstudio.mjs +1 -1
- package/demo/parallel.mjs +3 -3
- package/demo/short.mjs +16 -10
- package/demo/stream.mjs +4 -65
- package/demo/together.mjs +4 -17
- package/index.js +202 -426
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -6,159 +6,11 @@ const Bottleneck = require('bottleneck');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const generateJsonSchema = require('./schema');
|
|
8
8
|
|
|
9
|
-
class ModelMixBuilder {
|
|
10
|
-
constructor(args = {}) {
|
|
11
|
-
this.models = []; // Array of { key: string, providerClass: class, options: {}, config: {} }
|
|
12
|
-
this.mix = new ModelMix(args);
|
|
13
|
-
this.handler = null;
|
|
14
|
-
this._messageHandlerMethods = [ // Methods to delegate after handler creation
|
|
15
|
-
'new', 'addText', 'addTextFromFile', 'setSystem', 'setSystemFromFile',
|
|
16
|
-
'addImage', 'addImageFromUrl', 'message', 'json', 'block', 'raw',
|
|
17
|
-
'stream', 'replace', 'replaceKeyFromFile'
|
|
18
|
-
];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
addModel(key, providerClass, { options = {}, config = {} } = {}) {
|
|
22
|
-
if (this.handler) {
|
|
23
|
-
throw new Error("Cannot add models after message generation has started.");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Attach provider if not already attached
|
|
27
|
-
const providerInstance = new providerClass();
|
|
28
|
-
const mainPrefix = providerInstance.config.prefix[0];
|
|
29
|
-
if (!Object.values(this.mix.models).some(p => p.config.prefix.includes(mainPrefix))) {
|
|
30
|
-
this.mix.attach(providerInstance);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!key) {
|
|
34
|
-
throw new Error(`Model key is required when adding a model via ${providerClass.name}.`);
|
|
35
|
-
}
|
|
36
|
-
this.models.push({ key, providerClass, options, config });
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
_getHandler() {
|
|
41
|
-
if (!this.handler) {
|
|
42
|
-
if (!this.mix || this.models.length === 0) {
|
|
43
|
-
throw new Error("No models specified. Use methods like .gpt(), .sonnet() first.");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Pass all model definitions. The create method will handle it appropriately
|
|
47
|
-
this.handler = this.mix.createByDef(this.models);
|
|
48
|
-
|
|
49
|
-
// Delegate chainable methods to the handler
|
|
50
|
-
this._messageHandlerMethods.forEach(methodName => {
|
|
51
|
-
if (typeof this.handler[methodName] === 'function') {
|
|
52
|
-
this[methodName] = (...args) => {
|
|
53
|
-
const result = this.handler[methodName](...args);
|
|
54
|
-
// Return the handler instance for chainable methods, otherwise the result
|
|
55
|
-
return result === this.handler ? this : result;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
// Special handling for async methods that return results
|
|
60
|
-
['message', 'json', 'block', 'raw', 'stream'].forEach(asyncMethodName => {
|
|
61
|
-
if (typeof this.handler[asyncMethodName] === 'function') {
|
|
62
|
-
this[asyncMethodName] = async (...args) => {
|
|
63
|
-
return await this.handler[asyncMethodName](...args);
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return this.handler;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// --- Instance methods for adding models (primary or fallback) ---
|
|
72
|
-
// These will be mirrored by static methods on ModelMix
|
|
73
|
-
gpt41({ model = 'gpt-4.1', options = {}, config = {} } = {}) {
|
|
74
|
-
return this.addModel(model, MixOpenAI, { options, config });
|
|
75
|
-
}
|
|
76
|
-
gpt41mini({ model = 'gpt-4.1-mini', options = {}, config = {} } = {}) {
|
|
77
|
-
return this.addModel(model, MixOpenAI, { options, config });
|
|
78
|
-
}
|
|
79
|
-
gpt41nano({ model = 'gpt-4.1-nano', options = {}, config = {} } = {}) {
|
|
80
|
-
return this.addModel(model, MixOpenAI, { options, config });
|
|
81
|
-
}
|
|
82
|
-
gpt4o({ model = 'gpt-4o', options = {}, config = {} } = {}) {
|
|
83
|
-
return this.addModel(model, MixOpenAI, { options, config });
|
|
84
|
-
}
|
|
85
|
-
o4mini({ model = 'o4-mini', options = {}, config = {} } = {}) {
|
|
86
|
-
return this.addModel(model, MixOpenAI, { options, config });
|
|
87
|
-
}
|
|
88
|
-
o3({ model = 'o3', options = {}, config = {} } = {}) {
|
|
89
|
-
return this.addModel(model, MixOpenAI, { options, config });
|
|
90
|
-
}
|
|
91
|
-
sonnet37({ model = 'claude-3-7-sonnet-20250219', options = {}, config = {} } = {}) {
|
|
92
|
-
return this.addModel(model, MixAnthropic, { options, config });
|
|
93
|
-
}
|
|
94
|
-
sonnet37think({ model = 'claude-3-7-sonnet-20250219', options = {
|
|
95
|
-
thinking: {
|
|
96
|
-
"type": "enabled",
|
|
97
|
-
"budget_tokens": 1024
|
|
98
|
-
},
|
|
99
|
-
temperature: 1
|
|
100
|
-
}, config = {} } = {}) {
|
|
101
|
-
return this.addModel(model, MixAnthropic, { options, config });
|
|
102
|
-
}
|
|
103
|
-
sonnet35({ model = 'claude-3-5-sonnet-20241022', options = {}, config = {} } = {}) {
|
|
104
|
-
return this.addModel(model, MixAnthropic, { options, config });
|
|
105
|
-
}
|
|
106
|
-
haiku35({ model = 'claude-3-5-haiku-20241022', options = {}, config = {} } = {}) {
|
|
107
|
-
return this.addModel(model, MixAnthropic, { options, config });
|
|
108
|
-
}
|
|
109
|
-
gemini25flash({ model = 'gemini-2.5-flash-preview-04-17', options = {}, config = {} } = {}) {
|
|
110
|
-
return this.addModel(model, MixGoogle, { options, config });
|
|
111
|
-
}
|
|
112
|
-
gemini25proExp({ model = 'gemini-2.5-pro-exp-03-25', options = {}, config = {} } = {}) {
|
|
113
|
-
return this.addModel(model, MixGoogle, { options, config });
|
|
114
|
-
}
|
|
115
|
-
gemini25pro({ model = 'gemini-2.5-pro-preview-05-06', options = {}, config = {} } = {}) {
|
|
116
|
-
return this.addModel(model, MixGoogle, { options, config });
|
|
117
|
-
}
|
|
118
|
-
sonar({ model = 'sonar-pro', options = {}, config = {} } = {}) {
|
|
119
|
-
return this.addModel(model, MixPerplexity, { options, config });
|
|
120
|
-
}
|
|
121
|
-
qwen3({ model = 'Qwen/Qwen3-235B-A22B-fp8-tput', options = {}, config = {} } = {}) {
|
|
122
|
-
return this.addModel(model, MixTogether, { options, config });
|
|
123
|
-
}
|
|
124
|
-
grok2({ model = 'grok-2-latest', options = {}, config = {} } = {}) {
|
|
125
|
-
return this.addModel(model, MixGrok, { options, config });
|
|
126
|
-
}
|
|
127
|
-
grok3({ model = 'grok-3-beta', options = {}, config = {} } = {}) {
|
|
128
|
-
return this.addModel(model, MixGrok, { options, config });
|
|
129
|
-
}
|
|
130
|
-
grok3mini({ model = 'grok-3-mini-beta', options = {}, config = {} } = {}) {
|
|
131
|
-
return this.addModel(model, MixGrok, { options, config });
|
|
132
|
-
}
|
|
133
|
-
scout({ model = 'llama-4-scout-17b-16e-instruct', options = {}, config = {} } = {}) {
|
|
134
|
-
return this.addModel(model, MixCerebras, { options, config });
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// --- Methods delegated to MessageHandler after creation ---
|
|
138
|
-
// Define stubs that will call _getHandler first
|
|
139
|
-
|
|
140
|
-
new() { this._getHandler(); return this.new(...arguments); }
|
|
141
|
-
addText() { this._getHandler(); return this.addText(...arguments); }
|
|
142
|
-
addTextFromFile() { this._getHandler(); return this.addTextFromFile(...arguments); }
|
|
143
|
-
setSystem() { this._getHandler(); return this.setSystem(...arguments); }
|
|
144
|
-
setSystemFromFile() { this._getHandler(); return this.setSystemFromFile(...arguments); }
|
|
145
|
-
addImage() { this._getHandler(); return this.addImage(...arguments); }
|
|
146
|
-
addImageFromUrl() { this._getHandler(); return this.addImageFromUrl(...arguments); }
|
|
147
|
-
replace() { this._getHandler(); return this.replace(...arguments); }
|
|
148
|
-
replaceKeyFromFile() { this._getHandler(); return this.replaceKeyFromFile(...arguments); }
|
|
149
|
-
|
|
150
|
-
// Async methods need await
|
|
151
|
-
async message() { this._getHandler(); return await this.message(...arguments); }
|
|
152
|
-
async json() { this._getHandler(); return await this.json(...arguments); }
|
|
153
|
-
async block() { this._getHandler(); return await this.block(...arguments); }
|
|
154
|
-
async raw() { this._getHandler(); return await this.raw(...arguments); }
|
|
155
|
-
async stream() { this._getHandler(); return await this.stream(...arguments); }
|
|
156
|
-
}
|
|
157
|
-
|
|
158
9
|
class ModelMix {
|
|
159
10
|
constructor({ options = {}, config = {} } = {}) {
|
|
160
|
-
this.models =
|
|
161
|
-
this.
|
|
11
|
+
this.models = [];
|
|
12
|
+
this.messages = [];
|
|
13
|
+
this.options = {
|
|
162
14
|
max_tokens: 5000,
|
|
163
15
|
temperature: 1, // 1 --> More creative, 0 --> More deterministic.
|
|
164
16
|
top_p: 1, // 100% --> The model considers all possible tokens.
|
|
@@ -181,6 +33,7 @@ class ModelMix {
|
|
|
181
33
|
}
|
|
182
34
|
|
|
183
35
|
this.limiter = new Bottleneck(this.config.bottleneck);
|
|
36
|
+
|
|
184
37
|
}
|
|
185
38
|
|
|
186
39
|
replace(keyValues) {
|
|
@@ -188,165 +41,131 @@ class ModelMix {
|
|
|
188
41
|
return this;
|
|
189
42
|
}
|
|
190
43
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const key = modelInstance.config.prefix.join("_");
|
|
194
|
-
this.models[key] = modelInstance;
|
|
195
|
-
}
|
|
196
|
-
return this;
|
|
44
|
+
static new({ options = {}, config = {} } = {}) {
|
|
45
|
+
return new ModelMix({ options, config });
|
|
197
46
|
}
|
|
198
47
|
|
|
199
|
-
|
|
200
|
-
return new
|
|
48
|
+
new() {
|
|
49
|
+
return new ModelMix({ options: this.options, config: this.config });
|
|
201
50
|
}
|
|
202
51
|
|
|
203
|
-
|
|
52
|
+
attach(key, provider) {
|
|
204
53
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const allModelsInfo = modelDefinitions;
|
|
208
|
-
const modelKeys = allModelsInfo.map(m => m.key);
|
|
209
|
-
|
|
210
|
-
if (modelKeys.length === 0) {
|
|
211
|
-
throw new Error('No model keys provided in modelDefinitions.');
|
|
54
|
+
if (this.models.some(model => model.key === key)) {
|
|
55
|
+
return this;
|
|
212
56
|
}
|
|
213
57
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return !Object.values(this.models).some(entry =>
|
|
217
|
-
entry.config.prefix.some(p => modelKey.startsWith(p))
|
|
218
|
-
);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
if (unavailableModels.length > 0) {
|
|
222
|
-
throw new Error(`The following models are not available: ${unavailableModels.join(', ')}`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Una vez verificado que todos están disponibles, obtener el primer modelo (primary)
|
|
226
|
-
const primaryModelInfo = allModelsInfo[0];
|
|
227
|
-
const primaryModelKey = primaryModelInfo.key;
|
|
228
|
-
const primaryModelEntry = Object.values(this.models).find(entry =>
|
|
229
|
-
entry.config.prefix.some(p => primaryModelKey.startsWith(p))
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
if (!primaryModelEntry) { // Should be caught by unavailableModels, but good for robustness
|
|
233
|
-
throw new Error(`Primary model provider for key ${primaryModelKey} not found or attached.`);
|
|
58
|
+
if (this.messages.length > 0) {
|
|
59
|
+
throw new Error("Cannot add models after message generation has started.");
|
|
234
60
|
}
|
|
235
61
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const optionsHandler = {
|
|
239
|
-
...this.defaultOptions, // ModelMix global defaults
|
|
240
|
-
...(primaryModelEntry.options || {}), // Primary provider class defaults
|
|
241
|
-
...(primaryModelInfo.options || {}), // Options from addModel for primary
|
|
242
|
-
...explicitOverallOptions, // Explicit options to .create() if any
|
|
243
|
-
model: primaryModelKey // Ensure primary model key is set
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const configHandler = {
|
|
247
|
-
...this.config, // ModelMix global config
|
|
248
|
-
...(primaryModelEntry.config || {}), // Primary provider class config
|
|
249
|
-
...(primaryModelInfo.config || {}), // Config from addModel for primary
|
|
250
|
-
...explicitOverallConfig // Explicit config to .create()
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
// Pass the entire allModelsInfo array for fallback/iteration
|
|
254
|
-
return new MessageHandler(this, primaryModelEntry, optionsHandler, configHandler, allModelsInfo);
|
|
62
|
+
this.models.push({ key, provider });
|
|
63
|
+
return this;
|
|
255
64
|
}
|
|
256
65
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (!modelKeys || (Array.isArray(modelKeys) && modelKeys.length === 0)) {
|
|
261
|
-
return new ModelMixBuilder({ config: { ...this.config, ...config }, options: { ...this.defaultOptions, ...options } });
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// If modelKeys is a string, convert it to an array for backward compatibility
|
|
265
|
-
const modelArray = Array.isArray(modelKeys) ? modelKeys : [modelKeys];
|
|
266
|
-
|
|
267
|
-
if (modelArray.length === 0) {
|
|
268
|
-
throw new Error('No model keys provided');
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Create model definitions based on string keys
|
|
272
|
-
const modelDefinitions = modelArray.map(key => {
|
|
273
|
-
// Find the provider for this model key
|
|
274
|
-
const providerEntry = Object.values(this.models).find(entry =>
|
|
275
|
-
entry.config.prefix.some(p => key.startsWith(p))
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
if (!providerEntry) {
|
|
279
|
-
throw new Error(`Model provider not found for key: ${key}`);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Return a synthesized model definition with just the key and options/config from the create call
|
|
283
|
-
return {
|
|
284
|
-
key,
|
|
285
|
-
providerClass: null, // Not needed for our purpose
|
|
286
|
-
options, // Use the options from create call for all models
|
|
287
|
-
config // Use the config from create call for all models
|
|
288
|
-
};
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// Pass to the new implementation
|
|
292
|
-
return this.createByDef(modelDefinitions, { config, options });
|
|
66
|
+
// --- Model addition methods ---
|
|
67
|
+
gpt41({ options = {}, config = {} } = {}) {
|
|
68
|
+
return this.attach('gpt-4.1', new MixOpenAI({ options, config }));
|
|
293
69
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
70
|
+
gpt41mini({ options = {}, config = {} } = {}) {
|
|
71
|
+
return this.attach('gpt-4.1-mini', new MixOpenAI({ options, config }));
|
|
72
|
+
}
|
|
73
|
+
gpt41nano({ options = {}, config = {} } = {}) {
|
|
74
|
+
return this.attach('gpt-4.1-nano', new MixOpenAI({ options, config }));
|
|
75
|
+
}
|
|
76
|
+
gpt4o({ options = {}, config = {} } = {}) {
|
|
77
|
+
return this.attach('gpt-4o', new MixOpenAI({ options, config }));
|
|
78
|
+
}
|
|
79
|
+
o4mini({ options = {}, config = {} } = {}) {
|
|
80
|
+
return this.attach('o4-mini', new MixOpenAI({ options, config }));
|
|
81
|
+
}
|
|
82
|
+
o3({ options = {}, config = {} } = {}) {
|
|
83
|
+
return this.attach('o3', new MixOpenAI({ options, config }));
|
|
84
|
+
}
|
|
85
|
+
gpt45({ options = {}, config = {} } = {}) {
|
|
86
|
+
return this.attach('gpt-4.5-preview', new MixOpenAI({ options, config }));
|
|
87
|
+
}
|
|
88
|
+
sonnet37({ options = {}, config = {} } = {}) {
|
|
89
|
+
return this.attach('claude-3-7-sonnet-20250219', new MixAnthropic({ options, config }));
|
|
90
|
+
}
|
|
91
|
+
sonnet37think({ options = {
|
|
92
|
+
thinking: {
|
|
93
|
+
"type": "enabled",
|
|
94
|
+
"budget_tokens": 1024
|
|
95
|
+
},
|
|
96
|
+
temperature: 1
|
|
97
|
+
}, config = {} } = {}) {
|
|
98
|
+
return this.attach('claude-3-7-sonnet-20250219', new MixAnthropic({ options, config }));
|
|
99
|
+
}
|
|
100
|
+
sonnet35({ options = {}, config = {} } = {}) {
|
|
101
|
+
return this.attach('claude-3-5-sonnet-20241022', new MixAnthropic({ options, config }));
|
|
102
|
+
}
|
|
103
|
+
haiku35({ options = {}, config = {} } = {}) {
|
|
104
|
+
return this.attach('claude-3-5-haiku-20241022', new MixAnthropic({ options, config }));
|
|
105
|
+
}
|
|
106
|
+
gemini25flash({ options = {}, config = {} } = {}) {
|
|
107
|
+
return this.attach('gemini-2.5-flash-preview-04-17', new MixGoogle({ options, config }));
|
|
108
|
+
}
|
|
109
|
+
gemini25proExp({ options = {}, config = {} } = {}) {
|
|
110
|
+
return this.attach('gemini-2.5-pro-exp-03-25', new MixGoogle({ options, config }));
|
|
111
|
+
}
|
|
112
|
+
gemini25pro({ options = {}, config = {} } = {}) {
|
|
113
|
+
return this.attach('gemini-2.5-pro-preview-05-06', new MixGoogle({ options, config }));
|
|
114
|
+
}
|
|
115
|
+
sonarPro({ options = {}, config = {} } = {}) {
|
|
116
|
+
return this.attach('sonar-pro', new MixPerplexity({ options, config }));
|
|
117
|
+
}
|
|
118
|
+
sonar({ options = {}, config = {} } = {}) {
|
|
119
|
+
return this.attach('sonar', new MixPerplexity({ options, config }));
|
|
298
120
|
}
|
|
299
121
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
122
|
+
grok2({ options = {}, config = {} } = {}) {
|
|
123
|
+
return this.attach('grok-2-latest', new MixGrok({ options, config }));
|
|
124
|
+
}
|
|
125
|
+
grok3({ options = {}, config = {} } = {}) {
|
|
126
|
+
return this.attach('grok-3-beta', new MixGrok({ options, config }));
|
|
127
|
+
}
|
|
128
|
+
grok3mini({ options = {}, config = {} } = {}) {
|
|
129
|
+
return this.attach('grok-3-mini-beta', new MixGrok({ options, config }));
|
|
304
130
|
}
|
|
305
131
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
} catch (error) {
|
|
311
|
-
if (error.code === 'ENOENT') {
|
|
312
|
-
throw new Error(`File not found: ${filePath}`);
|
|
313
|
-
} else if (error.code === 'EACCES') {
|
|
314
|
-
throw new Error(`Permission denied: ${filePath}`);
|
|
315
|
-
} else {
|
|
316
|
-
throw new Error(`Error reading file ${filePath}: ${error.message}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
132
|
+
qwen3({ options = {}, config = {}, mix = { groq: true, together: false } } = {}) {
|
|
133
|
+
if (mix.groq) this.attach('qwen-qwq-32b', new MixGroq({ options, config }));
|
|
134
|
+
if (mix.together) this.attach('Qwen/Qwen3-235B-A22B-fp8-tput', new MixTogether({ options, config }));
|
|
135
|
+
return this;
|
|
319
136
|
}
|
|
320
|
-
}
|
|
321
137
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.
|
|
325
|
-
this.
|
|
326
|
-
this
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
this.
|
|
330
|
-
this.
|
|
138
|
+
scout({ options = {}, config = {}, mix = { groq: true, together: false, cerebras: false } } = {}) {
|
|
139
|
+
if (mix.groq) this.attach('meta-llama/llama-4-scout-17b-16e-instruct', new MixGroq({ options, config }));
|
|
140
|
+
if (mix.together) this.attach('meta-llama/Llama-4-Scout-17B-16E-Instruct', new MixTogether({ options, config }));
|
|
141
|
+
if (mix.cerebras) this.attach('llama-4-scout-17b-16e-instruct', new MixCerebras({ options, config }));
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
maverick({ options = {}, config = {}, mix = { groq: true, together: false } } = {}) {
|
|
145
|
+
if (mix.groq) this.attach('meta-llama/llama-4-maverick-17b-128e-instruct', new MixGroq({ options, config }));
|
|
146
|
+
if (mix.together) this.attach('meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8', new MixTogether({ options, config }));
|
|
147
|
+
return this;
|
|
331
148
|
}
|
|
332
149
|
|
|
333
|
-
|
|
334
|
-
this.
|
|
150
|
+
deepseekR1({ options = {}, config = {}, mix = { groq: true, together: false, cerebras: false } } = {}) {
|
|
151
|
+
if (mix.groq) this.attach('deepseek-r1-distill-llama-70b', new MixGroq({ options, config }));
|
|
152
|
+
if (mix.together) this.attach('deepseek-ai/DeepSeek-R1', new MixTogether({ options, config }));
|
|
153
|
+
if (mix.cerebras) this.attach('deepseek-r1-distill-llama-70b', new MixCerebras({ options, config }));
|
|
335
154
|
return this;
|
|
336
155
|
}
|
|
337
156
|
|
|
338
|
-
addText(text,
|
|
157
|
+
addText(text, { role = "user" } = {}) {
|
|
339
158
|
const content = [{
|
|
340
159
|
type: "text",
|
|
341
160
|
text
|
|
342
161
|
}];
|
|
343
162
|
|
|
344
|
-
this.messages.push({
|
|
163
|
+
this.messages.push({ role, content });
|
|
345
164
|
return this;
|
|
346
165
|
}
|
|
347
166
|
|
|
348
167
|
addTextFromFile(filePath, { role = "user" } = {}) {
|
|
349
|
-
const content = this.
|
|
168
|
+
const content = this.readFile(filePath);
|
|
350
169
|
this.addText(content, { role });
|
|
351
170
|
return this;
|
|
352
171
|
}
|
|
@@ -357,13 +176,13 @@ class MessageHandler {
|
|
|
357
176
|
}
|
|
358
177
|
|
|
359
178
|
setSystemFromFile(filePath) {
|
|
360
|
-
const content = this.
|
|
179
|
+
const content = this.readFile(filePath);
|
|
361
180
|
this.setSystem(content);
|
|
362
181
|
return this;
|
|
363
182
|
}
|
|
364
183
|
|
|
365
184
|
addImage(filePath, { role = "user" } = {}) {
|
|
366
|
-
const imageBuffer = this.
|
|
185
|
+
const imageBuffer = this.readFile(filePath, { encoding: null });
|
|
367
186
|
const mimeType = mime.lookup(filePath);
|
|
368
187
|
|
|
369
188
|
if (!mimeType || !mimeType.startsWith('image/')) {
|
|
@@ -387,16 +206,20 @@ class MessageHandler {
|
|
|
387
206
|
};
|
|
388
207
|
|
|
389
208
|
this.messages.push(imageMessage);
|
|
390
|
-
|
|
391
209
|
return this;
|
|
392
210
|
}
|
|
393
211
|
|
|
394
212
|
addImageFromUrl(url, config = { role: "user" }) {
|
|
213
|
+
if (!this.imagesToProcess) {
|
|
214
|
+
this.imagesToProcess = [];
|
|
215
|
+
}
|
|
395
216
|
this.imagesToProcess.push({ url, config });
|
|
396
217
|
return this;
|
|
397
218
|
}
|
|
398
219
|
|
|
399
220
|
async processImageUrls() {
|
|
221
|
+
if (!this.imagesToProcess) return;
|
|
222
|
+
|
|
400
223
|
const imageContents = await Promise.all(
|
|
401
224
|
this.imagesToProcess.map(async (image) => {
|
|
402
225
|
try {
|
|
@@ -405,7 +228,7 @@ class MessageHandler {
|
|
|
405
228
|
const mimeType = response.headers['content-type'];
|
|
406
229
|
return { base64, mimeType, config: image.config };
|
|
407
230
|
} catch (error) {
|
|
408
|
-
console.error(`Error
|
|
231
|
+
console.error(`Error downloading image from ${image.url}:`, error);
|
|
409
232
|
return null;
|
|
410
233
|
}
|
|
411
234
|
})
|
|
@@ -434,22 +257,18 @@ class MessageHandler {
|
|
|
434
257
|
async message() {
|
|
435
258
|
this.options.stream = false;
|
|
436
259
|
let raw = await this.execute();
|
|
437
|
-
if (!raw.message && raw.response?.content?.[1]?.text) {
|
|
438
|
-
return raw.response.content[1].text;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
260
|
return raw.message;
|
|
442
261
|
}
|
|
443
262
|
|
|
444
263
|
async json(schemaExample = null, schemaDescription = {}, { type = 'json_object', addExample = false, addSchema = true } = {}) {
|
|
445
264
|
this.options.response_format = { type };
|
|
265
|
+
|
|
446
266
|
if (schemaExample) {
|
|
267
|
+
this.config.schema = generateJsonSchema(schemaExample, schemaDescription);
|
|
447
268
|
|
|
448
269
|
if (addSchema) {
|
|
449
|
-
|
|
450
|
-
this.config.systemExtra = "\nOutput JSON Schema: \n```\n" + JSON.stringify(schema) + "\n```";
|
|
270
|
+
this.config.systemExtra = "\nOutput JSON Schema: \n```\n" + JSON.stringify(this.config.schema) + "\n```";
|
|
451
271
|
}
|
|
452
|
-
|
|
453
272
|
if (addExample) {
|
|
454
273
|
this.config.systemExtra += "\nOutput JSON Example: \n```\n" + JSON.stringify(schemaExample) + "\n```";
|
|
455
274
|
}
|
|
@@ -480,22 +299,18 @@ class MessageHandler {
|
|
|
480
299
|
|
|
481
300
|
async stream(callback) {
|
|
482
301
|
this.options.stream = true;
|
|
483
|
-
this.
|
|
302
|
+
this.streamCallback = callback;
|
|
484
303
|
return this.execute();
|
|
485
304
|
}
|
|
486
305
|
|
|
487
|
-
replace(keyValues) {
|
|
488
|
-
this.config.replace = { ...this.config.replace, ...keyValues };
|
|
489
|
-
return this;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
306
|
replaceKeyFromFile(key, filePath) {
|
|
493
|
-
const content = this.
|
|
307
|
+
const content = this.readFile(filePath);
|
|
494
308
|
this.replace({ [key]: this.template(content, this.config.replace) });
|
|
495
309
|
return this;
|
|
496
310
|
}
|
|
497
311
|
|
|
498
312
|
template(input, replace) {
|
|
313
|
+
if (!replace) return input;
|
|
499
314
|
for (const k in replace) {
|
|
500
315
|
input = input.split(/([¿?¡!,"';:\(\)\.\s])/).map(x => x === k ? replace[k] : x).join("");
|
|
501
316
|
}
|
|
@@ -519,13 +334,13 @@ class MessageHandler {
|
|
|
519
334
|
applyTemplate() {
|
|
520
335
|
if (!this.config.replace) return;
|
|
521
336
|
|
|
522
|
-
this.config.system = this.template(this.config.system, this.config.replace)
|
|
337
|
+
this.config.system = this.template(this.config.system, this.config.replace);
|
|
523
338
|
|
|
524
339
|
this.messages = this.messages.map(message => {
|
|
525
340
|
if (message.content instanceof Array) {
|
|
526
341
|
message.content = message.content.map(content => {
|
|
527
342
|
if (content.type === 'text') {
|
|
528
|
-
content.text = this.template(content.text, this.config.replace)
|
|
343
|
+
content.text = this.template(content.text, this.config.replace);
|
|
529
344
|
}
|
|
530
345
|
return content;
|
|
531
346
|
});
|
|
@@ -542,140 +357,96 @@ class MessageHandler {
|
|
|
542
357
|
this.options.messages = this.messages;
|
|
543
358
|
}
|
|
544
359
|
|
|
360
|
+
readFile(filePath, { encoding = 'utf8' } = {}) {
|
|
361
|
+
try {
|
|
362
|
+
const absolutePath = path.resolve(filePath);
|
|
363
|
+
return fs.readFileSync(absolutePath, { encoding });
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (error.code === 'ENOENT') {
|
|
366
|
+
throw new Error(`File not found: ${filePath}`);
|
|
367
|
+
} else if (error.code === 'EACCES') {
|
|
368
|
+
throw new Error(`Permission denied: ${filePath}`);
|
|
369
|
+
} else {
|
|
370
|
+
throw new Error(`Error reading file ${filePath}: ${error.message}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
545
375
|
async execute() {
|
|
546
|
-
|
|
547
|
-
|
|
376
|
+
if (!this.models || this.models.length === 0) {
|
|
377
|
+
throw new Error("No models specified. Use methods like .gpt(), .sonnet() first.");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return this.limiter.schedule(async () => {
|
|
381
|
+
await this.prepareMessages();
|
|
548
382
|
|
|
549
383
|
if (this.messages.length === 0) {
|
|
550
384
|
throw new Error("No user messages have been added. Use addText(prompt), addTextFromFile(filePath), addImage(filePath), or addImageFromUrl(url) to add a prompt.");
|
|
551
385
|
}
|
|
552
386
|
|
|
553
387
|
let lastError = null;
|
|
554
|
-
const modelIterationList = this.allModelsInfo; // Use the full info for iteration
|
|
555
|
-
|
|
556
|
-
// Iterate through the models defined in the handler's list
|
|
557
|
-
for (let i = 0; i < modelIterationList.length; i++) {
|
|
558
|
-
const currentModelDetail = modelIterationList[i];
|
|
559
|
-
const currentModelKey = currentModelDetail.key;
|
|
560
|
-
const currentModelBuilderOptions = currentModelDetail.options || {};
|
|
561
|
-
const currentModelBuilderConfig = currentModelDetail.config || {};
|
|
562
|
-
|
|
563
|
-
// Find the corresponding model provider instance in the ModelMix instance
|
|
564
|
-
const currentModelProviderInstance = Object.values(this.mix.models).find(entry =>
|
|
565
|
-
entry.config.prefix.some(p => currentModelKey.startsWith(p))
|
|
566
|
-
);
|
|
567
|
-
|
|
568
|
-
if (!currentModelProviderInstance) {
|
|
569
|
-
log.warn(`Model provider not found or attached for key: ${currentModelKey}. Skipping.`);
|
|
570
|
-
if (!lastError) {
|
|
571
|
-
lastError = new Error(`Model provider not found for key: ${currentModelKey}`);
|
|
572
|
-
}
|
|
573
|
-
continue; // Try the next model
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// Construct effective options and config for THIS attempt
|
|
577
|
-
const attemptOptions = {
|
|
578
|
-
...this.mix.defaultOptions, // 1. ModelMix global defaults
|
|
579
|
-
...(currentModelProviderInstance.options || {}), // 2. Provider class defaults for current model
|
|
580
|
-
...this.options, // 3. MessageHandler current general options (from primary + handler changes)
|
|
581
|
-
...currentModelBuilderOptions, // 4. Specific options from addModel for THIS model
|
|
582
|
-
model: currentModelKey // 5. Crucial: set current model key
|
|
583
|
-
};
|
|
584
388
|
|
|
585
|
-
|
|
586
|
-
...this.mix.config, // 1. ModelMix global config
|
|
587
|
-
...(currentModelProviderInstance.config || {}), // 2. Provider class config for current model
|
|
588
|
-
...this.config, // 3. MessageHandler current general config
|
|
589
|
-
...currentModelBuilderConfig // 4. Specific config from addModel for THIS model
|
|
590
|
-
};
|
|
389
|
+
for (let i = 0; i < this.models.length; i++) {
|
|
591
390
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
this.config.hasOwnProperty('debug') ? this.config.debug :
|
|
596
|
-
this.mix.config.debug;
|
|
391
|
+
const currentModel = this.models[i];
|
|
392
|
+
const currentModelKey = currentModel.key;
|
|
393
|
+
const providerInstance = currentModel.provider;
|
|
597
394
|
|
|
598
|
-
|
|
599
|
-
|
|
395
|
+
let options = {
|
|
396
|
+
...this.options,
|
|
397
|
+
...providerInstance.options,
|
|
398
|
+
model: currentModelKey
|
|
399
|
+
};
|
|
600
400
|
|
|
401
|
+
const config = {
|
|
402
|
+
...this.config,
|
|
403
|
+
...providerInstance.config,
|
|
404
|
+
};
|
|
601
405
|
|
|
602
|
-
if (
|
|
406
|
+
if (config.debug) {
|
|
603
407
|
const isPrimary = i === 0;
|
|
604
|
-
log.debug(`Attempt #${i + 1}
|
|
605
|
-
log.debug("Effective attemptOptions for " + currentModelKey + ":");
|
|
606
|
-
log.inspect(attemptOptions);
|
|
607
|
-
log.debug("Effective apiCallConfig for " + currentModelKey + ":");
|
|
608
|
-
log.inspect(apiCallConfig);
|
|
408
|
+
log.debug(`[${currentModelKey}] Attempt #${i + 1}` + (isPrimary ? ' (Primary)' : ' (Fallback)'));
|
|
609
409
|
}
|
|
610
410
|
|
|
611
|
-
|
|
612
|
-
// Apply model-specific adjustments to a copy of options for this attempt
|
|
613
|
-
let finalAttemptOptions = { ...attemptOptions };
|
|
614
|
-
if (currentModelProviderInstance instanceof MixOpenAI && finalAttemptOptions.model?.startsWith('o')) {
|
|
615
|
-
delete finalAttemptOptions.max_tokens;
|
|
616
|
-
delete finalAttemptOptions.temperature;
|
|
617
|
-
}
|
|
618
|
-
if (currentModelProviderInstance instanceof MixAnthropic) {
|
|
619
|
-
if (finalAttemptOptions.thinking) {
|
|
620
|
-
delete finalAttemptOptions.top_p;
|
|
621
|
-
// if (finalAttemptOptions.temperature < 1) {
|
|
622
|
-
// finalAttemptOptions.temperature = 1;
|
|
623
|
-
// }
|
|
624
|
-
}
|
|
625
|
-
delete finalAttemptOptions.response_format; // Anthropic doesn't use this top-level option
|
|
626
|
-
}
|
|
627
|
-
// ... add other potential model-specific option adjustments here ...
|
|
628
|
-
|
|
629
411
|
try {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (finalAttemptOptions.stream && this.modelEntry && this.modelEntry.streamCallback) {
|
|
633
|
-
currentModelProviderInstance.streamCallback = this.modelEntry.streamCallback;
|
|
412
|
+
if (options.stream && this.streamCallback) {
|
|
413
|
+
providerInstance.streamCallback = this.streamCallback;
|
|
634
414
|
}
|
|
635
415
|
|
|
636
|
-
|
|
637
|
-
const result = await currentModelProviderInstance.create({ options: finalAttemptOptions, config: apiCallConfig });
|
|
638
|
-
|
|
639
|
-
// Add successful response to history *before* returning
|
|
640
|
-
let messageContentToAdd = result.message;
|
|
641
|
-
if (currentModelProviderInstance instanceof MixAnthropic && result.response?.content?.[0]?.text) {
|
|
642
|
-
messageContentToAdd = result.response.content[0].text;
|
|
643
|
-
} else if (currentModelProviderInstance instanceof MixOllama && result.response?.message?.content) {
|
|
644
|
-
messageContentToAdd = result.response.message.content;
|
|
645
|
-
} // Add more cases if other providers have different structures
|
|
416
|
+
const result = await providerInstance.create({ options, config });
|
|
646
417
|
|
|
647
|
-
this.messages.push({ role: "assistant", content:
|
|
418
|
+
this.messages.push({ role: "assistant", content: result.message });
|
|
648
419
|
|
|
649
|
-
if (
|
|
420
|
+
if (config.debug) {
|
|
650
421
|
log.debug(`Request successful with model: ${currentModelKey}`);
|
|
651
422
|
log.inspect(result.response);
|
|
652
423
|
}
|
|
653
|
-
|
|
424
|
+
|
|
425
|
+
return result;
|
|
426
|
+
|
|
654
427
|
} catch (error) {
|
|
655
|
-
lastError = error;
|
|
656
|
-
log.warn(`Model ${currentModelKey} failed (Attempt #${i + 1}/${
|
|
428
|
+
lastError = error;
|
|
429
|
+
log.warn(`Model ${currentModelKey} failed (Attempt #${i + 1}/${this.models.length}).`);
|
|
657
430
|
if (error.message) log.warn(`Error: ${error.message}`);
|
|
658
431
|
if (error.statusCode) log.warn(`Status Code: ${error.statusCode}`);
|
|
659
432
|
if (error.details) log.warn(`Details: ${JSON.stringify(error.details)}`);
|
|
660
433
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
throw lastError; // Re-throw the last encountered error
|
|
434
|
+
if (i === this.models.length - 1) {
|
|
435
|
+
log.error(`All ${this.models.length} model(s) failed. Throwing last error from ${currentModelKey}.`);
|
|
436
|
+
throw lastError;
|
|
665
437
|
} else {
|
|
666
|
-
const nextModelKey =
|
|
438
|
+
const nextModelKey = this.models[i + 1].key;
|
|
667
439
|
log.info(`-> Proceeding to next model: ${nextModelKey}`);
|
|
668
440
|
}
|
|
669
441
|
}
|
|
670
442
|
}
|
|
671
443
|
|
|
672
|
-
// This point should theoretically not be reached if there's at least one model key
|
|
673
|
-
// and the loop either returns a result or throws an error.
|
|
674
444
|
log.error("Fallback logic completed without success or throwing the final error.");
|
|
675
445
|
throw lastError || new Error("Failed to get response from any model, and no specific error was caught.");
|
|
676
446
|
});
|
|
677
447
|
}
|
|
678
448
|
}
|
|
449
|
+
|
|
679
450
|
class MixCustom {
|
|
680
451
|
constructor({ config = {}, options = {}, headers = {} } = {}) {
|
|
681
452
|
this.config = this.getDefaultConfig(config);
|
|
@@ -694,7 +465,6 @@ class MixCustom {
|
|
|
694
465
|
return {
|
|
695
466
|
url: '',
|
|
696
467
|
apiKey: '',
|
|
697
|
-
prefix: [],
|
|
698
468
|
...customConfig
|
|
699
469
|
};
|
|
700
470
|
}
|
|
@@ -797,8 +567,16 @@ class MixCustom {
|
|
|
797
567
|
return '';
|
|
798
568
|
}
|
|
799
569
|
|
|
570
|
+
extractMessage(data) {
|
|
571
|
+
if (data.choices && data.choices[0].message.content) return data.choices[0].message.content;
|
|
572
|
+
return '';
|
|
573
|
+
}
|
|
574
|
+
|
|
800
575
|
processResponse(response) {
|
|
801
|
-
return {
|
|
576
|
+
return {
|
|
577
|
+
response: response.data,
|
|
578
|
+
message: this.extractMessage(response.data)
|
|
579
|
+
};
|
|
802
580
|
}
|
|
803
581
|
}
|
|
804
582
|
|
|
@@ -806,7 +584,6 @@ class MixOpenAI extends MixCustom {
|
|
|
806
584
|
getDefaultConfig(customConfig) {
|
|
807
585
|
return super.getDefaultConfig({
|
|
808
586
|
url: 'https://api.openai.com/v1/chat/completions',
|
|
809
|
-
prefix: ['gpt', 'ft:', 'o'],
|
|
810
587
|
apiKey: process.env.OPENAI_API_KEY,
|
|
811
588
|
...customConfig
|
|
812
589
|
});
|
|
@@ -854,7 +631,6 @@ class MixAnthropic extends MixCustom {
|
|
|
854
631
|
getDefaultConfig(customConfig) {
|
|
855
632
|
return super.getDefaultConfig({
|
|
856
633
|
url: 'https://api.anthropic.com/v1/messages',
|
|
857
|
-
prefix: ['claude'],
|
|
858
634
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
859
635
|
...customConfig
|
|
860
636
|
});
|
|
@@ -889,8 +665,18 @@ class MixAnthropic extends MixCustom {
|
|
|
889
665
|
return '';
|
|
890
666
|
}
|
|
891
667
|
|
|
892
|
-
|
|
893
|
-
|
|
668
|
+
extractMessage(data) {
|
|
669
|
+
if (data.content) {
|
|
670
|
+
// thinking
|
|
671
|
+
if (data.content?.[1]?.text) {
|
|
672
|
+
return data.content[1].text;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (data.content[0].text) {
|
|
676
|
+
return data.content[0].text;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return '';
|
|
894
680
|
}
|
|
895
681
|
}
|
|
896
682
|
|
|
@@ -898,13 +684,22 @@ class MixPerplexity extends MixCustom {
|
|
|
898
684
|
getDefaultConfig(customConfig) {
|
|
899
685
|
return super.getDefaultConfig({
|
|
900
686
|
url: 'https://api.perplexity.ai/chat/completions',
|
|
901
|
-
prefix: ['sonar'],
|
|
902
687
|
apiKey: process.env.PPLX_API_KEY,
|
|
903
688
|
...customConfig
|
|
904
689
|
});
|
|
905
690
|
}
|
|
906
691
|
|
|
907
692
|
async create({ config = {}, options = {} } = {}) {
|
|
693
|
+
|
|
694
|
+
if (config.schema) {
|
|
695
|
+
config.systemExtra = '';
|
|
696
|
+
|
|
697
|
+
options.response_format = {
|
|
698
|
+
type: 'json_schema',
|
|
699
|
+
json_schema: { schema: config.schema }
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
908
703
|
if (!this.config.apiKey) {
|
|
909
704
|
throw new Error('Perplexity API key not found. Please provide it in config or set PPLX_API_KEY environment variable.');
|
|
910
705
|
}
|
|
@@ -943,8 +738,8 @@ class MixOllama extends MixCustom {
|
|
|
943
738
|
return super.create({ config, options });
|
|
944
739
|
}
|
|
945
740
|
|
|
946
|
-
|
|
947
|
-
return
|
|
741
|
+
extractMessage(data) {
|
|
742
|
+
return data.message.content.trim();
|
|
948
743
|
}
|
|
949
744
|
|
|
950
745
|
static convertMessages(messages) {
|
|
@@ -973,7 +768,6 @@ class MixGrok extends MixOpenAI {
|
|
|
973
768
|
getDefaultConfig(customConfig) {
|
|
974
769
|
return super.getDefaultConfig({
|
|
975
770
|
url: 'https://api.x.ai/v1/chat/completions',
|
|
976
|
-
prefix: ['grok'],
|
|
977
771
|
apiKey: process.env.XAI_API_KEY,
|
|
978
772
|
...customConfig
|
|
979
773
|
});
|
|
@@ -1000,7 +794,6 @@ class MixGroq extends MixCustom {
|
|
|
1000
794
|
getDefaultConfig(customConfig) {
|
|
1001
795
|
return super.getDefaultConfig({
|
|
1002
796
|
url: 'https://api.groq.com/openai/v1/chat/completions',
|
|
1003
|
-
prefix: ["llama", "mixtral", "gemma", "deepseek-r1-distill"],
|
|
1004
797
|
apiKey: process.env.GROQ_API_KEY,
|
|
1005
798
|
...customConfig
|
|
1006
799
|
});
|
|
@@ -1022,7 +815,6 @@ class MixTogether extends MixCustom {
|
|
|
1022
815
|
getDefaultConfig(customConfig) {
|
|
1023
816
|
return super.getDefaultConfig({
|
|
1024
817
|
url: 'https://api.together.xyz/v1/chat/completions',
|
|
1025
|
-
prefix: ["meta-llama", "google", "NousResearch", "deepseek-ai", "Qwen"],
|
|
1026
818
|
apiKey: process.env.TOGETHER_API_KEY,
|
|
1027
819
|
...customConfig
|
|
1028
820
|
});
|
|
@@ -1061,7 +853,6 @@ class MixCerebras extends MixCustom {
|
|
|
1061
853
|
getDefaultConfig(customConfig) {
|
|
1062
854
|
return super.getDefaultConfig({
|
|
1063
855
|
url: 'https://api.cerebras.ai/v1/chat/completions',
|
|
1064
|
-
prefix: ["llama"],
|
|
1065
856
|
apiKey: process.env.CEREBRAS_API_KEY,
|
|
1066
857
|
...customConfig
|
|
1067
858
|
});
|
|
@@ -1079,14 +870,12 @@ class MixGoogle extends MixCustom {
|
|
|
1079
870
|
getDefaultConfig(customConfig) {
|
|
1080
871
|
return super.getDefaultConfig({
|
|
1081
872
|
url: 'https://generativelanguage.googleapis.com/v1beta/models',
|
|
1082
|
-
prefix: ['gemini'],
|
|
1083
873
|
apiKey: process.env.GOOGLE_API_KEY,
|
|
1084
874
|
...customConfig
|
|
1085
875
|
});
|
|
1086
876
|
}
|
|
1087
877
|
|
|
1088
878
|
getDefaultHeaders(customHeaders) {
|
|
1089
|
-
// Remove the authorization header as we'll use the API key as a query parameter
|
|
1090
879
|
return {
|
|
1091
880
|
'Content-Type': 'application/json',
|
|
1092
881
|
...customHeaders
|
|
@@ -1105,7 +894,7 @@ class MixGoogle extends MixCustom {
|
|
|
1105
894
|
static convertMessages(messages) {
|
|
1106
895
|
return messages.map(message => {
|
|
1107
896
|
const parts = [];
|
|
1108
|
-
|
|
897
|
+
|
|
1109
898
|
if (message.content instanceof Array) {
|
|
1110
899
|
message.content.forEach(content => {
|
|
1111
900
|
if (content.type === 'text') {
|
|
@@ -1137,13 +926,13 @@ class MixGoogle extends MixCustom {
|
|
|
1137
926
|
|
|
1138
927
|
const modelId = options.model || 'gemini-2.5-flash-preview-04-17';
|
|
1139
928
|
const generateContentApi = options.stream ? 'streamGenerateContent' : 'generateContent';
|
|
1140
|
-
|
|
929
|
+
|
|
1141
930
|
// Construct the full URL with model ID, API endpoint, and API key
|
|
1142
931
|
const fullUrl = `${this.config.url}/${modelId}:${generateContentApi}?key=${this.config.apiKey}`;
|
|
1143
932
|
|
|
1144
933
|
// Convert messages to Gemini format
|
|
1145
934
|
const contents = MixGoogle.convertMessages(options.messages);
|
|
1146
|
-
|
|
935
|
+
|
|
1147
936
|
// Add system message if present
|
|
1148
937
|
if (config.system || config.systemExtra) {
|
|
1149
938
|
contents.unshift({
|
|
@@ -1171,21 +960,8 @@ class MixGoogle extends MixCustom {
|
|
|
1171
960
|
}
|
|
1172
961
|
}
|
|
1173
962
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
const parsed = JSON.parse(data);
|
|
1177
|
-
if (parsed.candidates?.[0]?.content?.parts?.[0]?.text) {
|
|
1178
|
-
return parsed.candidates[0].content.parts[0].text;
|
|
1179
|
-
}
|
|
1180
|
-
} catch (e) {
|
|
1181
|
-
// If parsing fails, return empty string
|
|
1182
|
-
}
|
|
1183
|
-
return '';
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
processResponse(response) {
|
|
1187
|
-
const content = response.data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
1188
|
-
return { response: response.data, message: content };
|
|
963
|
+
extractMessage(data) {
|
|
964
|
+
return data.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
1189
965
|
}
|
|
1190
966
|
}
|
|
1191
967
|
|