branch: elpa/gptel commit 00f39ea179630cd9a15430d0edc2d584a139ed31 Author: Psionik K <73710933+psioni...@users.noreply.github.com> Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
gptel: Ignore blank/whitespace messages - Support across APIs for blank messages in the messages array varies. Uniformly enforce a policy of not including blank/whitespace messages in the messages array. - When response tracking is off, All models trim the whole buffer using simple string-trim. - Zero-length multi-part vectors are also skipped. - Whitespace inside a prefix is respected. That is, a prefix containing whitespace at either end will not be recognized as one if this whitespace is missing in the buffer. * gptel.el (gptel--trim-prefixes): `gptel--trim-prefixes' now returns nil whenever a string is empty after trimming (this will occur for blank strings or prompt-only strings). * gptel-anthropic.el: (gptel--parse-buffer, gptel--anthropic-parse-multipart): Omit empty strings when constructing the messages array. * gptel-gemini.el (gptel--parse-buffer, gptel--gemini-parse-multipart): Ditto. * gptel-ollama.el (gptel--parse-buffer, gptel--ollama-parse-multipart): Ditto. * gptel-openai.el (gptel--parse-buffer, gptel--openai-parse-multipart): Ditto. --- gptel-anthropic.el | 38 +++++++++++++++++++------------------- gptel-gemini.el | 33 +++++++++++++++------------------ gptel-ollama.el | 31 ++++++++++++++----------------- gptel-openai.el | 25 ++++++++++++------------- gptel.el | 19 +++++++++++++------ 5 files changed, 73 insertions(+), 73 deletions(-) diff --git a/gptel-anthropic.el b/gptel-anthropic.el index 60a07cbe81..c97cbf5bc2 100644 --- a/gptel-anthropic.el +++ b/gptel-anthropic.el @@ -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) @@ -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 diff --git a/gptel-gemini.el b/gptel-gemini.el index 4018b3f021..e6311b6662 100644 --- a/gptel-gemini.el +++ b/gptel-gemini.el @@ -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) @@ -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 diff --git a/gptel-ollama.el b/gptel-ollama.el index 9061786dc1..cb62108b12 100644 --- a/gptel-ollama.el +++ b/gptel-ollama.el @@ -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) @@ -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 diff --git a/gptel-openai.el b/gptel-openai.el index bb9311c894..ae9b206d68 100644 --- a/gptel-openai.el +++ b/gptel-openai.el @@ -371,18 +371,17 @@ 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)))) - (unless (string-empty-p content) - (push (list :role "user" :content content) prompts)))))) + (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))))) - (unless (string-empty-p content) - (push (list :role "user" :content content) prompts)))) + (let ((content (string-trim (buffer-substring-no-properties + (point-min) (point-max))))) + (push (list :role "user" :content content) prompts))) prompts)) ;; TODO This could be a generic function @@ -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 diff --git a/gptel.el b/gptel.el index a6727a2fe9..bf90a0f91c 100644 --- a/gptel.el +++ b/gptel.el @@ -913,12 +913,19 @@ 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* ((trimmed (string-trim-left + s (format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*" + (regexp-quote + (gptel-prompt-prefix-string))))) + (trimmed (string-trim-right + trimmed (format "[\t\r\n ]*\\(?:%s\\)?[\t\r\n ]*" + (regexp-quote + (gptel-response-prefix-string)))))) + (unless (string-empty-p trimmed) + trimmed))) (defsubst gptel--link-standalone-p (beg end) "Return non-nil if positions BEG and END are isolated.