branch: elpa/gptel
commit cb420b69287c0df69672ea916e318685b4a312ce
Author: Psionik K <73710933+psioni...@users.noreply.github.com>
Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>

    gptel: Breaking change to gptel-request API
    
    * gptel.el (gptel-request, gptel--handle-tool-use,
    gptel--insert-response): Change gptel-request's API for handling
    tool use again.  When there are pending tool calls or tool
    results, the callback is now called cons cells of the form
    
     (tool-call   . ((tool args callback) ...))
     (tool-result . ((tool args result) ...))
    
    respectively.  The idea is to support dispatching on more types of
    responses (like "reasoning" text blocks) by disambiguating between
    them explicitly via symbols like `tool-call' and `tool-result'.
    The gptel-request callback can then match against the type of
    symbol to define custom behavior.
    
     (pcase response
       ((pred stringp) ...)                  ;regular response
       (`(,tool-call . call-specs) ...)
       (`(,tool-result . result-specs) ...))
    
    The case of a regular response string remains simple.  See
    `gptel--insert-response' for an example of this kind of dispatch.
    
    Update tool handling (in the fsm) for the new API.
    
    * gptel-transient.el (gptel--suffix-send): Update custom callbacks
    to the new API.
    
    * gptel-curl.el (gptel-curl--stream-insert-response): Update
    `gptel-curl--stream-insert-response' to the new API.
---
 gptel-curl.el      |  8 ++++----
 gptel-transient.el | 36 ++++++++++++++++++------------------
 gptel.el           | 49 ++++++++++++++++++++++++++++++-------------------
 3 files changed, 52 insertions(+), 41 deletions(-)

