Skip to content

Defining custom gptel commands

karthink edited this page Jun 25, 2024 · 2 revisions

GPTel provides gptel-request, a lower level function, to query ChatGPT with custom behavior.

Its signature is as follows:

(gptel-request
 "my prompt"                                 ;the prompt to send to ChatGPT
 ;; The below keys are all optional
 :buffer   some-buffer-or-name              ;defaults to (current-buffer)
 :system   "Chat directive here"            ;defaults to gptel--system-message
 :position some-pt                          ;defaults to (point)
 :context  (list "any other info")          ;will be available to the callback
 :callback (lambda (response info) ...))    ;called with the response and an info plist
                                            ;defaults to inserting the response at :position

See its documentation for details.

Example 1: A one-shot prompt from the minibuffer

For example, to define a command that accepts a prompt in the minibuffer and pops up a window with the response, you could define the following:

(defvar gptel-lookup--history nil)

(defun gptel-lookup (prompt)
  (interactive (list (read-string "Ask ChatGPT: " nil gptel-lookup--history)))
  (when (string= prompt "") (user-error "A prompt is required."))
  (gptel-request
   prompt
   :callback
   (lambda (response info)
     (if (not response)
         (message "gptel-lookup failed with message: %s" (plist-get info :status))
       (with-current-buffer (get-buffer-create "*gptel-lookup*")
         (let ((inhibit-read-only t))
           (erase-buffer)
           (insert response))
         (special-mode)
         (display-buffer (current-buffer)
                         `((display-buffer-in-side-window)
                           (side . bottom)
                           (window-height . ,#'fit-window-to-buffer))))))))

Example 2: Rewrite a region

A command that asks ChatGPT to rewrite and replace the current region, sentence or line. Calling with a prefix-arg will query the user for the instructions to include with the text. (Note that gptel includes a refactoring interface, so this is purely for demonstration.)

(defun gptel-rewrite-and-replace (bounds &optional directive)
  (interactive
   (list
    (cond
     ((use-region-p) (cons (region-beginning) (region-end)))
     ((derived-mode-p 'text-mode)
      (list (bounds-of-thing-at-point 'sentence)))
     (t (cons (line-beginning-position) (line-end-position))))
    (and current-prefix-arg
         (read-string "ChatGPT Directive: "
                      "You are a prose editor. Rewrite my prompt more professionally."))))
  (gptel-request
   (buffer-substring-no-properties (car bounds) (cdr bounds)) ;the prompt
   :system (or directive "You are a prose editor. Rewrite my prompt more professionally.")
   :buffer (current-buffer)
   :context (cons (set-marker (make-marker) (car bounds))
                  (set-marker (make-marker) (cdr bounds)))
   :callback
   (lambda (response info)
     (if (not response)
         (message "ChatGPT response failed with: %s" (plist-get info :status))
       (let* ((bounds (plist-get info :context))
              (beg (car bounds))
              (end (cdr bounds))
              (buf (plist-get info :buffer)))
         (with-current-buffer buf
           (save-excursion
             (goto-char beg)
             (kill-region beg end)
             (insert response)
             (set-marker beg nil)
             (set-marker end nil)
             (message "Rewrote line. Original line saved to kill-ring."))))))))

Example 3: Quickly explain the thing or region at point

A slightly more comprehensive use of gptel-request can be found in the gptel-quick package. The included gptel-quick command shows a short summary or explanation of the word at point, or an active region, in a popup. This is useful for quickly looking up names, words, phrases, or summarizing/explaining prose or snippets of code, with minimal friction:

gptel-quick-simple.webm

And you can tweak the detail in the response on the fly:

gptel-quick-make-longer.webm

Embark actions using gptel-request

Generate a summary of a URL with Kagi

;; Create a kagi backend if you don't have one defined
(defvar gptel--kagi
  (gptel-make-kagi "Kagi" :key "YOUR_KAGI_KEY")) ;or function that returns a key

;; Function that requests kagi for a url summary and shows it in a side-window
(defun my/kagi-summarize (url)
  (let ((gptel-backend gptel--kagi)
        (gptel-model "summarize:agnes")) ;or summarize:cecil, summarize:daphne, summarize:muriel
    (gptel-request
     url
     :callback
     (lambda (response info)
       (if response
           (with-current-buffer (get-buffer-create "*Kagi Summary*")
             (let ((inhibit-read-only t))
               (erase-buffer)
               (visual-line-mode 1)
               (insert response)
               (display-buffer
                (current-buffer)
                '((display-buffer-in-side-window
                   display-buffer-at-bottom)
                  (side . bottom))))
             (special-mode 1))
         (message "gptel-request failed with message: %s"
                  (plist-get info :status)))))))

;; Make this function available to Embark
(keymap-set embark-url-map "=" #'my/kagi-summarize)

Running embark-act on a (text or video) link followed by = will pop up a summary of the link contents at the bottom of the screen.