2023-12-20 11:12:40 +00:00
|
|
|
;;; gptel-gemini.el --- Gemini suppport for gptel -*- lexical-binding: t; -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2023 Karthik Chikmagalur
|
|
|
|
|
|
|
|
;; Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
|
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;; This file adds support for the Gemini API to gptel
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
(require 'gptel)
|
|
|
|
(require 'cl-generic)
|
2023-12-20 13:51:01 -08:00
|
|
|
(require 'map)
|
2023-12-29 13:19:23 -08:00
|
|
|
(eval-when-compile (require 'cl-lib))
|
2023-12-20 11:12:40 +00:00
|
|
|
|
2023-12-20 14:23:45 -08:00
|
|
|
(declare-function prop-match-value "text-property-search")
|
|
|
|
(declare-function text-property-search-backward "text-property-search")
|
|
|
|
(declare-function json-read "json")
|
|
|
|
|
2023-12-20 11:12:40 +00:00
|
|
|
;;; Gemini
|
|
|
|
(cl-defstruct
|
|
|
|
(gptel-gemini (:constructor gptel--make-gemini)
|
|
|
|
(:copier nil)
|
|
|
|
(:include gptel-backend)))
|
|
|
|
|
2023-12-20 13:51:01 -08:00
|
|
|
(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))))
|
|
|
|
|
2023-12-20 11:12:40 +00:00
|
|
|
(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
|
2024-02-07 19:00:26 -08:00
|
|
|
`(:contents [,@prompts]
|
|
|
|
:safetySettings [(:category "HARM_CATEGORY_HARASSMENT"
|
|
|
|
:threshold "BLOCK_NONE")
|
|
|
|
(:category "HARM_CATEGORY_SEXUALLY_EXPLICIT"
|
|
|
|
:threshold "BLOCK_NONE")
|
|
|
|
(:category "HARM_CATEGORY_DANGEROUS_CONTENT"
|
|
|
|
:threshold "BLOCK_NONE")
|
|
|
|
(:category "HARM_CATEGORY_HATE_SPEECH"
|
|
|
|
:threshold "BLOCK_NONE")]))
|
2023-12-20 13:51:01 -08:00
|
|
|
params)
|
|
|
|
(when gptel-temperature
|
|
|
|
(setq params
|
|
|
|
(plist-put params
|
2023-12-20 14:23:45 -08:00
|
|
|
:temperature (max gptel-temperature 1.0))))
|
2023-12-20 13:51:01 -08:00
|
|
|
(when gptel-max-tokens
|
|
|
|
(setq params
|
|
|
|
(plist-put params
|
|
|
|
:maxOutputTokens gptel-max-tokens)))
|
|
|
|
(when params
|
|
|
|
(plist-put prompts-plist
|
|
|
|
:generationConfig params))
|
2023-12-20 11:12:40 +00:00
|
|
|
prompts-plist))
|
|
|
|
|
|
|
|
(cl-defmethod gptel--parse-buffer ((_backend gptel-gemini) &optional max-entries)
|
|
|
|
(let ((prompts) (prop))
|
|
|
|
(while (and
|
|
|
|
(or (not max-entries) (>= max-entries 0))
|
|
|
|
(setq prop (text-property-search-backward
|
|
|
|
'gptel 'response
|
|
|
|
(when (get-char-property (max (point-min) (1- (point)))
|
|
|
|
'gptel)
|
|
|
|
t))))
|
|
|
|
(push (list :role (if (prop-match-value prop) "model" "user")
|
|
|
|
:parts
|
|
|
|
(list :text (string-trim
|
|
|
|
(buffer-substring-no-properties (prop-match-beginning prop)
|
|
|
|
(prop-match-end prop))
|
2023-12-20 13:51:01 -08:00
|
|
|
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
|
|
|
(regexp-quote (gptel-prompt-prefix-string)))
|
|
|
|
(format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
|
2023-12-20 14:23:45 -08:00
|
|
|
(regexp-quote (gptel-response-prefix-string))))))
|
2023-12-20 11:12:40 +00:00
|
|
|
prompts)
|
|
|
|
(and max-entries (cl-decf max-entries)))
|
2023-12-27 22:35:41 -08:00
|
|
|
(cl-callf (lambda (msg) (concat gptel--system-message "\n\n" msg))
|
|
|
|
(thread-first (car prompts)
|
|
|
|
(plist-get :parts)
|
|
|
|
(plist-get :text)))
|
2023-12-20 11:12:40 +00:00
|
|
|
prompts))
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(cl-defun gptel-make-gemini
|
2024-02-21 00:21:46 +01:00
|
|
|
(name &key curl-args header key (stream nil)
|
2023-12-20 11:12:40 +00:00
|
|
|
(host "generativelanguage.googleapis.com")
|
|
|
|
(protocol "https")
|
2023-12-20 13:51:01 -08:00
|
|
|
(models '("gemini-pro"))
|
2024-03-10 21:02:42 -07:00
|
|
|
(endpoint "/v1beta/models"))
|
2023-12-20 11:12:40 +00:00
|
|
|
|
|
|
|
"Register a Gemini backend for gptel with NAME.
|
|
|
|
|
|
|
|
Keyword arguments:
|
|
|
|
|
2024-02-21 00:21:46 +01:00
|
|
|
CURL-ARGS (optional) is a list of additional Curl arguments.
|
|
|
|
|
2023-12-20 13:51:01 -08:00
|
|
|
HOST (optional) is the API host, defaults to
|
|
|
|
\"generativelanguage.googleapis.com\".
|
2023-12-20 11:12:40 +00:00
|
|
|
|
2024-03-10 21:02:42 -07:00
|
|
|
MODELS is a list of available model names.
|
2023-12-20 11:12:40 +00:00
|
|
|
|
2023-12-22 16:16:31 -08:00
|
|
|
STREAM is a boolean to enable streaming responses, defaults to
|
2023-12-20 11:12:40 +00:00
|
|
|
false.
|
|
|
|
|
2023-12-20 13:51:01 -08:00
|
|
|
PROTOCOL (optional) specifies the protocol, \"https\" by default.
|
2023-12-20 11:12:40 +00:00
|
|
|
|
|
|
|
ENDPOINT (optional) is the API endpoint for completions, defaults to
|
2024-03-10 21:02:42 -07:00
|
|
|
\"/v1beta/models\".
|
2023-12-20 11:12:40 +00:00
|
|
|
|
|
|
|
HEADER (optional) is for additional headers to send with each
|
|
|
|
request. It should be an alist or a function that retuns an
|
|
|
|
alist, like:
|
|
|
|
((\"Content-Type\" . \"application/json\"))
|
|
|
|
|
|
|
|
KEY (optional) is a variable whose value is the API key, or
|
|
|
|
function that returns the key."
|
2024-01-19 14:19:22 -08:00
|
|
|
(declare (indent 1))
|
2023-12-20 11:12:40 +00:00
|
|
|
(let ((backend (gptel--make-gemini
|
2024-02-21 00:21:46 +01:00
|
|
|
:curl-args curl-args
|
2023-12-20 11:12:40 +00:00
|
|
|
:name name
|
|
|
|
:host host
|
|
|
|
:header header
|
|
|
|
:models models
|
|
|
|
:protocol protocol
|
|
|
|
:endpoint endpoint
|
2023-12-20 13:51:01 -08:00
|
|
|
:stream stream
|
2023-12-22 16:25:32 -08:00
|
|
|
:key key
|
2024-03-10 21:02:42 -07:00
|
|
|
:url (lambda ()
|
|
|
|
(let ((method (if (and stream
|
|
|
|
gptel-stream)
|
|
|
|
"streamGenerateContent"
|
|
|
|
"generateContent")))
|
|
|
|
(format "%s://%s%s/%s:%s?key=%s"
|
|
|
|
protocol
|
|
|
|
host
|
|
|
|
endpoint
|
|
|
|
gptel-model
|
|
|
|
method
|
|
|
|
(gptel--get-api-key)))))))
|
2023-12-20 11:12:40 +00:00
|
|
|
(prog1 backend
|
|
|
|
(setf (alist-get name gptel--known-backends
|
|
|
|
nil nil #'equal)
|
|
|
|
backend))))
|
|
|
|
|
|
|
|
(provide 'gptel-gemini)
|
|
|
|
;;; gptel-gemini.el ends here
|