branch: elpa/gptel commit 1a3b393a4b676c89e7d675c7d1b395c73d238755 Author: Karthik Chikmagalur <karthikchikmaga...@gmail.com> Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
gptel-anthropic: Parse tool results from buffer * gptel-anthropic.el (gptel--anthropic-format-tool-id, gptel--anthropic-unformat-tool-id, gptel-curl--parse-stream, gptel--parse-response, gptel--parse-tool-results, gptel--parse-buffer): - Unformat and format tool ids for storage, store only the UUID part of the ID, such as abcd in toolu_abcd. - Match the gptel text property values for (tool . id) - Create tool call and result messages from each recorded call Parse tool messages in the buffer. This commit covers the Anthropic backend. The strategy is to pcase on the gptel property. Since tool insertions have one distinct gptel text property, the call and response message are recovered at the same time. Introduce support for the `ignore' property in the Anthropic prompt creator. Users can make use of it to have non-conversation content intermingled with chat. --- gptel-anthropic.el | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/gptel-anthropic.el b/gptel-anthropic.el index c97cbf5bc2..7965d4ae81 100644 --- a/gptel-anthropic.el +++ b/gptel-anthropic.el @@ -126,7 +126,9 @@ information if the stream contains it. Not my best work, I know." ;; Then shape the tool-use block by adding args so we can call the functions (mapc (lambda (tool-call) (plist-put tool-call :args (plist-get tool-call :input)) - (plist-put tool-call :input nil)) + (plist-put tool-call :input nil) + (plist-put tool-call :id (gptel--anthropic-unformat-tool-id + (plist-get tool-call :id)))) tool-use))) (when-let* ((response (gptel--json-read))) (plist-put info :output-tokens @@ -170,6 +172,8 @@ Mutate state INFO with response metadata." for call = (copy-sequence call-raw) do (plist-put call :args (plist-get call :input)) (plist-put call :input nil) + (plist-put call :id (gptel--anthropic-unformat-tool-id + (plist-get call :id))) collect call into calls finally do (plist-put info :tool-use calls))) finally return @@ -250,7 +254,8 @@ TOOL-USE is a list of plists containing tool names, arguments and call results." (let* ((result (plist-get tool-call :result)) (formatted (list :type "tool_result" - :tool_use_id (plist-get tool-call :id) + :tool_use_id (gptel--anthropic-format-tool-id + (plist-get tool-call :id)) :content (if (stringp result) result (prin1-to-string result))))) (prog1 formatted @@ -261,6 +266,18 @@ TOOL-USE is a list of plists containing tool names, arguments and call results." ;; NOTE: No `gptel--inject-prompt' method required for gptel-anthropic, since ;; this is handled by its defgeneric implementation +(defun gptel--anthropic-format-tool-id (tool-id) + (if (string-prefix-p "toolu_" tool-id) + tool-id + (format "toolu_%s" tool-id))) + +(defun gptel--anthropic-unformat-tool-id (tool-id) + (or (and (string-match "toolu_\\(.+\\)" tool-id) + (match-string 1 tool-id)) + (progn + (message "Unexpected tool_call_id format: %s" tool-id) + tool-id))) + (cl-defmethod gptel--parse-list ((_backend gptel-anthropic) prompt-list) (cl-loop for text in prompt-list for role = t then (not role) @@ -268,7 +285,7 @@ TOOL-USE is a list of plists containing tool names, arguments and call results." (list :role (if role "user" "assistant") :content `[(:type "text" :text ,text)]))) -(cl-defmethod gptel--parse-buffer ((_backend gptel-anthropic) &optional max-entries) +(cl-defmethod gptel--parse-buffer ((backend gptel-anthropic) &optional max-entries) (let ((prompts) (prev-pt (point)) (include-media (and gptel-track-media (or (gptel--model-capable-p 'media) (gptel--model-capable-p 'url))))) @@ -283,7 +300,6 @@ TOOL-USE is a list of plists containing tool names, arguments and call results." ;; We check for blank prompts by skipping whitespace and comparing ;; point against the previous. (unless (save-excursion (skip-syntax-forward " ") (>= (point) prev-pt)) - ;; XXX update for tools (pcase (get-char-property (point) 'gptel) ('response (when-let* ((content @@ -291,6 +307,27 @@ TOOL-USE is a list of plists containing tool names, arguments and call results." (buffer-substring-no-properties (point) prev-pt)))) (when (not (string-blank-p content)) (push (list :role "assistant" :content content) prompts)))) + (`(tool . ,id) + (save-excursion + (condition-case nil + (let* ((tool-call (read (current-buffer))) + (id (gptel--anthropic-format-tool-id id)) + (name (plist-get tool-call :name)) + (arguments (plist-get tool-call :args))) + (plist-put tool-call :id id) + (plist-put tool-call :result + (string-trim (buffer-substring-no-properties + (point) prev-pt))) + (push (gptel--parse-tool-results backend (list tool-call)) + prompts) + (push (list :role "assistant" + :content `[( :type "tool_use" :id ,id :name ,name + :input ,arguments)]) + prompts)) + ((end-of-file invalid-read-syntax) + (message (format "Could not parse tool-call %s on line %s" + id (line-number-at-pos (point)))))))) + ('ignore) ('nil ; user role: possibly with media (if include-media (when-let* ((content (gptel--anthropic-parse-multipart