1
1
; ;; chatgpt-shell-google.el --- Google-specific logic -*- lexical-binding : t -*-
2
2
3
- ; ; Copyright (C) 2023 Alvaro Ramirez
3
+ ; ; Copyright (C) 2023-2025 Alvaro Ramirez
4
4
5
5
; ; Author: Alvaro Ramirez https://xenodium.com
6
6
; ; URL: https://github.com/xenodium/chatgpt-shell
27
27
28
28
(eval-when-compile
29
29
(require 'cl-lib ))
30
+ (require 'let-alist )
30
31
(require 'shell-maker )
31
32
(require 'map )
33
+ (require 'rx )
32
34
(require 'json )
33
35
34
36
(defvar chatgpt-shell-proxy )
@@ -92,73 +94,72 @@ supports \"generateContent\".
92
94
93
95
This is used to filter the list of models returned from
94
96
https://generativelanguage.googleapis.com"
95
- (when-let* ((description (gethash " description" api-response))
96
- ((not (string-match-p (rx (or " discontinued" " deprecated" )) description)))
97
- (supported-methods (gethash " supportedGenerationMethods" api-response)))
98
- (seq-contains-p supported-methods " generateContent" )))
97
+ (let-alist api-response
98
+ (if (not (string-match-p (rx (or " discontinued" " deprecated" )) .description))
99
+ (seq-contains-p .supportedGenerationMethods " generateContent" ))))
99
100
100
101
(defun chatgpt-shell-google--fetch-model-versions ()
101
102
" Retrieves the list of generative models from the Google API."
102
- (let ((url (concat chatgpt-shell-google-api-url-base " /v1beta/models?key="
103
- (chatgpt-shell-google-key))))
104
- (with-current-buffer (url-retrieve-synchronously url)
105
- (goto-char (if (boundp 'url-http-end-of-headers )
106
- url-http-end-of-headers
107
- (error " `url-http-end-of-headers' marker is not defined" )))
108
- (let ((json-object-type 'hash-table )
109
- (json-array-type 'list )
110
- (json-key-type 'string ))
111
- (let ((parsed-response
112
- (json-read-from-string
113
- (buffer-substring-no-properties (point ) (point-max )))))
114
- (seq-filter #'chatgpt-shell-google--current-generative-model-p
115
- (gethash " models" parsed-response)))))))
116
-
117
- (defun chatgpt-shell-google--convert-model (api-response )
103
+ (if-let* ((api-key (chatgpt-shell-google-key)))
104
+ (let ((url (concat chatgpt-shell-google-api-url-base " /v1beta/models?key=" api-key)))
105
+ (with-current-buffer (url-retrieve-synchronously url)
106
+ (goto-char (if (boundp 'url-http-end-of-headers )
107
+ url-http-end-of-headers
108
+ (error " `url-http-end-of-headers' marker is not defined" )))
109
+ (if-let* ((parsed-response
110
+ (shell-maker--json-parse-string
111
+ (buffer-substring-no-properties (point ) (point-max )))))
112
+ (let-alist parsed-response
113
+ (seq-filter #'chatgpt-shell-google--current-generative-model-p .models)))))
114
+ (error " Set your Google API Key. " )))
115
+
116
+ (defun chatgpt-shell-google--parse-model (api-response )
118
117
" Convert the API-RESPONSE returned by Gemini into a
119
118
the model description needed by `chatgpt-shell' ."
120
- (let ((model-name (gethash " name" model))
121
- (model-cwindow (gethash " inputTokenLimit" model)))
122
- (let ((model-version (string-remove-prefix " models/" model-name)))
123
- (let ((model-shortversion (string-remove-prefix " gemini-" model-version))
124
- (model-urlpath (concat " /v1beta/" model-name))
125
- ; ; The model descriptor does not stipulate whether grounding is supported.
126
- ; ; So this logic just checks the name.
127
- (model-supports-grounding (or
128
- (string-prefix-p " gemini-1.5" model-version)
129
- (string-prefix-p " gemini-2.0" model-version))))
130
- (chatgpt-shell-google-make-model :version model-version
131
- :short-version model-shortversion
132
- :grounding-search model-supports-grounding
133
- :path model-urlpath
134
- :token-width 4
135
- :context-window model-cwindow)))))
119
+ (let-alist api-response
120
+ (let* ((model-version (string-remove-prefix " models/" .name))
121
+ (model-shortversion (string-remove-prefix " gemini-" model-version))
122
+ (model-urlpath (concat " /v1beta/" .name))
123
+ ; ; The api-response descriptor does not stipulate whether grounding is supported.
124
+ ; ; This logic applies a heuristic based on the model name (aka version).
125
+ (model-supports-grounding
126
+ (if (string-match-p (rx bol (or " gemini-1.5" " gemini-2.0" )) model-version) t nil )))
127
+ (chatgpt-shell-google-make-model :version model-version
128
+ :short-version model-shortversion
129
+ :grounding-search model-supports-grounding
130
+ :path model-urlpath
131
+ :token-width 4
132
+ :context-window .inputTokenLimit))))
136
133
137
134
(cl-defun chatgpt-shell-google-load-models (&key override )
138
135
" Query Google for the list of Gemini LLM models available.
139
136
140
- The data is retrieved from
141
- https://ai.google.dev/gemini-api/docs/models/gemini. This fn then the
142
- models retrieved to `chatgpt-shell-models' unless a model with the same
143
- name is already present.
137
+ By default, this package uses a static list of models as returned from
138
+ `chatgpt-shell-google-models' . But some users may want to choose from
139
+ a fresher set of available models.
140
+
141
+ This function retrieves data from
142
+ https://ai.google.dev/gemini-api/docs/models/gemini. This fn then
143
+ appends the models retrieved to the `chatgpt-shell-models' list, unless
144
+ a model with the same name is already present.
144
145
145
146
By default, replace the existing Google models in `chatgpt-shell-models'
146
- with the newly retrieved models. When OVERRIDE is non-nil (interactively
147
- with a prefix argument), replace all the Google models with those
148
- retrieved."
147
+ with the newly retrieved models. When OVERRIDE is non-nil, which
148
+ happens when the function is invoked interactively with a prefix
149
+ argument, replace all the Google models with those retrieved."
150
+
149
151
(interactive (list :override current-prefix-arg))
150
152
(let* ((goog-predicate (lambda (model )
151
153
(string= (map-elt model :provider ) " Google" )))
152
154
(goog-index (or (cl-position-if goog-predicate chatgpt-shell-models)
153
155
(length chatgpt-shell-models))))
154
156
(setq chatgpt-shell-models (and (not override)
155
157
(cl-remove-if goog-predicate chatgpt-shell-models)))
156
- (let* ((existing-gemini-models (mapcar (lambda (model )
157
- (map-elt model :version ))
158
- (cl-remove-if-not goog-predicate
159
- chatgpt-shell-models)))
158
+ (let* ((existing-gemini-models
159
+ (mapcar (lambda (model ) (map-elt model :version ))
160
+ (cl-remove-if-not goog-predicate chatgpt-shell-models)))
160
161
(new-gemini-models
161
- (mapcar #'chatgpt-shell-google--convert -model (chatgpt-shell-google--fetch-model-versions))))
162
+ (mapcar #'chatgpt-shell-google--parse -model (chatgpt-shell-google--fetch-model-versions))))
162
163
(setq chatgpt-shell-models
163
164
(append (seq-take chatgpt-shell-models goog-index)
164
165
new-gemini-models
@@ -167,12 +168,13 @@ retrieved."
167
168
(length new-gemini-models)
168
169
(length existing-gemini-models)))))
169
170
170
- (defun chatgpt-shell-google-toggle-grounding ()
171
+ (defun chatgpt-shell-google-toggle-grounding-with-google-search ()
171
172
" Toggle the `:grounding-search' boolean for the currently-selected model.
172
173
173
174
Google's documentation states that All Gemini 1.5 and 2.0 models support
174
- grounding, and `:grounding-search' will be `t' for those models. For
175
- models that support grounding, this package will include a
175
+ grounding with Google search, and `:grounding-search' will be `t' for
176
+ those models. For models that support grounding, this package will
177
+ include a
176
178
177
179
(tools .((google_search . ())))
178
180
@@ -182,29 +184,36 @@ in the request payload for 2.0+ models, or
182
184
183
185
for 1.5-era models.
184
186
185
- But some of the experimental models may not support grounding. If
186
- `chatgpt-shell' tries to send a tools parameter as above to a model that
187
- does not support grounding, the API returns an error. In that case, the
188
- user can use this function to toggle grounding on the model, so that
189
- this package does not send the tools parameter in subsequent outbound
190
- requests to that model.
187
+ But some of the experimental models of those versions may not support
188
+ grounding. If `chatgpt-shell' tries to send a tools parameter as above
189
+ to a model that does not support grounding, the API returns an error.
190
+
191
+ And in some cases users may wish to not _use_ grounding in Search, even
192
+ though it is available.
193
+
194
+ In either case, the user can invoke this function to toggle
195
+ grounding-in-google-search on the model. This package will send the
196
+ tools parameter in subsequent outbound requests to that model, when
197
+ grounding is enabled.
191
198
192
- Returns the newly toggled value of `:grounding-search' ."
199
+ Returns the new boolean value of `:grounding-search' ."
193
200
(interactive )
194
201
(when-let* ((current-model (chatgpt-shell--resolved-model))
195
202
(is-google (string= (map-elt current-model :provider ) " Google" ))
196
203
(current-grounding-cons (assq :grounding-search current-model)))
197
- (setf (cdr current-grounding-cons) (not (cdr current-grounding-cons)))))
204
+ (let ((toggled (not (cdr current-grounding-cons))))
205
+ (setf (cdr current-grounding-cons) toggled)
206
+ (message " Grounding in Google search: %s " (if toggled " ON" " OFF" ))
207
+ toggled)))
198
208
199
- (defun chatgpt-shell-google--get-grounding-tool-keyword (model )
209
+ (defun chatgpt-shell-google--get-grounding-in-search- tool-keyword (model )
200
210
" Retrieves the keyword for the grounding tool.
201
211
202
212
This gets set once for each model, based on a heuristic."
203
213
(when-let* ((current-model model)
204
214
(is-google (string= (map-elt current-model :provider ) " Google" ))
205
215
(version (map-elt current-model :version )))
206
- (save-match-data
207
- (if (string-match " 1\\ .5" version) " google_search_retrieval" " google_search" ))))
216
+ (if (string-match " 1\\ .5" version) " google_search_retrieval" " google_search" )))
208
217
209
218
(defun chatgpt-shell-google-models ()
210
219
" Build a list of Google LLM models available."
312
321
(when prompt
313
322
(list (cons prompt nil ))))))))
314
323
(when (map-elt model :grounding-search )
315
- ; ; Google's docs say that grounding is supported for all Gemini 1.5 and 2.0 models.
324
+ ; ; Grounding in Google Search is supported for both Gemini 1.5 and 2.0 models.
316
325
; ; But the API is slightly different between them. This uses the correct tool name.
317
- `((tools . ((,(intern (chatgpt-shell-google--get-grounding-tool-keyword model)) . ())))))
326
+ `((tools . ((,(intern (chatgpt-shell-google--get-grounding-in-search- tool-keyword model)) . ())))))
318
327
`((generation_config . ((temperature . ,(or (map-elt settings :temperature ) 1 ))
319
328
; ; 1 is most diverse output.
320
329
(topP . 1 ))))))
0 commit comments