config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

gptel-privategpt.el (6830B)


      1 ;;; gptel-privategpt.el ---  Privategpt AI suppport for gptel  -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2023  Karthik Chikmagalur
      4 
      5 ;; Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
      6 
      7 ;; This program is free software; you can redistribute it and/or modify
      8 ;; it under the terms of the GNU General Public License as published by
      9 ;; the Free Software Foundation, either version 3 of the License, or
     10 ;; (at your option) any later version.
     11 
     12 ;; This program is distributed in the hope that it will be useful,
     13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 ;; GNU General Public License for more details.
     16 
     17 ;; You should have received a copy of the GNU General Public License
     18 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     19 
     20 ;;; Commentary:
     21 
     22 ;; This file adds support for Privategpt's Messages API to gptel
     23 
     24 ;;; Code:
     25 (require 'cl-generic)
     26 (eval-when-compile
     27   (require 'cl-lib))
     28 (require 'map)
     29 (require 'gptel)
     30 
     31 (defvar json-object-type)
     32 
     33 (declare-function prop-match-value "text-property-search")
     34 (declare-function text-property-search-backward "text-property-search")
     35 (declare-function json-read "json" ())
     36 
     37 
     38 
     39 ;;; Privategpt (Messages API)
     40 (cl-defstruct (gptel-privategpt (:constructor gptel--make-privategpt)
     41                                (:copier nil)
     42                                (:include gptel-openai))
     43   context sources)
     44 
     45 (defun gptel--privategpt-parse-sources (response)
     46   (cl-loop with source-alist
     47            for source across (map-nested-elt response '(:choices 0 :sources))
     48            for name = (map-nested-elt source '(:document :doc_metadata :file_name))
     49            for page = (map-nested-elt source '(:document :doc_metadata :page_label))
     50            do (push page (alist-get name source-alist nil nil #'equal))
     51            finally return
     52            (cl-loop for (file-name . file-pages) in source-alist
     53                     for pages = (delete-dups (delq nil file-pages))
     54                     if pages
     55                     collect (format "- %s (page %s)" file-name (mapconcat #'identity pages ", "))
     56                     into source-items
     57                     else collect (format "- %s" file-name) into source-items
     58                     finally return (mapconcat #'identity (cons "\n\nSources:" source-items) "\n"))))
     59 
     60 (cl-defmethod gptel-curl--parse-stream ((_backend gptel-privategpt) info)
     61   (let* ((content-strs))
     62     (condition-case nil
     63         (while (re-search-forward "^data:" nil t)
     64           (save-match-data
     65             (if (looking-at " *\\[DONE\\]")
     66                 (when-let ((sources-string (plist-get info :sources)))
     67                   (push sources-string content-strs))
     68               (let ((response (gptel--json-read)))
     69 		(unless (or (plist-get info :sources)
     70                             (not (gptel-privategpt-sources (plist-get info :backend))))
     71                   (plist-put info :sources (gptel--privategpt-parse-sources response)))
     72 		(let* ((delta (map-nested-elt response '(:choices 0 :delta)))
     73 		       (content (plist-get delta :content)))
     74 		  (push content content-strs))))))
     75     (error
     76      (goto-char (match-beginning 0))))
     77   (apply #'concat (nreverse content-strs))))
     78 
     79 (cl-defmethod gptel--parse-response ((_backend gptel-privategpt) response info)
     80   (let ((response-string (map-nested-elt response '(:choices 0 :message :content)))
     81         (sources-string (and (gptel-privategpt-sources (plist-get info :backend))
     82                              (gptel--privategpt-parse-sources response))))
     83     (concat response-string sources-string)))
     84 
     85 (cl-defmethod gptel--request-data ((_backend gptel-privategpt) prompts)
     86   "JSON encode PROMPTS for sending to ChatGPT."
     87   (let ((prompts-plist
     88          `(:model ,(gptel--model-name gptel-model)
     89 	   :messages [,@prompts]
     90 	   :use_context ,(or (gptel-privategpt-context gptel-backend) :json-false)
     91 	   :include_sources ,(or (gptel-privategpt-sources gptel-backend) :json-false)
     92            :stream ,(or (and gptel-stream gptel-use-curl
     93                          (gptel-backend-stream gptel-backend))
     94                      :json-false))))
     95     (when gptel-temperature
     96       (plist-put prompts-plist :temperature gptel-temperature))
     97     (when gptel-max-tokens
     98       (plist-put prompts-plist :max_tokens gptel-max-tokens))
     99     ;; Merge request params with model and backend params.
    100     (gptel--merge-plists
    101      prompts-plist
    102      (gptel-backend-request-params gptel-backend)
    103      (gptel--model-request-params  gptel-model))))
    104 
    105 
    106 ;;;###autoload
    107 (cl-defun gptel-make-privategpt
    108     (name &key curl-args stream key request-params
    109           (header
    110            (lambda () (when-let (key (gptel--get-api-key))
    111 			`(("Authorization" . ,(concat "Bearer " key))))))
    112           (host "localhost:8001")
    113           (protocol "http")
    114 	  (models '(private-gpt))
    115           (endpoint "/v1/chat/completions")
    116           (context t) (sources t))
    117   "Register an Privategpt API-compatible backend for gptel with NAME.
    118 
    119 Keyword arguments:
    120 
    121 CURL-ARGS (optional) is a list of additional Curl arguments.
    122 
    123 HOST (optional) is the API host, \"api.privategpt.com\" by default.
    124 
    125 MODELS is a list of available model names.
    126 
    127 STREAM is a boolean to toggle streaming responses, defaults to
    128 false.
    129 
    130 PROTOCOL (optional) specifies the protocol, https by default.
    131 
    132 ENDPOINT (optional) is the API endpoint for completions, defaults to
    133 \"/v1/messages\".
    134 
    135 HEADER (optional) is for additional headers to send with each
    136 request. It should be an alist or a function that retuns an
    137 alist, like:
    138 ((\"Content-Type\" . \"application/json\"))
    139 
    140 KEY is a variable whose value is the API key, or function that
    141 returns the key.
    142 
    143 CONTEXT and SOURCES: if true (the default), use available context
    144 and provide sources used by the model to generate the response.
    145 
    146 REQUEST-PARAMS (optional) is a plist of additional HTTP request
    147 parameters (as plist keys) and values supported by the API.  Use
    148 these to set parameters that gptel does not provide user options
    149 for."
    150   (declare (indent 1))
    151   (let ((backend (gptel--make-privategpt
    152                   :curl-args curl-args
    153                   :name name
    154                   :host host
    155                   :header header
    156                   :key key
    157                   :models models
    158                   :protocol protocol
    159                   :endpoint endpoint
    160                   :stream stream
    161                   :request-params request-params
    162                   :url (if protocol
    163                            (concat protocol "://" host endpoint)
    164                          (concat host endpoint))
    165                   :context context
    166                   :sources sources)))
    167     (prog1 backend
    168       (setf (alist-get name gptel--known-backends
    169                        nil nil #'equal)
    170                   backend))))
    171 
    172 (provide 'gptel-privategpt)
    173 ;;; gptel-backends.el ends here