Skip to content

Non-async tools are evaluated in reverse order #1489

Description

@LeonAmp9

Please update gptel first -- errors are often fixed by the time they're reported.

  • I have updated gptel to the latest commit and tested that the issue still exists

Bug Description

First of all, great tool, thank you. Lots of potential. Now the issue.

One would think that non-async tools will be evaluated in the order the LLM requests them. But they are not.

This leads to catastrophic results if the tools mutate something, be it Emacs state or filesystem. For me, it was when I had a tool for "dump particular text into a new emacs buffer with particular name" and a tool for "show a particular buffer to the user" (for presentation of time-consuming tasks). The LLM would correctly sequence the 2 tool calls in one "turn" but the second call would consistently fail with buffer allegedly not existing. I was quite confused because the buffer was indeed created and the arguments used by the LLM were indeed correct. And the second tool would consistently succeed if called in a second conversation "turn".

After making a minimum viable test case (see the test case section), the underlying cause (see the title of this report) was clear.

Is this behavior intended? I thought, maybe it's an intended limitation because the LLM can mix async and sync tools and it would be difficult to guarantee an ordering, so it's just evaluated in a YOLO manner. But I find it weird that the calls are not merely out of order, they are so reliably and in a particular way - always exactly reverse. I couldn't find any mention of this surprising behavior in the manual.

I don't have the elisp skills necessary to understand this codebase to see why this is happening.

Backend

Other (please specify in Additional Context)

Steps to Reproduce

Tools:

(defvar counter 0)

(gptel-make-tool
 :name "tool-x"
 :description "Use this when the user requests tool-x"
 :category "core"
 :async nil
 :function
 (lambda ()
   (setq counter (+ counter 1))
   (message "X %0.6f %d" (float-time) counter)))

(gptel-make-tool
 :name "tool-y"
 :description "Use this when the user requests tool-y."
 :category "core"
 :async nil
 :function
 (lambda ()
   (setq counter (+ counter 1))
   (message "Y %0.6f %d" (float-time) counter)))

(gptel-make-tool
 :name "tool-z"
 :description "Use this when the user requests tool-z."
 :category "core"
 :async nil
 :function
 (lambda ()
   (setq counter (+ counter 1))
   (message "Z %0.6f %d" (float-time) counter)))

LLM chat:

### use tool-x, tool-y and tool-z


``` tool (tool-x nil)
(:name "tool-x" :args nil)

X 1784533301.935472 3
```
``` tool (tool-y nil)
(:name "tool-y" :args nil)

Y 1784533301.935415 2
```
``` tool (tool-z nil)
(:name "tool-z" :args nil)

Z 1784533301.935281 1
```

As can be seen from both the timestamps and the counter variable printout, the tools are requested by the LLM in order X-Y-Z but are evaluated as Z-Y-X.

Additional Context

Emacs: 30.2
OS: NixOS unstable
gptel: commit 8701e2b, per gptel-pkg.el
backend: llama.cpp with qwen3

Backtrace

Log Information

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions