karthink / gptel

A simple LLM client for Emacs
GNU General Public License v3.0
1.63k stars 151 forks source link

(Claude) Request fails with "all messages must have non-empty content" #409

Closed agzam closed 1 month ago

agzam commented 1 month ago

I set gptel-default-mode to org-mode and at first, it seemed that it's worked, but it sometimes can fail.

What I did:

Claude error: (HTTP/2 400) messages.2: all messages must have non-empty content except for the optional final assistant message
karthink commented 1 month ago

Ah yeah, I'm aware of this issue. It's unrelated to Org mode. See prior discussions in #406, #321, #351, #343.

There is no easy fix for this yet. Rather, there are several easy ways to fix it but all of them create new problems. Here are three independent things you can do to avoid the error (i.e., any one of them will work, don't do them all):

  1. Don't modify Claude's response in any way, either using gptel-post-request-functions or manually. (Obviously this is not an acceptable compromise.)
  2. Switch to the feature-overlays branch of gptel.
  3. Run
    (setf (alist-get 'gptel 'text-property-default-nonsticky nil 'remove) nil)

Each of these causes other problems, but it's possible you may not encounter any of them.

Eventually I need to find a solution that works with minimal compromises.

agzam commented 1 month ago

It's unrelated to Org mode

For some reason I don't see it (at least not as often) when using Markdown mode.

wlauppe commented 1 month ago

when i looked at the code after a recent "invalid_request_error" I realized, that my buffer had adjacent GPTEL_BOUNDS shoudnt it just merge: :GPTEL_BOUNDS: ((359 . 835) (836 . 2720) (2721 . 3388)) to :GPTEL_BOUNDS: ((359 . 2720) (2721 . 3388)) and then there would be no empty user message? Would this be a solution?

wlauppe commented 1 month ago

I got the feeling, that gptel sometimes produces these invalid_request_error-hickups even if you dont edit the response. For debugging purposes i wrote some functions, which visually show the gptel-bounds:

;; gptel chat coloring 
(defvar gptel-bounds-overlay nil
  "Variable to store the overlay for GPTEL_BOUNDS.")

(defun show-and-highlight-gptel-bounds ()
  "Display the first occurrence of GPTEL_BOUNDS property in the file and highlight the bounds."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (let ((bounds-str (org-entry-get (point) "GPTEL_BOUNDS")))
      (if bounds-str
          (let ((bounds (read bounds-str)))
            (message "GPTEL_BOUNDS: %s" bounds-str)
            (highlight-bounds bounds))
        (message "No GPTEL_BOUNDS property found in the file.")))))

(defun highlight-bounds (bounds-list)
  "Highlight the regions specified by BOUNDS-LIST."
  (dolist (range bounds-list)
    (let ((start (car range))
          (end (cdr range)))
      (when (and (numberp start) (numberp end) (<= start end))
        (let ((overlay (make-overlay start end)))
          (overlay-put overlay 'face '(:background "light sky blue"))
          (overlay-put (make-overlay start (1+ start)) 'face '(:background "steel blue"))
          (overlay-put (make-overlay (1- end) end) 'face '(:background "steel blue"))
          (push overlay gptel-bounds-overlay))))))

(defun toggle-highlight-gptel-bounds ()
  "Toggle the highlighting of GPTEL_BOUNDS."
  (interactive)
  (if gptel-bounds-overlay
      (progn
        (mapc 'delete-overlay gptel-bounds-overlay)
        (setq gptel-bounds-overlay nil)
        (message "GPTEL_BOUNDS highlighting removed."))
    (save-excursion
      (goto-char (point-min))
      (let ((bounds-str (org-entry-get (point) "GPTEL_BOUNDS")))
        (if bounds-str
            (let ((bounds (read bounds-str)))
              (highlight-bounds bounds)
              (message "GPTEL_BOUNDS highlighting enabled."))
          (message "No GPTEL_BOUNDS property found in the file."))))))

(global-set-key (kbd "C-c h") 'toggle-highlight-gptel-bounds)

if you execute this code you can toggle highlighting of system messages, and get a clearer image what is going on. assistant messages are highlighted with a blue background, with the start and end character in a darker blue. For example i had the following conversation: chat_error_annotated

you see that a system message is ending at character 971 and the next one starting right at 972. If i want to continue the chat with a second question, thats were empty user messages are created, an then claude complains.

wlauppe commented 1 month ago

thats the relevant snippet from gptel-log

(re-search-forward \":GPTEL_BOUNDS: (\" nil t)"
    },
    {
      "role": "user",
      "content": ""
    },
    {
      "role": "assistant",
      "content": "\n      (let ((bounds
karthink commented 1 month ago

@agzam, @wlauppe could you try it now? I added a clause to the logic for Anthropic where I don't include "empty" user prompts in the message.

(Even if this works, this is a stopgap solution -- as I haven't been able to find a response tracking method that is both robust and simple.)

agzam commented 1 month ago

Apologies for the delay, just wanted to make sure it's good. This is what I have in my config

(setq gptel-default-mode 'org-mode)
(setf (alist-get 'org-mode gptel-prompt-prefix-alist) "* ")

Sorry, with org-indent-mode my converstations were weirdly shifting onto the right, so I removed a few asterisks.

Been using it for a couple of days, so far no issues with the requests. Looks good. Thank you very much.

karthink commented 1 month ago

Closing this now.

@wlauppe and @agzam, feel free to reopen if you can still reproduce the error.

wlauppe commented 1 month ago

karthik, if you write an updated :GPTEL_BOUNDS: line, do you account for the changes in length? For example if it had length 10 and the new :GPTEL_BOUNDS: line has length 20. the number in the cons cell should be 10 higher, becouse GPTEL_BOUNDS is inserted at the beginning of the buffer. (359 . 835) (836 . 2720) becomes (369 . 845) (846 . 2730) do you do that?

karthink commented 1 month ago

karthik, if you write an updated :GPTEL_BOUNDS: line, do you account for the changes in length? For example if it had length 10 and the new :GPTEL_BOUNDS: line has length 20. the number in the cons cell should be 10 higher, becouse GPTEL_BOUNDS is inserted at the beginning of the buffer. (359 . 835) (836 . 2720) becomes (369 . 845) (846 . 2730) do you do that?

Fixed-point iteration: https://github.com/karthink/gptel/blob/master/gptel-org.el#L410

wlauppe commented 1 month ago

I think it is nice to have a workaround hack, to be able to work with Claude in conversation mode even if something is broken at the moment, but at the same time I think we are very close at finding the real problem, and this workaround obscures the fact that something is broken. Do you understand why the gptel--get-buffer-bounds() function. from gptel.el

(defun gptel--get-buffer-bounds ()
  "Return the gptel response boundaries in the buffer as an alist."
  (save-excursion
    (save-restriction
      (widen)
      (goto-char (point-max))
      (let ((prop) (bounds))
        (while (setq prop (text-property-search-backward
                           'gptel 'response t))
          (push (cons (prop-match-beginning prop)
                      (prop-match-end prop))
                bounds))
        bounds))))

returns, two adjacent cells of a text which has consecutively the text property ' gptel 'response? eg. :GPTEL_BOUNDS: ((359 . 2720) (2721 . 3388)) At the same time i got some spurious errors, and dont know if there are connected to the workaround hack. Thats why havent answered with 👍 I may play around with the old version for a bit, maybe I find something out.

karthink commented 1 month ago

and this workaround obscures the fact that something is broken.

If you are referring to the workaround chosen as the solution in this thread, it's a workaround, but nothing is broken. The text-properties are applied and read as intended.

:GPTEL_BOUNDS: ((359 . 2720) (2721 . 3388))

These bounds are correct, you can have one character without the gptel text-property between 2720 and 2721. Text property bounds are the "edges" between chars. Typically this character ends up being some kind of whitespace.

The real question is if we should also apply this text property to the range 2720-2721. Unfortunately making the gptel property sticky causes a bunch of other problems. There has been extensive discussion about this, see the issues linked in this comment above, and see (info "(elisp) Sticky Properties") for details on sticky text properties.

wlauppe commented 1 month ago

Text property bounds are the "edges" between chars.

i thought the above means. character 359 up to and included character 2720 is an assistant message? and character 2721 up to and included character 3388 too? This wrong?

wlauppe commented 1 month ago

thanks for pointing me to the sticky text properties documentation.

karthink commented 1 month ago

i thought the above means. character 359 up to and included character 2720 is an assistant message? and character 2721 up to and included character 3388 too? This wrong?

There is no character at 359, a character occupies the range from 359 to 360. Similarly, there is no character at 2720. So the above means that all characters between points 359 and 2720 are part of an assistant message, as are characters between points 2721 and 3388.

The single character between the points 2720 and 2721 is part of the user prompt. It's recognized as user input because it was inserted by the user, possibly by running a command like fill-region or typing in the newline character there.

daedsidog commented 2 weeks ago

Anecdotally, I never had this error.