Skip to content

Commit

Permalink
all-model vanishing message support
Browse files Browse the repository at this point in the history
1. gptel--trim-prefixes returns nil whenever a string is empty after
trimming (for blank strings or prompt-only strings, this will occur)

2. all models check for a non-nil after-trimming string before appending
messages to their parts

3. all models trim the whole buffer using simple string-trim when response
tracking is off

4. zero-length multi-part vectors are also skipped

Squashed: Trim prefixes to nil

Also fixes the edge case where a prefix includes whitespace.  Whitespace removal
is added to each literal prefix, so the literals need to be trimmed of whitespace.

* gptel-anthropic.el:
* gptel-gemini.el:
* gptel-ollama.el:
* gptel-openai.el:
* gptel.el:
  • Loading branch information
psionic-k committed Mar 1, 2025
1 parent 3c74df1 commit cc18cc4
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 72 deletions.
38 changes: 19 additions & 19 deletions gptel-anthropic.el
Original file line number Diff line number Diff line change
Expand Up @@ -286,27 +286,27 @@ TOOL-USE is a list of plists containing tool names, arguments and call results."
;; XXX update for tools
(pcase (get-char-property (point) 'gptel)
('response
(push (list :role "assistant"
:content (buffer-substring-no-properties (point) prev-pt))
prompts))
(when-let* ((content
(gptel--trim-prefixes
(buffer-substring-no-properties (point) prev-pt))))
(when (not (string-blank-p content))
(push (list :role "assistant" :content content) prompts))))
('nil ; user role: possibly with media
(if include-media
(push (list :role "user"
:content
(gptel--anthropic-parse-multipart
(gptel--parse-media-links major-mode (point) prev-pt)))
prompts)
(push (list :role "user"
:content
(gptel--trim-prefixes
(buffer-substring-no-properties (point) prev-pt)))
prompts)))))
(if include-media
(when-let* ((content (gptel--anthropic-parse-multipart
(gptel--parse-media-links major-mode (point) prev-pt))))
(when (> (length content) 0)
(push (list :role "user" :content content) prompts)))
(when-let* ((content (gptel--trim-prefixes
(buffer-substring-no-properties (point) prev-pt))))
(push (list :role "user" :content content) prompts))))))
(setq prev-pt (point))
(and max-entries (cl-decf max-entries)))
(push (list :role "user"
:content
(string-trim (buffer-substring-no-properties (point-min) (point-max))))
prompts))
(when-let* ((content (string-trim (buffer-substring-no-properties
(point-min) (point-max)))))
;; XXX fails if content is empty. The correct error behavior is left to
;; a future discussion.
(push (list :role "user" :content content) prompts)))
prompts))

