Skip to content

Commit 7e59428

Browse files
committed
fix: add an interactive fn to load google models
1 parent f052960 commit 7e59428

File tree

1 file changed

+137
-86
lines changed

1 file changed

+137
-86
lines changed

chatgpt-shell-google.el

Lines changed: 137 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,11 @@ VALIDATE-COMMAND, and GROUNDING-SEARCH handler."
8383
(:key . chatgpt-shell-google-key)
8484
(:validate-command . chatgpt-shell-google--validate-command)))
8585

86-
8786
(defun chatgpt-shell--google-current-generative-model-p (model)
8887
"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"
9091
(let ((description (gethash "description" model))
9192
(supported-methods
9293
(gethash "supportedGenerationMethods" model)))
@@ -102,7 +103,6 @@ generativelanguage.googleapis.com"
102103
(goto-char (if (boundp 'url-http-end-of-headers)
103104
url-http-end-of-headers
104105
(error "`url-http-end-of-headers' marker is not defined")))
105-
;;(forward-line 2)
106106
(let ((json-object-type 'hash-table)
107107
(json-array-type 'list)
108108
(json-key-type 'string))
@@ -132,37 +132,86 @@ the model description needed by chatgpt-shell ."
132132
:token-width 4
133133
:context-window model-cwindow)))))
134134

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+
135190
(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)))
166215

167216
(defun chatgpt-shell-google--validate-command (_command _model _settings)
168217
"Return error string if command/setup isn't valid."
@@ -244,7 +293,9 @@ or
244293
(when prompt
245294
(list (cons prompt nil))))))))
246295
(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)) . ())))))
248299
`((generation_config . ((temperature . ,(or (map-elt settings :temperature) 1))
249300
;; 1 is most diverse output.
250301
(topP . 1))))))
@@ -287,58 +338,58 @@ For example:
287338
.choices "")))))
288339
response
289340
(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\nError: 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\nError: 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)
342393
(list (cons :filtered nil)
343394
(cons :pending raw-response)))))
344395

0 commit comments

Comments
 (0)