diff --git a/gptel-curl.el b/gptel-curl.el
index 7972e6e68e..61c3173eba 100644
--- a/gptel-curl.el
+++ b/gptel-curl.el
@@ -257,10 +257,10 @@ See `gptel--url-get-response' for details."
            ;; (run-hooks 'gptel-pre-stream-hook)
            (insert response)
            (run-hooks 'gptel-post-stream-hook)))))
-    ((pred consp)
-     (gptel--display-tool-calls response info))
-    (_                                  ; placeholder for later condition
-     (gptel--display-tool-results response info))))
+    (`(tool-call . ,tool-calls)
+     (gptel--display-tool-calls tool-calls info))
+    (`(tool-result . ,tool-results)
+     (gptel--display-tool-results tool-results info))))
 
 (defun gptel-curl--stream-filter (process output)
   (let* ((fsm (alist-get process gptel--request-alist))
diff --git a/gptel-transient.el b/gptel-transient.el
index db89c86abe..aecda6f6af 100644
--- a/gptel-transient.el
+++ b/gptel-transient.el
@@ -1227,27 +1227,27 @@ This sets the variable `gptel-include-tool-results', 
which see."
      ((member "e" args)
       (setq stream nil)
       (setq callback
-            (lambda (resp info)
-              (cond
-               ((stringp resp) (message "%s response: %s" backend-name resp))
-               ;; XXX Check types
-               ((consp resp) (gptel--display-tool-calls resp info 'minibuffer))
-               ((and (null resp) (plist-get info :error))
-                (message "%s response error: %s"
-                         backend-name (plist-get info :status)))))))
+            (lambda (resp info &optional _raw)
+              (pcase resp
+                ((pred stringp) (message "%s response: %s" backend-name resp))
+                (`(tool-call . ,tool-calls) (gptel--display-tool-calls 
tool-calls info 'minibuffer))
+                (`(tool-result . ,tool-results) (gptel--display-tool-results 
tool-results info))
+                (_ (when (and (null resp) (plist-get info :error))
+                     (message "%s response error: %s"
+                              backend-name (plist-get info :status))))))))
      ((member "k" args)
       (setq stream nil)
       (setq callback
-            (lambda (resp info)
-              (cond
-               ((stringp resp) (kill-new resp)
-                (message "%s response: \"%s\" copied to kill-ring." 
backend-name
-                         (truncate-string-to-width resp 30)))
-               ;; XXX Check types
-               ((consp resp) (gptel--display-tool-calls resp info 'minibuffer))
-               ((and (null resp) (plist-get info :error))
-                (message "%s response error: %s" backend-name
-                         (plist-get info :status)))))))
+            (lambda (resp info &optional _raw)
+              (pcase resp
+                ((pred stringp) (kill-new resp)
+                 (message "%s response: \"%s\" copied to kill-ring." 
backend-name
+                          (truncate-string-to-width resp 30)))
+                (`(tool-call . ,tool-calls) (gptel--display-tool-calls 
tool-calls info 'minibuffer))
+                (`(tool-result . ,tool-results) (gptel--display-tool-results 
tool-results info))
+                (_ (when (and (null resp) (plist-get info :error))
+                     (message "%s response error: %s" backend-name
+                              (plist-get info :status))))))))
      ((setq gptel-buffer-name
             (cl-some (lambda (s) (and (stringp s) (string-prefix-p "g" s)
                                  (substring s 1)))
diff --git a/gptel.el b/gptel.el
index f76a8cd810..eaecbc6e4b 100644
--- a/gptel.el
+++ b/gptel.el
@@ -1947,26 +1947,30 @@ Run post-response hooks."
     (let ((result-alist) (pending-calls))
       (mapc                             ; Construct function calls
        (lambda (tool-call)
-         (letrec ((name (plist-get tool-call :name))
-                  (args (plist-get tool-call :args))
+         (letrec ((args (plist-get tool-call :args))
+                  (name (plist-get tool-call :name))
                   (arg-values)
+                  (tool-spec
+                   (cl-find-if
+                    (lambda (ts) (equal (gptel-tool-name ts) name))
+                    (plist-get info :tools)))
                   (process-tool-result
                    (lambda (result)
                      (plist-put info :tool-success t)
-                     (plist-put tool-call :result (gptel--to-string result))
-                     (push (list name arg-values result) result-alist)
+                     (let ((result (gptel--to-string result)))
+                       (plist-put tool-call :result result)
+                       (push (list tool-spec args result) result-alist))
                      (cl-incf tool-idx)
                      (when (>= tool-idx ntools) ; All tools have run
                        (gptel--inject-prompt
                         backend (plist-get info :data)
                         (gptel--parse-tool-results
                          backend (plist-get info :tool-use)))
-                       (funcall (plist-get info :callback) result-alist info)
+                       (funcall (plist-get info :callback)
+                                (cons 'tool-result result-alist) info)
                        (gptel--fsm-transition fsm)))))
-           (when-let* ((tool-spec
-                        (cl-find-if
-                         (lambda (ts) (equal (gptel-tool-name ts) name))
-                         (plist-get info :tools))))
+           (if (null tool-spec)
+               (message "Unknown tool called by model: %s" name)
              (setq arg-values
                    (mapcar
                     (lambda (arg)
@@ -1975,7 +1979,7 @@ Run post-response hooks."
                     (gptel-tool-args tool-spec)))
              ;; Check if tool requires confirmation
              (if (and gptel-confirm-tool-calls (or (eq 
gptel-confirm-tool-calls t)
-                                                 (gptel-tool-confirm 
tool-spec)))
+                                                   (gptel-tool-confirm 
tool-spec)))
                  (push (list tool-spec arg-values process-tool-result)
                        pending-calls)
                ;; If not, run the tool
@@ -1993,7 +1997,8 @@ Run post-response hooks."
           (setq gptel--fsm-last fsm)
           (when gptel-mode (gptel--update-status
                             (format " Run tools?" ) 'mode-line-emphasis)))
-        (funcall (plist-get info :callback) pending-calls info)))))
+        (funcall (plist-get info :callback)
+                 (cons 'tool-call pending-calls) info)))))
 
 ;;;; State machine predicates
 ;; Predicates used to find the next state to transition to, see
@@ -2047,10 +2052,16 @@ or streaming responses (see STREAM).  In these cases, 
RESPONSE
 can be
 
 - The symbol `abort' if the request is aborted, see `gptel-abort'.
-- An alist of tool names, arguments and tool call results after the LLM
-  runs a tool: ((name arguments result) ...)
-- An alist of `gptel-tool' structs and tool call arguments if tools
-  require confirmation to run: ((tool arguments tool-call-func) ...)
+
+- A list beginning with `tool-call'.  The cdr form is (TOOL ARGS
+  CALLBACK) ...) where TOOL is a gptel-tool struct, ARGS is a plist of
+  arguments, and CALLBACK is a function for handling the results.
+
+- A list beginning with `tool-result'.  The cdr form is ((TOOL ARGS
+  RESULT) ...) where TOOL is a gptel-tool struct, ARGS is a plist of
+  arguments, and RESULT was returned from calling the tool function.
+
+See `gptel--insert-response' for an example callback handling all cases.
 
 STREAM is a boolean that determines if the response should be
 streamed, as in `gptel-stream'.  If the model or the backend does
@@ -2359,10 +2370,10 @@ See `gptel--url-get-response' for details."
              (plist-put info :tracking-marker (setq tracking-marker 
(point-marker)))
              ;; for uniformity with streaming responses
              (set-marker-insertion-type tracking-marker t)))))
-      ((pred consp)
-       (gptel--display-tool-calls response info))
-      (_
-       (gptel--display-tool-results response info)))))
+      (`(tool-call . ,tool-calls)
+       (gptel--display-tool-calls tool-calls info))
+      (`(tool-result . ,tool-results)
+       (gptel--display-tool-results tool-results info)))))
 
 (defun gptel--create-prompt (&optional prompt-end)
   "Return a full conversation prompt from the contents of this buffer.

Reply via email to