2023-12-21 21:23:28 -08:00
|
|
|
;;; gptel-kagi.el --- Kagi support for gptel -*- lexical-binding: t; -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2023 Karthik Chikmagalur
|
|
|
|
|
|
|
|
;; Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
|
|
|
|
;; Keywords: hypermedia
|
|
|
|
|
|
|
|
;; 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 Kagi FastGPT LLM API to gptel
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
(require 'gptel)
|
|
|
|
(require 'cl-generic)
|
|
|
|
(eval-when-compile
|
|
|
|
(require 'cl-lib))
|
|
|
|
|
|
|
|
;;; Kagi
|
|
|
|
(cl-defstruct (gptel-kagi (:constructor gptel--make-kagi)
|
|
|
|
(:copier nil)
|
|
|
|
(:include gptel-backend)))
|
|
|
|
|
|
|
|
(cl-defmethod gptel--parse-response ((_backend gptel-kagi) response info)
|
|
|
|
(let* ((data (plist-get response :data))
|
|
|
|
(output (plist-get data :output))
|
|
|
|
(references (plist-get data :references)))
|
|
|
|
(when references
|
|
|
|
(setq references
|
|
|
|
(cl-loop with linker =
|
|
|
|
(pcase (buffer-local-value 'major-mode
|
|
|
|
(plist-get info :buffer))
|
|
|
|
('org-mode
|
|
|
|
(lambda (text url)
|
|
|
|
(format "[[%s][%s]]" url text)))
|
|
|
|
('markdown-mode
|
|
|
|
(lambda (text url)
|
|
|
|
(format "[%s](%s)" text url)))
|
|
|
|
(_ (lambda (text url)
|
|
|
|
(buttonize
|
|
|
|
text (lambda (data) (browse-url data))
|
|
|
|
url))))
|
|
|
|
for ref across references
|
|
|
|
for title = (plist-get ref :title)
|
|
|
|
for snippet = (plist-get ref :snippet)
|
|
|
|
for url = (plist-get ref :url)
|
|
|
|
for n upfrom 1
|
|
|
|
collect
|
|
|
|
(concat (format "[%d] " n)
|
|
|
|
(funcall linker title url) ": "
|
|
|
|
(replace-regexp-in-string
|
|
|
|
"</?b>" "*" snippet))
|
|
|
|
into ref-strings
|
|
|
|
finally return
|
|
|
|
(concat "\n\n" (mapconcat #'identity ref-strings "\n")))))
|
|
|
|
(concat output references)))
|
|
|
|
|
|
|
|
(cl-defmethod gptel--request-data ((_backend gptel-kagi) prompts)
|
|
|
|
"JSON encode PROMPTS for sending to ChatGPT."
|
2024-01-15 16:09:41 -08:00
|
|
|
(pcase-exhaustive gptel-model
|
|
|
|
("fastgpt"
|
|
|
|
`(,@prompts :web_search t :cache t))
|
|
|
|
((and model (guard (string-prefix-p "summarize" model)))
|
|
|
|
`(,@prompts :engine ,(substring model 10)))))
|
2023-12-21 21:23:28 -08:00
|
|
|
|
|
|
|
(cl-defmethod gptel--parse-buffer ((_backend gptel-kagi) &optional _max-entries)
|
2024-01-15 16:09:41 -08:00
|
|
|
(let ((url (or (thing-at-point 'url)
|
|
|
|
(get-text-property (point) 'shr-url)
|
|
|
|
(get-text-property (point) 'image-url)))
|
|
|
|
;; (filename (thing-at-point 'existing-filename)) ;no file upload support yet
|
2023-12-21 21:23:28 -08:00
|
|
|
(prop (text-property-search-backward
|
|
|
|
'gptel 'response
|
|
|
|
(when (get-char-property (max (point-min) (1- (point)))
|
|
|
|
'gptel)
|
|
|
|
t))))
|
2024-01-15 16:09:41 -08:00
|
|
|
(if (and url (string-prefix-p "summarize" gptel-model))
|
|
|
|
(list :url url)
|
|
|
|
(if (and (prop-match-p prop)
|
|
|
|
(prop-match-value prop))
|
|
|
|
(user-error "No user prompt found!")
|
|
|
|
(let ((prompts
|
|
|
|
(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))))))
|
|
|
|
(pcase-exhaustive gptel-model
|
|
|
|
("fastgpt"
|
|
|
|
(setq prompts (list
|
|
|
|
:query
|
|
|
|
(if (prop-match-p prop)
|
|
|
|
(concat
|
|
|
|
;; Fake a system message by including it in the prompt
|
|
|
|
gptel--system-message "\n\n" prompts)
|
|
|
|
""))))
|
|
|
|
((and model (guard (string-prefix-p "summarize" model)))
|
|
|
|
;; If the entire contents of the prompt looks like a url, send the url
|
|
|
|
;; Else send the text of the region
|
|
|
|
(setq prompts
|
|
|
|
(if-let (((prop-match-p prop))
|
|
|
|
(engine (substring model 10)))
|
|
|
|
;; It's a region of text
|
|
|
|
(list :text prompts)
|
|
|
|
""))))
|
|
|
|
prompts)))))
|
2023-12-21 21:23:28 -08:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(cl-defun gptel-make-kagi
|
|
|
|
(name &key stream key
|
|
|
|
(host "kagi.com")
|
|
|
|
(header (lambda () `(("Authorization" . ,(concat "Bot " (gptel--get-api-key))))))
|
2024-01-15 16:09:41 -08:00
|
|
|
(models '("fastgpt"
|
|
|
|
"summarize:cecil" "summarize:agnes"
|
|
|
|
"summarize:daphne" "summarize:muriel"))
|
2023-12-21 21:23:28 -08:00
|
|
|
(protocol "https")
|
2024-01-15 16:09:41 -08:00
|
|
|
(endpoint "/api/v0/"))
|
2023-12-21 21:23:28 -08:00
|
|
|
"Register a Kagi FastGPT backend for gptel with NAME.
|
|
|
|
|
|
|
|
Keyword arguments:
|
|
|
|
|
|
|
|
HOST is the Kagi host (with port), defaults to \"kagi.com\".
|
|
|
|
|
|
|
|
MODELS is a list of available Kagi models: only fastgpt is supported.
|
|
|
|
|
|
|
|
STREAM is a boolean to toggle streaming responses, defaults to
|
|
|
|
false. Kagi does not support a streaming API yet.
|
|
|
|
|
|
|
|
PROTOCOL (optional) specifies the protocol, https by default.
|
|
|
|
|
|
|
|
ENDPOINT (optional) is the API endpoint for completions, defaults to
|
|
|
|
\"/api/v0/fastgpt\".
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
-------
|
|
|
|
|
|
|
|
(gptel-make-kagi \"Kagi\" :key my-kagi-key)"
|
2024-01-19 14:19:22 -08:00
|
|
|
(declare (indent 1))
|
2023-12-21 21:23:28 -08:00
|
|
|
stream ;Silence byte-compiler
|
|
|
|
(let ((backend (gptel--make-kagi
|
|
|
|
:name name
|
|
|
|
:host host
|
|
|
|
:header header
|
|
|
|
:key key
|
|
|
|
:models models
|
|
|
|
:protocol protocol
|
|
|
|
:endpoint endpoint
|
2024-01-15 16:09:41 -08:00
|
|
|
:url
|
|
|
|
(lambda ()
|
|
|
|
(concat protocol "://" host endpoint
|
|
|
|
(if (equal gptel-model "fastgpt")
|
|
|
|
"fastgpt" "summarize"))))))
|
2023-12-21 21:23:28 -08:00
|
|
|
(prog1 backend
|
|
|
|
(setf (alist-get name gptel--known-backends
|
|
|
|
nil nil #'equal)
|
|
|
|
backend))))
|
|
|
|
|
|
|
|
(provide 'gptel-kagi)
|
|
|
|
;;; gptel-kagi.el ends here
|