-
Notifications
You must be signed in to change notification settings - Fork 165
Defining custom gptel commands
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.
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))))))))
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."))))))))
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
Feed the LLM the text of your chat buffer and get it to suggest a name. Prompt the user and rename the buffer/file appropriately.
This shows and advanced gptel-request
feature: basically, you construct a conversation consisting of this sequence:
system message: "I will provide a transcript of a chat with an LLM. Suggest a short and informative name for a file to store this chat in..."
user: <no message>
assistant: What is the chat content?
user: <contents of buffer here>
The system message is supplied to gptel-request
via the :system
keywrod argument. The series of exchanges is supplied as a list:
(nil ;no user message
"What is the chat content?" ;llm response, prefilled
(buffer-substring-no-properties ...)) ;user message
In the :callback
argument, we collect the response string and use it to rename the buffer or file.
Here is gptel-rename-chat
:
(defun gptel-rename-chat ()
(interactive)
(unless gptel-mode
(user-error "This command is intended to be used in gptel chat buffers."))
(let ((gptel-model 'gpt-4o-mini))
(gptel-request
(list nil ;user
"What is the chat content?" ;llm
(concat "```" (if (eq major-mode 'org-mode) "org" "markdown") "\n"
(buffer-substring-no-properties (point-min) (point-max))
"\n```")) ;user
:system
(list (format ;system message
"I will provide a transcript of a chat with an LLM. \
Suggest a short and informative name for a file to store this chat in. \
Use the following guidelines:
- be very concise, one very short sentence at most
- no spaces, use underscores if required
- return ONLY the title, no explanation or summary
- append the extension .%s"
(if (eq major-mode 'org-mode) "org" "md")))
:callback
(lambda (resp info) ;callback called with response and request info
(if (stringp resp)
(let ((buf (plist-get info :buffer)))
(when (and (buffer-live-p buf)
(y-or-n-p (format "Rename buffer %s to %s? " (buffer-name buf) resp)))
(with-current-buffer buf (rename-visited-file resp))))
(message "Error(%s): did not receive a response from the LLM."
(plist-get info :status)))))))
;; 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.