modelmix 3.2.2 → 3.3.4
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 +90 -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 +214 -424
- 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
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return this.
|
|
66
|
+
// --- Model addition methods ---
|
|
67
|
+
gpt41({ options = {}, config = {} } = {}) {
|
|
68
|
+
return this.attach('gpt-4.1', new MixOpenAI({ options, config }));
|
|
69
|
+
}
|
|
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 }));
|
|
293
120
|
}
|
|
294
121
|
|
|
295
|
-
|
|
296
|
-
this.config
|
|
297
|
-
|
|
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 }));
|
|
298
130
|
}
|
|
299
131
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
this.
|
|
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 }));
|
|
303
135
|
return this;
|
|
304
136
|
}
|
|
305
137
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
}
|
|
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;
|
|
319
143
|
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.mix = mix;
|
|
325
|
-
this.modelEntry = modelEntry; // Primary model's provider instance
|
|
326
|
-
this.options = options; // Session-level options, based on primary
|
|
327
|
-
this.config = config; // Session-level config, based on primary
|
|
328
|
-
this.messages = [];
|
|
329
|
-
this.allModelsInfo = allModelsInfo; // Store the full info array [{ key, providerClass, options, config }, ...]
|
|
330
|
-
this.imagesToProcess = [];
|
|
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
388
|
|
|
576
|
-
|
|
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
|
-
};
|
|
389
|
+
for (let i = 0; i < this.models.length; i++) {
|
|
584
390
|
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
...this.config, // 3. MessageHandler current general config
|
|
589
|
-
...currentModelBuilderConfig // 4. Specific config from addModel for THIS model
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
// Determine the effective debug flag for this attempt (for logging and API call context)
|
|
593
|
-
// Precedence: model-specific builder config -> handler config -> mix config
|
|
594
|
-
const effectiveDebugForAttempt = attemptConfig.hasOwnProperty('debug') ? attemptConfig.debug :
|
|
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 });
|
|
416
|
+
const result = await providerInstance.create({ options, config });
|
|
638
417
|
|
|
639
|
-
|
|
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
|
|
418
|
+
this.messages.push({ role: "assistant", content: result.message });
|
|
646
419
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (effectiveDebugForAttempt) {
|
|
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,24 @@ 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.trim();
|
|
572
|
+
return '';
|
|
573
|
+
}
|
|
574
|
+
|
|
800
575
|
processResponse(response) {
|
|
801
|
-
|
|
576
|
+
let message = this.extractMessage(response.data);
|
|
577
|
+
|
|
578
|
+
if (message.startsWith('<think>')) {
|
|
579
|
+
const endTagIndex = message.indexOf('</think>');
|
|
580
|
+
if (endTagIndex !== -1) {
|
|
581
|
+
const think = message.substring(7, endTagIndex).trim();
|
|
582
|
+
message = message.substring(endTagIndex + 8).trim();
|
|
583
|
+
return { response: response.data, message, think };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return { response: response.data, message };
|
|
802
588
|
}
|
|
803
589
|
}
|
|
804
590
|
|
|
@@ -806,7 +592,6 @@ class MixOpenAI extends MixCustom {
|
|
|
806
592
|
getDefaultConfig(customConfig) {
|
|
807
593
|
return super.getDefaultConfig({
|
|
808
594
|
url: 'https://api.openai.com/v1/chat/completions',
|
|
809
|
-
prefix: ['gpt', 'ft:', 'o'],
|
|
810
595
|
apiKey: process.env.OPENAI_API_KEY,
|
|
811
596
|
...customConfig
|
|
812
597
|
});
|
|
@@ -854,7 +639,6 @@ class MixAnthropic extends MixCustom {
|
|
|
854
639
|
getDefaultConfig(customConfig) {
|
|
855
640
|
return super.getDefaultConfig({
|
|
856
641
|
url: 'https://api.anthropic.com/v1/messages',
|
|
857
|
-
prefix: ['claude'],
|
|
858
642
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
859
643
|
...customConfig
|
|
860
644
|
});
|
|
@@ -890,7 +674,23 @@ class MixAnthropic extends MixCustom {
|
|
|
890
674
|
}
|
|
891
675
|
|
|
892
676
|
processResponse(response) {
|
|
893
|
-
|
|
677
|
+
if (response.data.content) {
|
|
678
|
+
|
|
679
|
+
if (response.data.content?.[1]?.text) {
|
|
680
|
+
return {
|
|
681
|
+
think: response.data.content[0]?.thinking,
|
|
682
|
+
message: response.data.content[1].text,
|
|
683
|
+
response: response.data
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (response.data.content[0].text) {
|
|
688
|
+
return {
|
|
689
|
+
message: response.data.content[0].text,
|
|
690
|
+
response: response.data
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
894
694
|
}
|
|
895
695
|
}
|
|
896
696
|
|
|
@@ -898,13 +698,22 @@ class MixPerplexity extends MixCustom {
|
|
|
898
698
|
getDefaultConfig(customConfig) {
|
|
899
699
|
return super.getDefaultConfig({
|
|
900
700
|
url: 'https://api.perplexity.ai/chat/completions',
|
|
901
|
-
prefix: ['sonar'],
|
|
902
701
|
apiKey: process.env.PPLX_API_KEY,
|
|
903
702
|
...customConfig
|
|
904
703
|
});
|
|
905
704
|
}
|
|
906
705
|
|
|
907
706
|
async create({ config = {}, options = {} } = {}) {
|
|
707
|
+
|
|
708
|
+
if (config.schema) {
|
|
709
|
+
config.systemExtra = '';
|
|
710
|
+
|
|
711
|
+
options.response_format = {
|
|
712
|
+
type: 'json_schema',
|
|
713
|
+
json_schema: { schema: config.schema }
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
908
717
|
if (!this.config.apiKey) {
|
|
909
718
|
throw new Error('Perplexity API key not found. Please provide it in config or set PPLX_API_KEY environment variable.');
|
|
910
719
|
}
|
|
@@ -943,8 +752,8 @@ class MixOllama extends MixCustom {
|
|
|
943
752
|
return super.create({ config, options });
|
|
944
753
|
}
|
|
945
754
|
|
|
946
|
-
|
|
947
|
-
return
|
|
755
|
+
extractMessage(data) {
|
|
756
|
+
return data.message.content.trim();
|
|
948
757
|
}
|
|
949
758
|
|
|
950
759
|
static convertMessages(messages) {
|
|
@@ -973,7 +782,6 @@ class MixGrok extends MixOpenAI {
|
|
|
973
782
|
getDefaultConfig(customConfig) {
|
|
974
783
|
return super.getDefaultConfig({
|
|
975
784
|
url: 'https://api.x.ai/v1/chat/completions',
|
|
976
|
-
prefix: ['grok'],
|
|
977
785
|
apiKey: process.env.XAI_API_KEY,
|
|
978
786
|
...customConfig
|
|
979
787
|
});
|
|
@@ -1000,7 +808,6 @@ class MixGroq extends MixCustom {
|
|
|
1000
808
|
getDefaultConfig(customConfig) {
|
|
1001
809
|
return super.getDefaultConfig({
|
|
1002
810
|
url: 'https://api.groq.com/openai/v1/chat/completions',
|
|
1003
|
-
prefix: ["llama", "mixtral", "gemma", "deepseek-r1-distill"],
|
|
1004
811
|
apiKey: process.env.GROQ_API_KEY,
|
|
1005
812
|
...customConfig
|
|
1006
813
|
});
|
|
@@ -1022,7 +829,6 @@ class MixTogether extends MixCustom {
|
|
|
1022
829
|
getDefaultConfig(customConfig) {
|
|
1023
830
|
return super.getDefaultConfig({
|
|
1024
831
|
url: 'https://api.together.xyz/v1/chat/completions',
|
|
1025
|
-
prefix: ["meta-llama", "google", "NousResearch", "deepseek-ai", "Qwen"],
|
|
1026
832
|
apiKey: process.env.TOGETHER_API_KEY,
|
|
1027
833
|
...customConfig
|
|
1028
834
|
});
|
|
@@ -1061,7 +867,6 @@ class MixCerebras extends MixCustom {
|
|
|
1061
867
|
getDefaultConfig(customConfig) {
|
|
1062
868
|
return super.getDefaultConfig({
|
|
1063
869
|
url: 'https://api.cerebras.ai/v1/chat/completions',
|
|
1064
|
-
prefix: ["llama"],
|
|
1065
870
|
apiKey: process.env.CEREBRAS_API_KEY,
|
|
1066
871
|
...customConfig
|
|
1067
872
|
});
|
|
@@ -1079,14 +884,12 @@ class MixGoogle extends MixCustom {
|
|
|
1079
884
|
getDefaultConfig(customConfig) {
|
|
1080
885
|
return super.getDefaultConfig({
|
|
1081
886
|
url: 'https://generativelanguage.googleapis.com/v1beta/models',
|
|
1082
|
-
prefix: ['gemini'],
|
|
1083
887
|
apiKey: process.env.GOOGLE_API_KEY,
|
|
1084
888
|
...customConfig
|
|
1085
889
|
});
|
|
1086
890
|
}
|
|
1087
891
|
|
|
1088
892
|
getDefaultHeaders(customHeaders) {
|
|
1089
|
-
// Remove the authorization header as we'll use the API key as a query parameter
|
|
1090
893
|
return {
|
|
1091
894
|
'Content-Type': 'application/json',
|
|
1092
895
|
...customHeaders
|
|
@@ -1105,7 +908,7 @@ class MixGoogle extends MixCustom {
|
|
|
1105
908
|
static convertMessages(messages) {
|
|
1106
909
|
return messages.map(message => {
|
|
1107
910
|
const parts = [];
|
|
1108
|
-
|
|
911
|
+
|
|
1109
912
|
if (message.content instanceof Array) {
|
|
1110
913
|
message.content.forEach(content => {
|
|
1111
914
|
if (content.type === 'text') {
|
|
@@ -1137,13 +940,13 @@ class MixGoogle extends MixCustom {
|
|
|
1137
940
|
|
|
1138
941
|
const modelId = options.model || 'gemini-2.5-flash-preview-04-17';
|
|
1139
942
|
const generateContentApi = options.stream ? 'streamGenerateContent' : 'generateContent';
|
|
1140
|
-
|
|
943
|
+
|
|
1141
944
|
// Construct the full URL with model ID, API endpoint, and API key
|
|
1142
945
|
const fullUrl = `${this.config.url}/${modelId}:${generateContentApi}?key=${this.config.apiKey}`;
|
|
1143
946
|
|
|
1144
947
|
// Convert messages to Gemini format
|
|
1145
948
|
const contents = MixGoogle.convertMessages(options.messages);
|
|
1146
|
-
|
|
949
|
+
|
|
1147
950
|
// Add system message if present
|
|
1148
951
|
if (config.system || config.systemExtra) {
|
|
1149
952
|
contents.unshift({
|
|
@@ -1171,21 +974,8 @@ class MixGoogle extends MixCustom {
|
|
|
1171
974
|
}
|
|
1172
975
|
}
|
|
1173
976
|
|
|
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 };
|
|
977
|
+
extractMessage(data) {
|
|
978
|
+
return data.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
1189
979
|
}
|
|
1190
980
|
}
|
|
1191
981
|
|