(defun gptel--anthropic-parse-multipart (parts)
Expand All @@ -329,7 +329,7 @@ format."
for media = (plist-get part :media)
if text do
(and (or (= n 1) (= n last)) (setq text (gptel--trim-prefixes text))) and
unless (string-empty-p text)
if text
collect `(:type "text" :text ,text) into parts-array end
else if media
do
Expand Down
33 changes: 15 additions & 18 deletions gptel-gemini.el
Original file line number Diff line number Diff line change
Expand Up @@ -236,27 +236,24 @@ See generic implementation for full documentation."
(not (= (point) prev-pt)))
(pcase (get-char-property (point) 'gptel)
('response
(push (list :role "model"
:parts
(list :text (buffer-substring-no-properties (point) prev-pt)))
prompts))
(when-let* ((content (gptel--trim-prefixes
(buffer-substring-no-properties (point) prev-pt))))
(push (list :role "model" :parts (list :text content)) prompts)))
('nil
(if include-media
(push (list :role "user"
:parts (gptel--gemini-parse-multipart
(gptel--parse-media-links major-mode (point) prev-pt)))
prompts)
(push (list :role "user"
:parts
`[(:text ,(gptel--trim-prefixes
(buffer-substring-no-properties (point) prev-pt)))])
prompts))))
(when-let* ((content (gptel--gemini-parse-multipart
(gptel--parse-media-links major-mode (point) prev-pt))))
(when (> (length content) 0)
(push (list :role "user" :parts content) prompts)))
(when-let* ((content (gptel--trim-prefixes
(buffer-substring-no-properties
(point) prev-pt))))
(push (list :role "user" :parts `[(:text ,content)]) prompts)))))
(setq prev-pt (point))
(and max-entries (cl-decf max-entries)))
(push (list :role "user"
:parts
`[(:text ,(string-trim (buffer-substring-no-properties (point-min) (point-max))))])
prompts))
(let ((content (string-trim (buffer-substring-no-properties
(point-min) (point-max)))))
(push (list :role "user" :parts `[(:text ,content)]) prompts)))
prompts))

(defun gptel--gemini-parse-multipart (parts)
Expand All @@ -277,7 +274,7 @@ format."
for media = (plist-get part :media)
if text do
(and (or (= n 1) (= n last)) (setq text (gptel--trim-prefixes text))) and
unless (string-empty-p text)
if text
collect (list :text text) into parts-array end
else if media
collect
Expand Down
31 changes: 14 additions & 17 deletions gptel-ollama.el
Original file line number Diff line number Diff line change
Expand Up @@ -152,26 +152,23 @@ Store response metadata in state INFO."
(not (= (point) prev-pt)))
(pcase (get-char-property (point) 'gptel)
('response
(push (list :role "assistant"
:content (buffer-substring-no-properties (point) prev-pt))
prompts))
(when-let* ((content (gptel--trim-prefixes
(buffer-substring-no-properties (point) prev-pt))))
(push (list :role "assistant" :content content) prompts)))
('nil
(if include-media
(push (append '(:role "user")
(gptel--ollama-parse-multipart
(gptel--parse-media-links major-mode (point) prev-pt)))
prompts)
(push (list :role "user"
:content
(gptel--trim-prefixes
(buffer-substring-no-properties (point) prev-pt)))
prompts))))
(when-let* ((content (gptel--ollama-parse-multipart
(gptel--parse-media-links major-mode (point) prev-pt))))
(when (> (length content) 0)
(push (append '(:role "user") content) prompts)))
(when-let* ((content (gptel--trim-prefixes (buffer-substring-no-properties
(point) prev-pt))))
(push (list :role "user" :content content) prompts)))))
(setq prev-pt (point))
(and max-entries (cl-decf max-entries)))
(push (list :role "user"
:content
(string-trim (buffer-substring-no-properties (point-min) (point-max))))
prompts))
(let ((content (string-trim (buffer-substring-no-properties
(point-min) (point-max)))))
(push (list :role "user" :content content) prompts)))
prompts))

(defun gptel--ollama-parse-multipart (parts)
Expand All @@ -192,7 +189,7 @@ format."
for media = (plist-get part :media)
if text do
(and (or (= n 1) (= n last)) (setq text (gptel--trim-prefixes text))) and
unless (string-empty-p text)
if text
collect text into text-array end
else if media
collect (gptel--base64-encode media) into media-array end
Expand Down
23 changes: 11 additions & 12 deletions gptel-openai.el
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ Mutate state INFO with response metadata."
(push (list :role "assistant" :content content) prompts)))
(`(tool . ,id)
(save-excursion
(condition-case-unless-debug _err
(condition-case _err
(let* ((tool-call (read (current-buffer)))
(id (gptel--openai-format-tool-id id))
(name (plist-get tool-call :name))
Expand All @@ -371,17 +371,16 @@ Mutate state INFO with response metadata."
(and max-entries (cl-decf max-entries))
(if include-media
(when-let* ((content (gptel--openai-parse-multipart
(gptel--parse-media-links major-mode (point) prev-pt))))
(push (list :role "user" :content content) prompts))
(when-let* ((content (gptel--trim-prefixes
(buffer-substring-no-properties (point)
prev-pt)))
(content (when (not (string-empty-p content)) content)))
(gptel--parse-media-links major-mode
(point) prev-pt))))
(when (> (length content) 0)
(push (list :role "user" :content content) prompts)))
(when-let* ((content (gptel--trim-prefixes (buffer-substring-no-properties
(point) prev-pt))))
(push (list :role "user" :content content) prompts)))))
(setq prev-pt (point)))
(when-let* ((content (gptel--trim-prefixes (buffer-substring-no-properties
(point-min) (point-max))))
(content (when (not (string-empty-p content)) content)))
(let ((content (string-trim (buffer-substring-no-properties
(point-min) (point-max)))))
(push (list :role "user" :content content) prompts)))
prompts))

Expand All @@ -403,8 +402,8 @@ format."
for text = (plist-get part :text)
for media = (plist-get part :media)
if text do
(and (or (= n 1) (= n last)) (setq text (gptel--trim-prefixes text))) and
unless (string-empty-p text)
(and (or (= n 1) (= n last)) (setq text (gptel--trim-prefixes text)))
and if text
collect `(:type "text" :text ,text) into parts-array end
else if media
collect
Expand Down
17 changes: 11 additions & 6 deletions gptel.el
Original file line number Diff line number Diff line change
Expand Up @@ -912,12 +912,17 @@ Note: This will move the cursor."
(or (alist-get major-mode gptel-response-prefix-alist) ""))

(defsubst gptel--trim-prefixes (s)
"Remove prompt/response prefixes from string S."
(string-trim s
(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)))))
"Remove prompt/response prefixes from string S.
Return nil if string collapses to empty string."
(let* ((prompt-regex (format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
(regexp-quote
(string-trim (gptel-prompt-prefix-string)))))
(response-regex (format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*"
(regexp-quote
(string-trim (gptel-response-prefix-string)))))
(trimmed (string-trim s prompt-regex response-regex)))
(unless (string-empty-p trimmed)
trimmed)))

(defsubst gptel--link-standalone-p (beg end)
"Return non-nil if positions BEG and END are isolated.
Expand Down

0 comments on commit cc18cc4

Please sign in to comment.