gptel: Use text-property based delimiting
gptel.el (gptel--prompt-markers, gptel-send, gptel--create-prompt, gptel--numberize): Switch from using markers to text-properties to distinguish queries from responses. The former method was very brittle. Remove `gptel--prompt-markers', add the function `gptel--create-prompt' and the variable `gptel--num-messages-to-send'. This variable limits the context of the conversation that is sent with each request.
This commit is contained in:
parent
77d1010fbc
commit
5159a773a0
1 changed files with 53 additions and 22 deletions
75
gptel.el
75
gptel.el
|
@ -57,6 +57,7 @@
|
||||||
(require 'aio)
|
(require 'aio)
|
||||||
(require 'json)
|
(require 'json)
|
||||||
(require 'map)
|
(require 'map)
|
||||||
|
(require 'text-property-search)
|
||||||
|
|
||||||
(defcustom gptel-api-key nil
|
(defcustom gptel-api-key nil
|
||||||
"An OpenAI API key (string).
|
"An OpenAI API key (string).
|
||||||
|
@ -82,7 +83,6 @@ When set to nil, it is inserted all at once.
|
||||||
:group 'gptel
|
:group 'gptel
|
||||||
:type 'boolean)
|
:type 'boolean)
|
||||||
|
|
||||||
(defvar-local gptel--prompt-markers nil)
|
|
||||||
(defvar gptel-default-session "*ChatGPT*")
|
(defvar gptel-default-session "*ChatGPT*")
|
||||||
(defvar gptel-default-mode (if (featurep 'markdown-mode)
|
(defvar gptel-default-mode (if (featurep 'markdown-mode)
|
||||||
'markdown-mode
|
'markdown-mode
|
||||||
|
@ -90,30 +90,19 @@ When set to nil, it is inserted all at once.
|
||||||
(defvar gptel-prompt-string "### ")
|
(defvar gptel-prompt-string "### ")
|
||||||
|
|
||||||
(aio-defun gptel-send ()
|
(aio-defun gptel-send ()
|
||||||
|
(defvar-local gptel--num-messages-to-send nil)
|
||||||
|
|
||||||
|
(defsubst gptel--numberize (val)
|
||||||
|
"Ensure VAL is a number."
|
||||||
|
(if (stringp val) (string-to-number val) val))
|
||||||
|
|
||||||
"Submit this prompt to ChatGPT."
|
"Submit this prompt to ChatGPT."
|
||||||
(interactive)
|
(interactive)
|
||||||
(message "Querying ChatGPT...")
|
(message "Querying ChatGPT...")
|
||||||
(unless (and gptel--prompt-markers
|
|
||||||
(equal (marker-position (car gptel--prompt-markers))
|
|
||||||
(point-max)))
|
|
||||||
(push (set-marker (make-marker) (point-max))
|
|
||||||
gptel--prompt-markers))
|
|
||||||
(setf (nth 1 header-line-format)
|
(setf (nth 1 header-line-format)
|
||||||
(propertize " Waiting..." 'face 'warning))
|
(propertize " Waiting..." 'face 'warning))
|
||||||
(let* ((gptel-buffer (current-buffer))
|
(let* ((gptel-buffer (current-buffer))
|
||||||
(full-prompt
|
(full-prompt (gptel--create-prompt))
|
||||||
(save-excursion
|
|
||||||
(goto-char (point-min))
|
|
||||||
(cl-loop with role = "user"
|
|
||||||
for (pm rm . _) on gptel--prompt-markers
|
|
||||||
collect
|
|
||||||
(list :role role
|
|
||||||
:content
|
|
||||||
(string-trim (buffer-substring-no-properties (or rm (point-min)) pm)
|
|
||||||
"[*# \t\n\r]+"))
|
|
||||||
into prompts
|
|
||||||
do (setq role (if (equal role "user") "assistant" "user"))
|
|
||||||
finally return (nreverse prompts))))
|
|
||||||
(response (aio-await
|
(response (aio-await
|
||||||
(funcall
|
(funcall
|
||||||
(if (and gptel-use-curl (require 'gptel-curl nil t))
|
(if (and gptel-use-curl (require 'gptel-curl nil t))
|
||||||
|
@ -124,6 +113,7 @@ When set to nil, it is inserted all at once.
|
||||||
(if content-str
|
(if content-str
|
||||||
(with-current-buffer gptel-buffer
|
(with-current-buffer gptel-buffer
|
||||||
(save-excursion
|
(save-excursion
|
||||||
|
(put-text-property 0 (length content-str) 'gptel 'response content-str)
|
||||||
(message "Querying ChatGPT... done.")
|
(message "Querying ChatGPT... done.")
|
||||||
(goto-char (point-max))
|
(goto-char (point-max))
|
||||||
(display-buffer (current-buffer)
|
(display-buffer (current-buffer)
|
||||||
|
@ -133,15 +123,56 @@ When set to nil, it is inserted all at once.
|
||||||
(if gptel-playback
|
(if gptel-playback
|
||||||
(gptel--playback (current-buffer) content-str (point))
|
(gptel--playback (current-buffer) content-str (point))
|
||||||
(insert content-str))
|
(insert content-str))
|
||||||
(push (set-marker (make-marker) (point))
|
|
||||||
gptel--prompt-markers)
|
|
||||||
(insert "\n\n" gptel-prompt-string)
|
(insert "\n\n" gptel-prompt-string)
|
||||||
(unless gptel-playback
|
(unless gptel-playback
|
||||||
(setf (nth 1 header-line-format)
|
(setf (nth 1 header-line-format)
|
||||||
(propertize " Ready" 'face 'success)))))
|
(propertize " Ready" 'face 'success)))))
|
||||||
(setf (nth 1 header-line-format)
|
(setf (nth 1 header-line-format)
|
||||||
(propertize (format " Response Error: %s" status-str)
|
(propertize (format " Response Error: %s" status-str)
|
||||||
'face 'error)))))
|
'face 'error))))))
|
||||||
|
|
||||||
|
(defun gptel--create-prompt ()
|
||||||
|
"Return a full conversation prompt from the contents of this buffer.
|
||||||
|
|
||||||
|
If `gptel--num-messages-to-send' is set, limit to that many
|
||||||
|
recent exchanges.
|
||||||
|
|
||||||
|
If the region is active limit the prompt to the region contents
|
||||||
|
instead."
|
||||||
|
(save-excursion
|
||||||
|
(save-restriction
|
||||||
|
(when (use-region-p)
|
||||||
|
(narrow-to-region (region-beginning) (region-end)))
|
||||||
|
(goto-char (point-max))
|
||||||
|
(let ((max-entries (and gptel--num-messages-to-send
|
||||||
|
(* 2 (gptel--numberize
|
||||||
|
gptel--num-messages-to-send))))
|
||||||
|
(prop) (prompts))
|
||||||
|
(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) "assistant" "user")
|
||||||
|
:content
|
||||||
|
(string-trim
|
||||||
|
(buffer-substring-no-properties (prop-match-beginning prop)
|
||||||
|
(prop-match-end prop))
|
||||||
|
"[*# \t\n\r]+"))
|
||||||
|
prompts)
|
||||||
|
(and max-entries (cl-decf max-entries)))
|
||||||
|
(cons (list :role "system"
|
||||||
|
:content
|
||||||
|
(concat
|
||||||
|
(when (eq major-mode 'org-mode)
|
||||||
|
(concat
|
||||||
|
"In this conversation, format your responses as in an org-mode buffer in Emacs."
|
||||||
|
" Do NOT use Markdown. I repeat, use org-mode markup and not markdown.\n"))
|
||||||
|
gptel--system-message))
|
||||||
|
prompts)))))
|
||||||
|
|
||||||
|
|
||||||
(aio-defun gptel--get-response (prompts)
|
(aio-defun gptel--get-response (prompts)
|
||||||
"Fetch response for PROMPTS from ChatGPT.
|
"Fetch response for PROMPTS from ChatGPT.
|
||||||
|
|
Loading…
Add table
Reference in a new issue