branch: externals/llm
commit 16c5fa7bdc4a68f9fd02b6571ac5d206722c4f8b
Author: Andrew Hyatt <[email protected]>
Commit: GitHub <[email protected]>
Fix bug calling two async tools in parallel (#246)
The issue ws that the async tools were calling a callback that captured
only the last tool, which caused duplicate tool result IDs in the
prompt.
---
NEWS.org | 1 +
llm-provider-utils-test.el | 42 ++++++++++++++++++++++++++++++++++++++++++
llm-provider-utils.el | 2 ++
3 files changed, 45 insertions(+)
diff --git a/NEWS.org b/NEWS.org
index 6fe6137a9d..30890af8c2 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -2,6 +2,7 @@
- Check for tool use mismatches and define new errors for them
- Normalize false values in tool args or tool call results
- Add Claude Opus 4.6
+- Fix bug running two async calls in parallel
- Set Gemini default to 3.0 pro
* Version 0.28.5
- Improved the tool calling docs
diff --git a/llm-provider-utils-test.el b/llm-provider-utils-test.el
index 53d7aa30bc..f30079a379 100644
--- a/llm-provider-utils-test.el
+++ b/llm-provider-utils-test.el
@@ -184,6 +184,48 @@
(cl-defmethod llm-provider-populate-tool-uses ((provider llm-testing-provider)
prompt tool-uses))
+(cl-defmethod llm-provider-append-to-prompt ((provider llm-testing-provider)
+ prompt content
+ &optional tool-results)
+ (llm-provider-utils-append-to-prompt prompt content tool-results
+ (if tool-results
+ 'user
+ 'assistant)))
+
+(ert-deftest llm-provider-utils-execute-tool-uses--parallel-same-tool ()
+ (let ((prompt (llm-make-chat-prompt
+ ""
+ :tools (list (llm-make-tool :name "a"
+ :function (lambda (callback arg)
+ (funcall callback
(format "Result for %s" arg)))
+ :args '((:name "argument"
+ :type integer
+ :description "An
argument"))
+ :async t)))))
+ (llm-provider-utils-execute-tool-uses
+ (make-llm-testing-provider)
+ prompt
+ (list
+ (make-llm-provider-utils-tool-use
+ :id "1"
+ :name "a"
+ :args '((argument . "foo")))
+ (make-llm-provider-utils-tool-use
+ :id "2"
+ :name "a"
+ :args '((argument . "bar"))))
+ t
+ nil
+ #'ignore
+ (lambda (_ err) (error "Should not error: %s" err)))
+ (let* ((last-interaction (car (last (llm-chat-prompt-interactions
prompt))))
+ (tool-results (llm-chat-prompt-interaction-tool-results
last-interaction)))
+ (dolist (id '("1" "2"))
+ (should (seq-find (lambda (result) (equal
(llm-chat-prompt-tool-result-call-id result)
+ id))
+ tool-results))))))
+
+
(ert-deftest llm-provider-utils-execute-tool-uses--missing-tool ()
(llm-provider-utils-execute-tool-uses
(make-llm-testing-provider)
diff --git a/llm-provider-utils.el b/llm-provider-utils.el
index 3836090c14..98ff407a1d 100644
--- a/llm-provider-utils.el
+++ b/llm-provider-utils.el
@@ -879,6 +879,8 @@ have returned results."
(cl-loop
for tool-use in tool-uses do
(let* ((name (llm-provider-utils-tool-use-name tool-use))
+ ;; Need this otherwise closures will capture the loop variable and
all end up with the same value.
+ (tool-use tool-use)
(arguments
(llm-provider-utils--normalize-args
(llm-provider-utils-tool-use-args tool-use)))