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.