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)))

Reply via email to