@@ -28,21 +28,11 @@ class GeminiLLM {
28
28
const genAI = new GoogleGenerativeAI ( process . env . GEMINI_API_KEY ) ;
29
29
this . model =
30
30
modelPreference || process . env . GEMINI_LLM_MODEL_PREF || "gemini-pro" ;
31
+
32
+ const isExperimental = this . isExperimentalModel ( this . model ) ;
31
33
this . gemini = genAI . getGenerativeModel (
32
34
{ model : this . model } ,
33
- {
34
- apiVersion :
35
- /**
36
- * There are some models that are only available in the v1beta API
37
- * and some models that are only available in the v1 API
38
- * generally, v1beta models have `exp` in the name, but not always
39
- * so we check for both against a static list as well.
40
- * @see {v1BetaModels}
41
- */
42
- this . model . includes ( "exp" ) || v1BetaModels . includes ( this . model )
43
- ? "v1beta"
44
- : "v1" ,
45
- }
35
+ { apiVersion : isExperimental ? "v1beta" : "v1" }
46
36
) ;
47
37
this . limits = {
48
38
history : this . promptWindowLimit ( ) * 0.15 ,
@@ -59,7 +49,7 @@ class GeminiLLM {
59
49
this . cacheModelPath = path . resolve ( cacheFolder , "models.json" ) ;
60
50
this . cacheAtPath = path . resolve ( cacheFolder , ".cached_at" ) ;
61
51
this . #log(
62
- `Initialized with model: ${ this . model } ( ${ this . promptWindowLimit ( ) } ) `
52
+ `Initialized with model: ${ this . model } ${ isExperimental ? "[Experimental v1beta]" : "[Stable v1]" } - ctx: ${ this . promptWindowLimit ( ) } `
63
53
) ;
64
54
}
65
55
@@ -71,7 +61,7 @@ class GeminiLLM {
71
61
// from the current date. If it is, then we will refetch the API so that all the models are up
72
62
// to date.
73
63
static cacheIsStale ( ) {
74
- const MAX_STALE = 6.048e8 ; // 1 Week in MS
64
+ const MAX_STALE = 8.64e7 ; // 1 day in MS
75
65
if ( ! fs . existsSync ( path . resolve ( cacheFolder , ".cached_at" ) ) ) return true ;
76
66
const now = Number ( new Date ( ) ) ;
77
67
const timestampMs = Number (
@@ -168,6 +158,28 @@ class GeminiLLM {
168
158
}
169
159
}
170
160
161
+ /**
162
+ * Checks if a model is experimental by reading from the cache if available, otherwise it will perform
163
+ * a blind check against the v1BetaModels list - which is manually maintained and updated.
164
+ * @param {string } modelName - The name of the model to check
165
+ * @returns {boolean } A boolean indicating if the model is experimental
166
+ */
167
+ isExperimentalModel ( modelName ) {
168
+ if (
169
+ fs . existsSync ( cacheFolder ) &&
170
+ fs . existsSync ( path . resolve ( cacheFolder , "models.json" ) )
171
+ ) {
172
+ const models = safeJsonParse (
173
+ fs . readFileSync ( path . resolve ( cacheFolder , "models.json" ) )
174
+ ) ;
175
+ const model = models . find ( ( model ) => model . id === modelName ) ;
176
+ if ( ! model ) return false ;
177
+ return model . experimental ;
178
+ }
179
+
180
+ return modelName . includes ( "exp" ) || v1BetaModels . includes ( modelName ) ;
181
+ }
182
+
171
183
/**
172
184
* Fetches Gemini models from the Google Generative AI API
173
185
* @param {string } apiKey - The API key to use for the request
@@ -186,63 +198,125 @@ class GeminiLLM {
186
198
) ;
187
199
}
188
200
189
- const url = new URL (
190
- "https://generativelanguage.googleapis.com/v1beta/models"
191
- ) ;
192
- url . searchParams . set ( "pageSize" , limit ) ;
193
- url . searchParams . set ( "key" , apiKey ) ;
194
- if ( pageToken ) url . searchParams . set ( "pageToken" , pageToken ) ;
195
- let success = false ;
196
-
197
- const models = await fetch ( url . toString ( ) , {
198
- method : "GET" ,
199
- headers : { "Content-Type" : "application/json" } ,
200
- } )
201
- . then ( ( res ) => res . json ( ) )
202
- . then ( ( data ) => {
203
- if ( data . error ) throw new Error ( data . error . message ) ;
204
- return data . models ?? [ ] ;
205
- } )
206
- . then ( ( models ) => {
207
- success = true ;
208
- return models
209
- . filter (
210
- ( model ) => ! model . displayName . toLowerCase ( ) . includes ( "tuning" )
211
- )
212
- . filter ( ( model ) =>
213
- model . supportedGenerationMethods . includes ( "generateContent" )
214
- ) // Only generateContent is supported
215
- . map ( ( model ) => {
216
- return {
217
- id : model . name . split ( "/" ) . pop ( ) ,
218
- name : model . displayName ,
219
- contextWindow : model . inputTokenLimit ,
220
- experimental : model . name . includes ( "exp" ) ,
221
- } ;
222
- } ) ;
223
- } )
224
- . catch ( ( e ) => {
225
- console . error ( `Gemini:getGeminiModels` , e . message ) ;
226
- success = false ;
227
- return defaultGeminiModels ;
228
- } ) ;
201
+ const stableModels = [ ] ;
202
+ const allModels = [ ] ;
229
203
230
- if ( success ) {
231
- console . log (
232
- `\x1b[32m[GeminiLLM]\x1b[0m Writing cached models API response to disk.`
233
- ) ;
234
- if ( ! fs . existsSync ( cacheFolder ) )
235
- fs . mkdirSync ( cacheFolder , { recursive : true } ) ;
236
- fs . writeFileSync (
237
- path . resolve ( cacheFolder , "models.json" ) ,
238
- JSON . stringify ( models )
204
+ // Fetch from v1
205
+ try {
206
+ const url = new URL (
207
+ "https://generativelanguage.googleapis.com/v1/models"
239
208
) ;
240
- fs . writeFileSync (
241
- path . resolve ( cacheFolder , ".cached_at" ) ,
242
- new Date ( ) . getTime ( ) . toString ( )
209
+ url . searchParams . set ( "pageSize" , limit ) ;
210
+ url . searchParams . set ( "key" , apiKey ) ;
211
+ if ( pageToken ) url . searchParams . set ( "pageToken" , pageToken ) ;
212
+ await fetch ( url . toString ( ) , {
213
+ method : "GET" ,
214
+ headers : { "Content-Type" : "application/json" } ,
215
+ } )
216
+ . then ( ( res ) => res . json ( ) )
217
+ . then ( ( data ) => {
218
+ if ( data . error ) throw new Error ( data . error . message ) ;
219
+ return data . models ?? [ ] ;
220
+ } )
221
+ . then ( ( models ) => {
222
+ return models
223
+ . filter (
224
+ ( model ) => ! model . displayName ?. toLowerCase ( ) ?. includes ( "tuning" )
225
+ ) // remove tuning models
226
+ . filter (
227
+ ( model ) =>
228
+ ! model . description ?. toLowerCase ( ) ?. includes ( "deprecated" )
229
+ ) // remove deprecated models (in comment)
230
+ . filter ( ( model ) =>
231
+ // Only generateContent is supported
232
+ model . supportedGenerationMethods . includes ( "generateContent" )
233
+ )
234
+ . map ( ( model ) => {
235
+ stableModels . push ( model . name ) ;
236
+ allModels . push ( {
237
+ id : model . name . split ( "/" ) . pop ( ) ,
238
+ name : model . displayName ,
239
+ contextWindow : model . inputTokenLimit ,
240
+ experimental : false ,
241
+ } ) ;
242
+ } ) ;
243
+ } )
244
+ . catch ( ( e ) => {
245
+ console . error ( `Gemini:getGeminiModelsV1` , e . message ) ;
246
+ return ;
247
+ } ) ;
248
+ } catch ( e ) {
249
+ console . error ( `Gemini:getGeminiModelsV1` , e . message ) ;
250
+ }
251
+
252
+ // Fetch from v1beta
253
+ try {
254
+ const url = new URL (
255
+ "https://generativelanguage.googleapis.com/v1beta/models"
243
256
) ;
257
+ url . searchParams . set ( "pageSize" , limit ) ;
258
+ url . searchParams . set ( "key" , apiKey ) ;
259
+ if ( pageToken ) url . searchParams . set ( "pageToken" , pageToken ) ;
260
+ await fetch ( url . toString ( ) , {
261
+ method : "GET" ,
262
+ headers : { "Content-Type" : "application/json" } ,
263
+ } )
264
+ . then ( ( res ) => res . json ( ) )
265
+ . then ( ( data ) => {
266
+ if ( data . error ) throw new Error ( data . error . message ) ;
267
+ return data . models ?? [ ] ;
268
+ } )
269
+ . then ( ( models ) => {
270
+ return models
271
+ . filter ( ( model ) => ! stableModels . includes ( model . name ) ) // remove stable models that are already in the v1 list
272
+ . filter (
273
+ ( model ) => ! model . displayName ?. toLowerCase ( ) ?. includes ( "tuning" )
274
+ ) // remove tuning models
275
+ . filter (
276
+ ( model ) =>
277
+ ! model . description ?. toLowerCase ( ) ?. includes ( "deprecated" )
278
+ ) // remove deprecated models (in comment)
279
+ . filter ( ( model ) =>
280
+ // Only generateContent is supported
281
+ model . supportedGenerationMethods . includes ( "generateContent" )
282
+ )
283
+ . map ( ( model ) => {
284
+ allModels . push ( {
285
+ id : model . name . split ( "/" ) . pop ( ) ,
286
+ name : model . displayName ,
287
+ contextWindow : model . inputTokenLimit ,
288
+ experimental : true ,
289
+ } ) ;
290
+ } ) ;
291
+ } )
292
+ . catch ( ( e ) => {
293
+ console . error ( `Gemini:getGeminiModelsV1beta` , e . message ) ;
294
+ return ;
295
+ } ) ;
296
+ } catch ( e ) {
297
+ console . error ( `Gemini:getGeminiModelsV1beta` , e . message ) ;
298
+ }
299
+
300
+ if ( allModels . length === 0 ) {
301
+ console . error ( `Gemini:getGeminiModels - No models found` ) ;
302
+ return defaultGeminiModels ;
244
303
}
245
- return models ;
304
+
305
+ console . log (
306
+ `\x1b[32m[GeminiLLM]\x1b[0m Writing cached models API response to disk.`
307
+ ) ;
308
+ if ( ! fs . existsSync ( cacheFolder ) )
309
+ fs . mkdirSync ( cacheFolder , { recursive : true } ) ;
310
+ fs . writeFileSync (
311
+ path . resolve ( cacheFolder , "models.json" ) ,
312
+ JSON . stringify ( allModels )
313
+ ) ;
314
+ fs . writeFileSync (
315
+ path . resolve ( cacheFolder , ".cached_at" ) ,
316
+ new Date ( ) . getTime ( ) . toString ( )
317
+ ) ;
318
+
319
+ return allModels ;
246
320
}
247
321
248
322
/**
0 commit comments