branch: externals/llm
commit 566c630f356a46f98039e739bf9f1cb332c5c60a
Author: Andrew Hyatt <[email protected]>
Commit: GitHub <[email protected]>

    Normalize false values when tools are called with or return false (#242)
    
    All false values must be `:json`, when we serialize.
---
 NEWS.org                   |  1 +
 llm-provider-utils-test.el | 20 +++++++++++++++++++-
 llm-provider-utils.el      | 43 +++++++++++++++++++++++++------------------
 3 files changed, 45 insertions(+), 19 deletions(-)

diff --git a/NEWS.org b/NEWS.org
index 9659cfdade..f57d2af55d 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -1,5 +1,6 @@
 * Version 0.29.0
 - Check for tool use mismatches and define new errors for them
+- Normalize false values in tool args or tool call results
 * Version 0.28.5
 - Improved the tool calling docs
 - Fix for running tools in the original buffer with streaming
diff --git a/llm-provider-utils-test.el b/llm-provider-utils-test.el
index 4c6c0f3ca3..53d7aa30bc 100644
--- a/llm-provider-utils-test.el
+++ b/llm-provider-utils-test.el
@@ -68,6 +68,22 @@
   (should (equal (llm-provider-utils-convert-to-serializable '(:inner '(:a foo 
:b bar)))
                  '(:inner '(:a "foo" :b "bar")))))
 
+(ert-deftest llm-provider-utils-append-to-prompt ()
+  (let ((prompt (llm-make-chat-prompt "Prompt")))
+    (llm-provider-utils-append-to-prompt prompt '(:a 1 :b :json-false)
+                                         (list
+                                          (make-llm-chat-prompt-tool-result
+                                           :tool-name "tool"
+                                           :result :json-false)))
+    (should (equal (nth 1 (llm-chat-prompt-interactions prompt))
+                   (make-llm-chat-prompt-interaction
+                    :role 'tool-results
+                    :content "(:a 1 :b nil)"
+                    :tool-results (list
+                                   (make-llm-chat-prompt-tool-result
+                                    :tool-name "tool"
+                                    :result :false)))))))
+
 (ert-deftest llm-provider-utils-combine-to-system-prompt ()
   (let* ((interaction1 (make-llm-chat-prompt-interaction :role 'user :content 
"Hello"))
          (example1 (cons "Request 1" "Response 1"))
@@ -159,7 +175,9 @@
   (should (equal '(1 2 [t nil t])
                  (llm-provider-utils--normalize-args '(1 2 [t :false t]))))
   (should (equal '(:a 1 :b nil)
-                 (llm-provider-utils--normalize-args '(:a 1 :b :json-false)))))
+                 (llm-provider-utils--normalize-args '(:a 1 :b :json-false))))
+  (should (equal '((a . 1) (b . nil))
+                 (llm-provider-utils--normalize-args '((a . 1) (b . 
:json-false))))))
 
 (cl-defstruct llm-testing-provider (llm-standard-chat-provider) ())
 
diff --git a/llm-provider-utils.el b/llm-provider-utils.el
index c776e79bc4..c28ee0b8ef 100644
--- a/llm-provider-utils.el
+++ b/llm-provider-utils.el
@@ -1,6 +1,6 @@
 ;;; llm-provider-utils.el --- Functions to make building providers easier -*- 
lexical-binding: t; package-lint-main-file: "llm.el"; ; 
byte-compile-docstring-max-column: 200-*-
 
-;; Copyright (c) 2023-2025  Free Software Foundation, Inc.
+;; Copyright (c) 2023-2026  Free Software Foundation, Inc.
 
 ;; This program is free software; you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
@@ -741,9 +741,18 @@ ROLE will be `assistant' by default, but can be passed in 
for other roles."
                        :content (if (or (not output)
                                         (and (not (stringp output))
                                              (not tool-results)))
-                                    output
-                                  (format "%s" output))
-                       :tool-results tool-results)))))
+                                    (llm-provider-utils--normalize-args output 
:false)
+                                  (format
+                                   "%s"
+                                   (llm-provider-utils--normalize-args 
output)))
+                       :tool-results (mapcar
+                                      (lambda (r)
+                                        (setf 
(llm-chat-prompt-tool-result-result r)
+                                              
(llm-provider-utils--normalize-args
+                                               
(llm-chat-prompt-tool-result-result r)
+                                               :false))
+                                        r)
+                                      tool-results))))))
 
 (defun llm-provider-utils-process-result (provider prompt partial-result 
multi-output success-callback
                                                    error-callback)
@@ -815,22 +824,18 @@ This transforms the plist so that:
                                    value)
                          value))))
 
-(defun llm-provider-utils--normalize-args (args)
+(defun llm-provider-utils--normalize-args (args &optional false-val)
   "Normalize ARGS to a form that can be passed to the user.
 
-This will convert all :json-false and :false values to nil."
+This will convert all :json-false and :false values to FALSE-VAL."
   (cond
-   ((vectorp args) (vconcat (mapcar #'llm-provider-utils--normalize-args 
args)))
-   ((listp args) (mapcar #'llm-provider-utils--normalize-args args))
-   ((plistp args) (let (new-plist)
-                    (map-do
-                     (lambda (key value)
-                       (setq new-plist
-                             (plist-put new-plist
-                                        key
-                                        (llm-provider-utils--normalize-args 
value))))
-                     args)))
-   ((member args '(:json-false :false)) nil)
+   ((vectorp args) (vconcat (mapcar (lambda (a)
+                                      (llm-provider-utils--normalize-args a 
false-val))
+                                    args)))
+   ((consp args) (cons
+                  (llm-provider-utils--normalize-args (car args) false-val)
+                  (llm-provider-utils--normalize-args (cdr args) false-val)))
+   ((member args '(:json-false :false)) false-val)
    (t args)))
 
 (defun llm-provider-utils-execute-tool-uses (provider prompt tool-uses 
multi-output
@@ -859,7 +864,9 @@ have returned results."
     (cl-loop
      for tool-use in tool-uses do
      (let* ((name (llm-provider-utils-tool-use-name tool-use))
-            (arguments (llm-provider-utils-tool-use-args tool-use))
+            (arguments
+             (llm-provider-utils--normalize-args
+              (llm-provider-utils-tool-use-args tool-use)))
             (tool (or
                    (seq-find
                     (lambda (f) (equal name (llm-tool-name f)))

Reply via email to