gptel-gemini: Add streaming responses, simplify configuration

* gptel-gemini.el (gptel-make-gemini, gptel-curl--parse-stream,
gptel--request-data, gptel--parse-buffer): Enable streaming for
the Gemini backend, as well as the temperature and max tokens
parameters when making requests.  Simplify the user configuration
required.

* README.org: Fix formatting errors.  Update the configuration
instructions for Gemini.

This closes #149.
This commit is contained in:
Karthik Chikmagalur 2023-12-20 13:51:01 -08:00
parent 84cd7bf5a4
commit 3dd00a7457
2 changed files with 64 additions and 21 deletions

View file

@ -202,26 +202,25 @@ You can pick this backend from the transient menu when using gptel (see Usage),
#+html: </details>
#+html: <details><summary>
**** Gemini
#+html: </summary>
Register a backend with
#+begin_src emacs-lisp
;; :key can be a function that returns the API key.
(gptel-make-gemini
"Gemini"
:key "YOUR_GEMINI_API_KEY"
:host "generativelanguage.googleapis.com"
:protocol "https"
:endpoint "/v1beta/models/gemini-pro:generateContent"
)
:stream t)
#+end_src
These are the required parameters, refer to the documentation of =gptel-make-gemini= for more. Currently stream is not functional.
These are the required parameters, refer to the documentation of =gptel-make-gemini= for more.
You can pick this backend from the transient menu when using gptel (see Usage), or set this as the default value of =gptel-backend=:
#+begin_src emacs-lisp
;; OPTIONAL configuration
(setq-default gptel-model "Gemini-Pro" ;Pick your default model
(setq-default gptel-model "gemini-pro" ;Pick your default model
gptel-backend (gptel-make-gemini "Gemini" :host ...))
#+end_src

View file

@ -25,6 +25,7 @@
;;; Code:
(require 'gptel)
(require 'cl-generic)
(require 'map)
;;; Gemini
(cl-defstruct
@ -32,14 +33,42 @@
(:copier nil)
(:include gptel-backend)))
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-gemini) _info)
(let* ((json-object-type 'plist)
(content-strs))
(condition-case nil
;; while-let is Emacs 29.1+ only
(while (prog1 (search-forward "{" nil t)
(backward-char 1))
(save-match-data
(when-let*
((response (json-read))
(text (map-nested-elt
response '(:candidates 0 :content :parts 0 :text))))
(push text content-strs))))
(error
(goto-char (match-beginning 0))))
(apply #'concat (nreverse content-strs))))
(cl-defmethod gptel--parse-response ((_backend gptel-gemini) response _info)
(map-nested-elt response '(:candidates 0 :content :parts 0 :text)))
(cl-defmethod gptel--request-data ((_backend gptel-gemini) prompts)
"JSON encode PROMPTS for sending to Gemini."
(let ((prompts-plist
`(:contents [,@prompts]
)))
`(:contents [,@prompts]))
params)
(when gptel-temperature
(setq params
(plist-put params
:temperature (max temperature 1.0))))
(when gptel-max-tokens
(setq params
(plist-put params
:maxOutputTokens gptel-max-tokens)))
(when params
(plist-put prompts-plist
:generationConfig params))
prompts-plist))
(cl-defmethod gptel--parse-buffer ((_backend gptel-gemini) &optional max-entries)
@ -56,8 +85,10 @@
(list :text (string-trim
(buffer-substring-no-properties (prop-match-beginning prop)
(prop-match-end prop))
(format "[\t\r\n ]*%s[\t\r\n ]*" (regexp-quote (gptel-prompt-prefix-string)))
(format "[\t\r\n ]*%s[\t\r\n ]*" (regexp-quote (gptel-response-prefix-string)))))
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
(regexp-quote (gptel-prompt-prefix-string)))
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
(regexp-quote (gptel-response-prefix-string)))))
)
prompts)
(and max-entries (cl-decf max-entries)))
@ -65,27 +96,30 @@
;;;###autoload
(cl-defun gptel-make-gemini
(name &key header key
(name &key header key stream
(host "generativelanguage.googleapis.com")
(protocol "https")
(models "gemini-pro")
(endpoint "/v1beta/models/gemini-pro:generateContent"))
(models '("gemini-pro"))
(endpoint "/v1beta/models/gemini-pro:"))
"Register a Gemini backend for gptel with NAME.
Keyword arguments:
HOST (optional) is the API host, typically \"generativelanguage.googleapis.com\".
HOST (optional) is the API host, defaults to
\"generativelanguage.googleapis.com\".
MODELS is a list of available model names.
MODELS is a list of available model names. Currently only
\"gemini-pro\" is available.
STREAM is a boolean to toggle streaming responses, defaults to
false.
PROTOCOL (optional) specifies the protocol, https by default.
PROTOCOL (optional) specifies the protocol, \"https\" by default.
ENDPOINT (optional) is the API endpoint for completions, defaults to
\"/v1beta/models/gemini-pro:generateContent\".
\"/v1beta/models/gemini-pro:streamGenerateContent\" if STREAM is true and
\"/v1beta/models/gemini-pro:generateContent\" otherwise.
HEADER (optional) is for additional headers to send with each
request. It should be an alist or a function that retuns an
@ -101,10 +135,20 @@ function that returns the key."
:models models
:protocol protocol
:endpoint endpoint
:stream nil
:url (if protocol
(concat protocol "://" host endpoint "?key=" key)
(concat host endpoint "?key=" key)))))
:stream stream
:url
(let ((key (cond
((functionp key) (funcall key))
((symbolp key) (symbol-value key))
((stringp key) key)
(t (user-error "gptel-make-gemini: arg :key is malformed")))))
(if protocol
(concat protocol "://" host endpoint
(if stream
"streamGenerateContent"
"generateContent")
"?key=" key)
(concat host endpoint "?key=" key))))))
(prog1 backend
(setf (alist-get name gptel--known-backends
nil nil #'equal)