branch: elpa/gptel
commit 7b00e85cbb025777f4761322fbf4ea9109de917c
Author: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>

    gptel: Allow all JSON schema keys in tool arg specs (#788)
    
    Change how gptel tool argument specs are parsed into the JSON
    schema for tool args understood by LLMs.  Instead of managing each
    argument keyword (like :type, :description etc) manually, remove
    keywords used only by gptel (:name and :optional) and attach
    the provided arg spec as is into the tool and (eventually)
    its JSON specification.  This makes correctness the responsibility
    of the tool author, but gptel now supports all schema keywords,
    including `allOf', `anyOf', `default' and so on.
    
    * gptel.el (gptel--parse-tools): Make the above change.  This
    covers tool specs for OpenAI and Ollama.
    
    * gptel-gemini.el (gptel--parse-tools): Make the above change.
    
    * gptel-anthropic.el (gptel--parse-tools): Make the above change.
    
    * test: Add unit tests for `gptel--parse-tools' (all backends).
---
 gptel-anthropic.el | 32 +++++++++++++++-----------------
 gptel-gemini.el    | 24 ++++++++++++------------
 gptel.el           | 35 +++++++++++++++++------------------
 test               |  2 +-
 4 files changed, 45 insertions(+), 48 deletions(-)

diff --git a/gptel-anthropic.el b/gptel-anthropic.el
index bd5c58bb74..0584c4afc4 100644
--- a/gptel-anthropic.el
+++ b/gptel-anthropic.el
@@ -87,7 +87,7 @@ information if the stream contains it.  Not my best work, I 
know."
                     (if-let* ((signature (plist-get delta :signature)))
                         (plist-put info :signature
                                    (concat (plist-get info :signature) 
signature))))))))
-           
+
            ((looking-at "content_block_start") ;Is the following block text or 
tool-use?
             (forward-line 1) (forward-char 5)
             (when-let* ((cblock (plist-get (gptel--json-read) :content_block)))
@@ -99,7 +99,7 @@ information if the stream contains it.  Not my best work, I 
know."
                                              (plist-get info :tool-use))))
                 ("thinking" (plist-put info :reasoning (plist-get cblock 
:thinking))
                  (plist-put info :reasoning-block 'in)))))
-           
+
            ((looking-at "content_block_stop")
             (cond
              ((plist-get info :partial_json)   ;End of tool block
@@ -117,7 +117,7 @@ information if the stream contains it.  Not my best work, I 
know."
 
              ((eq (plist-get info :reasoning-block) 'in) ;End of reasoning 
block
               (plist-put info :reasoning-block t)))) ;Signal end of reasoning 
stream to filter
-           
+
            ((looking-at "message_delta")
             ;; collect stop_reason, usage_tokens and prepare tools
             (forward-line 1) (forward-char 5)
@@ -250,25 +250,23 @@ TOOLS is a list of `gptel-tool' structs, which see."
             :description (gptel-tool-description tool)
             :input_schema ;NOTE: Anthropic wants "{}" if the function takes no 
args, not null
             (list :type "object"
+                  ;; See the generic implementation for an explanation of this
+                  ;; transformation.
                   :properties
                   (cl-loop
                    for arg in (gptel-tool-args tool)
-                   for name = (plist-get arg :name)
-                   for type = (plist-get arg :type)
+                   for argspec = (copy-sequence arg)
+                   for name = (plist-get arg :name) ;handled differently
+                   for type = (plist-get arg :type) ;to add additional keys to 
objects
                    for newname = (or (and (keywordp name) name)
                                      (make-symbol (concat ":" name)))
-                   for enum = (plist-get arg :enum)
-                   append (list newname
-                                `(:type ,(plist-get arg :type)
-                                  :description ,(plist-get arg :description)
-                                  ,@(if enum (list :enum (vconcat enum)))
-                                  ,@(cond
-                                     ((equal type "object")
-                                      (list
-                                       :properties (plist-get arg :properties)
-                                       :required (or (plist-get arg :required) 
[])))
-                                     ((equal type "array")
-                                      (list :items (plist-get arg :items)))))))
+                   do                  ;ARGSPEC is ARG without unrecognized 
keys
+                   (cl-remf argspec :name)
+                   (cl-remf argspec :optional)
+                   if (equal (plist-get arg :type) "object")
+                   do (unless (plist-member argspec :required)
+                        (plist-put argspec :required []))
+                   append (list newname argspec))
                   :required
                   (vconcat
                    (delq nil (mapcar
diff --git a/gptel-gemini.el b/gptel-gemini.el
index 471a5124b3..5a8a2af3ea 100644
--- a/gptel-gemini.el
+++ b/gptel-gemini.el
@@ -157,23 +157,23 @@ TOOLS is a list of `gptel-tool' structs, which see."
     (if (not (gptel-tool-args tool))
          :null           ;NOTE: Gemini wants :null if the function takes no 
args
       (list :type "object"
+            ;; See the generic implementation for an explanation of this
+            ;; transformation.
             :properties
             (cl-loop
              for arg in (gptel-tool-args tool)
-             for name = (plist-get arg :name)
-             for type = (plist-get arg :type)
+             for argspec = (copy-sequence arg)
+             for name = (plist-get arg :name) ;handled differently
+             for type = (plist-get arg :type) ;to add additional keys to 
objects
              for newname = (or (and (keywordp name) name)
                                (make-symbol (concat ":" name)))
-             for enum = (plist-get arg :enum)
-             append (list newname
-                          `(:type ,(plist-get arg :type)
-                            :description ,(plist-get arg :description)
-                            ,@(if enum (list :enum (vconcat enum)))
-                            ,@(cond
-                               ((equal type "object")
-                                (list :parameters (plist-get arg :parameters)))
-                               ((equal type "array")
-                                (list :items (plist-get arg :items)))))))
+             do                        ;ARGSPEC is ARG without unrecognized 
keys
+             (cl-remf argspec :name)
+             (cl-remf argspec :optional)
+             if (equal (plist-get arg :type) "object")
+             do (unless (plist-member argspec :required)
+                  (plist-put argspec :required []))
+             append (list newname argspec))
             :required
             (vconcat
              (delq nil (mapcar
diff --git a/gptel.el b/gptel.el
index 62528d64bc..f32f00312b 100644
--- a/gptel.el
+++ b/gptel.el
@@ -1719,29 +1719,28 @@ implementation, used by OpenAI-compatible APIs and 
Ollama."
              (list
               :parameters
               (list :type "object"
+                    ;; gptel's tool args spec is close to the JSON schema, 
except
+                    ;; that we use (:name "argname" ...)
+                    ;; instead of  (:argname (...)), and
+                    ;; (:optional t) for each arg instead of (:required [...])
+                    ;; for all args at once.  Handle this difference by
+                    ;; modifying a copy of the gptel tool arg spec.
                     :properties
                     (cl-loop
                      for arg in (gptel-tool-args tool)
-                     for name = (plist-get arg :name)
-                     for type = (plist-get arg :type)
+                     for argspec = (copy-sequence arg)
+                     for name = (plist-get arg :name) ;handled differently
+                     for type = (plist-get arg :type) ;to add additional keys 
to objects
                      for newname = (or (and (keywordp name) name)
                                        (make-symbol (concat ":" name)))
-                     for enum = (plist-get arg :enum)
-                     append
-                     (list newname
-                           `(:type ,type
-                             :description ,(plist-get arg :description)
-                             ,@(if enum (list :enum (vconcat enum)))
-                             ,@(cond
-                                ((equal type "object")
-                                 (list :properties (plist-get arg :properties)
-                                       :required (or (plist-get arg :required)
-                                                     (vector))
-                                       :additionalProperties :json-false))
-                                ((equal type "array")
-                                 ;; TODO(tool) If the item type is an object,
-                                 ;; add :additionalProperties to it
-                                 (list :items (plist-get arg :items)))))))
+                     do                ;ARGSPEC is ARG without unrecognized 
keys
+                     (cl-remf argspec :name)
+                     (cl-remf argspec :optional)
+                     if (equal (plist-get arg :type) "object")
+                     do (unless (plist-member argspec :required)
+                          (plist-put argspec :required []))
+                     (plist-put argspec :additionalProperties :json-false)
+                     append (list newname argspec))
                     :required
                     (vconcat
                      (delq nil (mapcar
diff --git a/test b/test
index 9556080bcc..c0c937b6c5 160000
--- a/test
+++ b/test
@@ -1 +1 @@
-Subproject commit 9556080bcc8dee4bfec86cf78190892dd2493c46
+Subproject commit c0c937b6c516bc9f7fb6b5b28ae19ac8dc399cae

Reply via email to