@@ -83,10 +83,11 @@ VALIDATE-COMMAND, and GROUNDING-SEARCH handler."
83
83
(:key . chatgpt-shell-google-key)
84
84
(:validate-command . chatgpt-shell-google--validate-command)))
85
85
86
-
87
86
(defun chatgpt-shell--google-current-generative-model-p (model )
88
87
" a predicate that looks at a model description returned from Google and
89
- returns non-nil if the model is current and supports \" generateContent\" ."
88
+ returns non-nil if the model is current and supports \" generateContent\" .
89
+ This is used to filter the list of models returned from
90
+ https://generativelanguage.googleapis.com"
90
91
(let ((description (gethash " description" model))
91
92
(supported-methods
92
93
(gethash " supportedGenerationMethods" model)))
@@ -102,7 +103,6 @@ generativelanguage.googleapis.com"
102
103
(goto-char (if (boundp 'url-http-end-of-headers )
103
104
url-http-end-of-headers
104
105
(error " `url-http-end-of-headers' marker is not defined" )))
105
- ; ;(forward-line 2)
106
106
(let ((json-object-type 'hash-table )
107
107
(json-array-type 'list )
108
108
(json-key-type 'string ))
@@ -132,37 +132,86 @@ the model description needed by chatgpt-shell ."
132
132
:token-width 4
133
133
:context-window model-cwindow)))))
134
134
135
+ (cl-defun chatgpt-shell-google-load-models (&key override )
136
+ " Query Google for the list of Gemini LLM models available (see
137
+ https://ai.google.dev/gemini-api/docs/models/gemini) and add them to
138
+ `chatgpt-shell-models' unless a model with the same name is already
139
+ present. By default, replace the existing Google models in
140
+ `chatgpt-shell-models' with the newly retrieved models. When OVERRIDE is
141
+ non-nil (interactively with a prefix argument), replace all the Google
142
+ models with those retrieved."
143
+ (interactive (list :override current-prefix-arg))
144
+ (let* ((goog-predicate (lambda (model )
145
+ (string= (map-elt model :provider ) " Google" )))
146
+ (goog-index (or (cl-position-if goog-predicate chatgpt-shell-models)
147
+ (length chatgpt-shell-models))))
148
+ (setq chatgpt-shell-models (and (not override)
149
+ (cl-remove-if goog-predicate chatgpt-shell-models)))
150
+ (let* ((existing-gemini-models (mapcar (lambda (model )
151
+ (map-elt model :version ))
152
+ (cl-remove-if-not goog-predicate
153
+ chatgpt-shell-models)))
154
+ (new-gemini-models
155
+ (mapcar #'chatgpt-shell--google-convert-model (chatgpt-shell--google-get-generative-models))))
156
+ (setq chatgpt-shell-models
157
+ (append (seq-take chatgpt-shell-models goog-index)
158
+ new-gemini-models
159
+ (seq-drop chatgpt-shell-models goog-index)))
160
+ (message " Added %d Gemini model(s); kept %d existing Gemini model(s) "
161
+ (length new-gemini-models)
162
+ (length existing-gemini-models)))))
163
+
164
+ (defun chatgpt-shell-google-toggle-grounding ()
165
+ " Toggles the :grounding for the currently-selected model. Google's
166
+ documentation states that All Gemini 1.5 and 2.0 models support
167
+ grounding, some of the experimental or short-lived models do not. If
168
+ chatgpt-shell tries to use a model that does nto support grounding, the
169
+ API returns an error. In that case, the user can toggle grounding on the
170
+ model, using this function."
171
+ (interactive )
172
+ (let ((current-model (chatgpt-shell--resolved-model)))
173
+ (when (and current-model
174
+ (string= (map-elt current-model :provider ) " Google" ))
175
+ (let ((current-grounding-cons
176
+ (assq :grounding-search current-model)))
177
+ (when current-grounding
178
+ (setf (cdr current-grounding-cons) (not (cdr current-grounding-cons))))))))
179
+
180
+ (defun chatgpt-shell-google-get-grounding-tool-keyword ()
181
+ " retrieves the keyword for the grounding tool. This gets set
182
+ once for each model, based on a heuristic."
183
+ (let ((current-model (chatgpt-shell--resolved-model)))
184
+ (when (and current-model
185
+ (string= (map-elt current-model :provider ) " Google" ))
186
+ (save-match-data
187
+ (let ((version (map-elt current-model :version )))
188
+ (if (string-match " 1\\ .5" version) " google_search_retrieval" " google_search" ))))))
189
+
135
190
(defun chatgpt-shell-google-models ()
136
- " Dynamically build a list of Google LLM models available. See
137
- https://ai.google.dev/gemini-api/docs/models/gemini. "
138
- (mapcar #'chatgpt-shell--google-convert-model (chatgpt-shell--google-get-generative-models)))
139
-
140
-
141
- ; ; (defun chatgpt-shell-google-models ()
142
- ; ; "Build a list of Google LLM models available."
143
- ; ; ;; Context windows have been verified as of 11/26/2024. See
144
- ; ; ;; https://ai.google.dev/gemini-api/docs/models/gemini.
145
- ; ; (list (chatgpt-shell-google-make-model :version "gemini-2.0-flash"
146
- ; ; :short-version "2.0-flash"
147
- ; ; :path "/v1beta/models/gemini-2.0-flash"
148
- ; ; :grounding-search t
149
- ; ; :token-width 4
150
- ; ; :context-window 1048576)
151
- ; ; (chatgpt-shell-google-make-model :version "gemini-1.5-pro-latest"
152
- ; ; :short-version "1.5-pro-latest"
153
- ; ; :path "/v1beta/models/gemini-1.5-pro-latest"
154
- ; ; :token-width 4
155
- ; ; :context-window 2097152)
156
- ; ; (chatgpt-shell-google-make-model :version "gemini-1.5-flash-latest"
157
- ; ; :short-version "1.5-flash-latest"
158
- ; ; :path "/v1beta/models/gemini-1.5-flash-latest"
159
- ; ; :token-width 4
160
- ; ; :context-window 1048576)
161
- ; ; (chatgpt-shell-google-make-model :version "gemini-2.0-flash-thinking-exp-01-21"
162
- ; ; :short-version "2.0-flash-thinking-exp"
163
- ; ; :path "/v1beta/models/gemini-2.0-flash-thinking-exp-01-21"
164
- ; ; :token-width 4
165
- ; ; :context-window 32767)))
191
+ " Build a list of Google LLM models available."
192
+ ; ; Context windows have been verified as of 11/26/2024. See
193
+ ; ; https://ai.google.dev/gemini-api/docs/models/gemini.
194
+ (list (chatgpt-shell-google-make-model :version " gemini-2.0-flash"
195
+ :short-version " 2.0-flash"
196
+ :path " /v1beta/models/gemini-2.0-flash"
197
+ :grounding-search t
198
+ :token-width 4
199
+ :context-window 1048576 )
200
+ (chatgpt-shell-google-make-model :version " gemini-1.5-pro-latest"
201
+ :short-version " 1.5-pro-latest"
202
+ :path " /v1beta/models/gemini-1.5-pro-latest"
203
+ :token-width 4
204
+ :context-window 2097152 )
205
+ (chatgpt-shell-google-make-model :version " gemini-1.5-flash-latest"
206
+ :short-version " 1.5-flash-latest"
207
+ :path " /v1beta/models/gemini-1.5-flash-latest"
208
+ :token-width 4
209
+ :context-window 1048576 )
210
+ (chatgpt-shell-google-make-model :version " gemini-2.0-flash-thinking-exp-01-21"
211
+ :short-version " 2.0-flash-thinking-exp"
212
+ :path " /v1beta/models/gemini-2.0-flash-thinking-exp-01-21"
213
+ :token-width 4
214
+ :context-window 32767 )))
166
215
167
216
(defun chatgpt-shell-google--validate-command (_command _model _settings )
168
217
" Return error string if command/setup isn't valid."
244
293
(when prompt
245
294
(list (cons prompt nil ))))))))
246
295
(when (map-elt model :grounding-search )
247
- '((tools . ((google_search . ())))))
296
+ ; ; Google's docs say that grounding is supported for all Gemini 1.5 and 2.0 models.
297
+ ; ; But the API is slightly different between them. This uses the correct tool name.
298
+ `((tools . ((,(intern (chatgpt-shell-google-get-grounding-tool-keyword)) . ())))))
248
299
`((generation_config . ((temperature . ,(or (map-elt settings :temperature ) 1 ))
249
300
; ; 1 is most diverse output.
250
301
(topP . 1 ))))))
@@ -287,58 +338,58 @@ For example:
287
338
.choices " " )))))
288
339
response
289
340
(if-let ((chunks (shell-maker--split-text raw-response)))
290
- (let ((response)
291
- (pending)
292
- (result))
293
- (mapc (lambda (chunk )
294
- ; ; Response chunks come in the form:
295
- ; ; data: {...}
296
- ; ; data: {...}
297
- (if-let* ((is-data (equal (map-elt chunk :key ) " data:" ))
298
- (obj (shell-maker--json-parse-string (map-elt chunk :value )))
299
- (text (let-alist obj
300
- (or (let-alist (seq-first .candidates)
301
- (cond ((seq-first .content.parts)
302
- (let-alist (seq-first .content.parts)
303
- .text))
304
- ((equal .finishReason " RECITATION" )
305
- " " )
306
- ((equal .finishReason " STOP" )
307
- " " )
308
- ((equal .finishReason " CANCELLED" )
309
- " Error: Request cancellled." )
310
- ((equal .finishReason " CRASHED" )
311
- " Error: An error occurred. Try again." )
312
- ((equal .finishReason " END_OF_PROMPT" )
313
- " Error: Couldn't generate a response. Try rephrasing." )
314
- ((equal .finishReason " LENGTH" )
315
- " Error: Response is too big. Try rephrasing." )
316
- ((equal .finishReason " TIME" )
317
- " Error: Timed out." )
318
- ((equal .finishReason " SAFETY" )
319
- " Error: Flagged for safety." )
320
- ((equal .finishReason " LANGUAGE" )
321
- " Error: Flagged for language." )
322
- ((equal .finishReason " BLOCKLIST" )
323
- " Error: Flagged for forbidden terms." )
324
- ((equal .finishReason " PROHIBITED_CONTENT" )
325
- " Error: Flagged for prohibited content." )
326
- ((equal .finishReason " SPII" )
327
- " Error: Flagged for sensitive personally identifiable information." )
328
- (.finishReason
329
- (format " \n\n Error: Something's up (%s )" .finishReason))))
330
- .error.message))))
331
- (unless (string-empty-p text)
332
- (setq response (concat response text)))
333
- (setq pending (concat pending
334
- (or (map-elt chunk :key ) " " )
335
- (map-elt chunk :value )))))
336
- chunks)
337
- (setq result
338
- (list (cons :filtered (unless (string-empty-p response)
339
- response))
340
- (cons :pending pending)))
341
- result)
341
+ (let ((response)
342
+ (pending)
343
+ (result))
344
+ (mapc (lambda (chunk )
345
+ ; ; Response chunks come in the form:
346
+ ; ; data: {...}
347
+ ; ; data: {...}
348
+ (if-let* ((is-data (equal (map-elt chunk :key ) " data:" ))
349
+ (obj (shell-maker--json-parse-string (map-elt chunk :value )))
350
+ (text (let-alist obj
351
+ (or (let-alist (seq-first .candidates)
352
+ (cond ((seq-first .content.parts)
353
+ (let-alist (seq-first .content.parts)
354
+ .text))
355
+ ((equal .finishReason " RECITATION" )
356
+ " " )
357
+ ((equal .finishReason " STOP" )
358
+ " " )
359
+ ((equal .finishReason " CANCELLED" )
360
+ " Error: Request cancellled." )
361
+ ((equal .finishReason " CRASHED" )
362
+ " Error: An error occurred. Try again." )
363
+ ((equal .finishReason " END_OF_PROMPT" )
364
+ " Error: Couldn't generate a response. Try rephrasing." )
365
+ ((equal .finishReason " LENGTH" )
366
+ " Error: Response is too big. Try rephrasing." )
367
+ ((equal .finishReason " TIME" )
368
+ " Error: Timed out." )
369
+ ((equal .finishReason " SAFETY" )
370
+ " Error: Flagged for safety." )
371
+ ((equal .finishReason " LANGUAGE" )
372
+ " Error: Flagged for language." )
373
+ ((equal .finishReason " BLOCKLIST" )
374
+ " Error: Flagged for forbidden terms." )
375
+ ((equal .finishReason " PROHIBITED_CONTENT" )
376
+ " Error: Flagged for prohibited content." )
377
+ ((equal .finishReason " SPII" )
378
+ " Error: Flagged for sensitive personally identifiable information." )
379
+ (.finishReason
380
+ (format " \n\n Error: Something's up (%s )" .finishReason))))
381
+ .error.message))))
382
+ (unless (string-empty-p text)
383
+ (setq response (concat response text)))
384
+ (setq pending (concat pending
385
+ (or (map-elt chunk :key ) " " )
386
+ (map-elt chunk :value )))))
387
+ chunks)
388
+ (setq result
389
+ (list (cons :filtered (unless (string-empty-p response)
390
+ response))
391
+ (cons :pending pending)))
392
+ result)
342
393
(list (cons :filtered nil )
343
394
(cons :pending raw-response)))))
344
395
0 commit comments