gptel: Simplify response API
* gptel.el (gptel--url-parse-response, gptel--url-get-response, gptel--insert-response, gptel-send): - Use shorter keys for passing the info plist, - record errors in the info plist, - separate user messaging from the callback and more. - Make the API more functional (i.e. less imperative) This is in preparation for adding `gptel-request', an API for defining custom commands. Note: The streaming filter and callback are mostly unchanged. Streaming is not planned to be accessible via `gptel-request'. * gptel-curl.el (gptel-curl--parse-response, gptel-curl--sentinel, gptel-curl--stream-filter, gptel-curl--stream-insert-response, gptel-curl--stream-cleanup, gptel-curl-get-response): Ditto.
This commit is contained in:
parent
36051b15d5
commit
f0953d569e
2 changed files with 62 additions and 66 deletions
|
@ -67,8 +67,8 @@ PROMPTS is the data to send, TOKEN is a unique identifier."
|
||||||
|
|
||||||
INFO is a plist with the following keys:
|
INFO is a plist with the following keys:
|
||||||
- :prompt (the prompt being sent)
|
- :prompt (the prompt being sent)
|
||||||
- :gptel-buffer (the gptel buffer)
|
- :buffer (the gptel buffer)
|
||||||
- :start-marker (marker at which to insert the response).
|
- :position (marker at which to insert the response).
|
||||||
|
|
||||||
Call CALLBACK with the response and INFO afterwards. If omitted
|
Call CALLBACK with the response and INFO afterwards. If omitted
|
||||||
the response is inserted into the current buffer after point."
|
the response is inserted into the current buffer after point."
|
||||||
|
@ -89,7 +89,7 @@ the response is inserted into the current buffer after point."
|
||||||
:transformer (when (or (eq gptel-default-mode 'org-mode)
|
:transformer (when (or (eq gptel-default-mode 'org-mode)
|
||||||
(eq (buffer-local-value
|
(eq (buffer-local-value
|
||||||
'major-mode
|
'major-mode
|
||||||
(plist-get info :gptel-buffer))
|
(plist-get info :buffer))
|
||||||
'org-mode))
|
'org-mode))
|
||||||
(gptel--stream-convert-markdown->org)))
|
(gptel--stream-convert-markdown->org)))
|
||||||
info))
|
info))
|
||||||
|
@ -98,6 +98,7 @@ the response is inserted into the current buffer after point."
|
||||||
(set-process-filter process #'gptel-curl--stream-filter))
|
(set-process-filter process #'gptel-curl--stream-filter))
|
||||||
(set-process-sentinel process #'gptel-curl--sentinel)))))
|
(set-process-sentinel process #'gptel-curl--sentinel)))))
|
||||||
|
|
||||||
|
;; TODO: Separate user-messaging from this function
|
||||||
(defun gptel-curl--stream-cleanup (process status)
|
(defun gptel-curl--stream-cleanup (process status)
|
||||||
"Process sentinel for GPTel curl requests.
|
"Process sentinel for GPTel curl requests.
|
||||||
|
|
||||||
|
@ -107,11 +108,11 @@ PROCESS and STATUS are process parameters."
|
||||||
(with-current-buffer proc-buf
|
(with-current-buffer proc-buf
|
||||||
(clone-buffer "*gptel-error*" 'show)))
|
(clone-buffer "*gptel-error*" 'show)))
|
||||||
(let* ((info (alist-get process gptel-curl--process-alist))
|
(let* ((info (alist-get process gptel-curl--process-alist))
|
||||||
(gptel-buffer (plist-get info :gptel-buffer))
|
(gptel-buffer (plist-get info :buffer))
|
||||||
(tracking-marker (plist-get info :tracking-marker))
|
(tracking-marker (plist-get info :tracking-marker))
|
||||||
(start-marker (plist-get info :start-marker))
|
(start-marker (plist-get info :position))
|
||||||
(http-status (plist-get info :http-status))
|
(http-status (plist-get info :http-status))
|
||||||
(http-msg (plist-get info :http-msg)))
|
(http-msg (plist-get info :status)))
|
||||||
(if (equal http-status "200")
|
(if (equal http-status "200")
|
||||||
;; Finish handling response
|
;; Finish handling response
|
||||||
(with-current-buffer gptel-buffer
|
(with-current-buffer gptel-buffer
|
||||||
|
@ -153,13 +154,12 @@ PROCESS and STATUS are process parameters."
|
||||||
|
|
||||||
INFO is a mutable plist containing information relevant to this buffer.
|
INFO is a mutable plist containing information relevant to this buffer.
|
||||||
See `gptel--url-get-response' for details."
|
See `gptel--url-get-response' for details."
|
||||||
(let ((content-str (plist-get response :content))
|
(let ((status-str (plist-get response :status))
|
||||||
(status-str (plist-get response :status))
|
(gptel-buffer (plist-get info :buffer))
|
||||||
(gptel-buffer (plist-get info :gptel-buffer))
|
(start-marker (plist-get info :position))
|
||||||
(start-marker (plist-get info :start-marker))
|
|
||||||
(tracking-marker (plist-get info :tracking-marker))
|
(tracking-marker (plist-get info :tracking-marker))
|
||||||
(transformer (plist-get info :transformer)))
|
(transformer (plist-get info :transformer)))
|
||||||
(if content-str
|
(when response
|
||||||
(with-current-buffer gptel-buffer
|
(with-current-buffer gptel-buffer
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(unless tracking-marker
|
(unless tracking-marker
|
||||||
|
@ -171,13 +171,11 @@ See `gptel--url-get-response' for details."
|
||||||
(plist-put info :tracking-marker tracking-marker))
|
(plist-put info :tracking-marker tracking-marker))
|
||||||
|
|
||||||
(when transformer
|
(when transformer
|
||||||
(setq content-str (funcall transformer content-str)))
|
(setq response (funcall transformer response)))
|
||||||
|
|
||||||
(put-text-property 0 (length content-str) 'gptel 'response content-str)
|
(put-text-property 0 (length response) 'gptel 'response response)
|
||||||
(goto-char tracking-marker)
|
(goto-char tracking-marker)
|
||||||
(insert content-str)))
|
(insert response))))))
|
||||||
(gptel--update-header-line
|
|
||||||
(format " Response Error: %s" status-str) 'error))))
|
|
||||||
|
|
||||||
(defun gptel-curl--stream-filter (process output)
|
(defun gptel-curl--stream-filter (process output)
|
||||||
(let* ((content-strs)
|
(let* ((content-strs)
|
||||||
|
@ -201,9 +199,9 @@ See `gptel--url-get-response' for details."
|
||||||
(and (string-match "HTTP/[.0-9]+ +\\([0-9]+\\)" http-msg)
|
(and (string-match "HTTP/[.0-9]+ +\\([0-9]+\\)" http-msg)
|
||||||
(match-string 1 http-msg)))))
|
(match-string 1 http-msg)))))
|
||||||
(plist-put proc-info :http-status http-status)
|
(plist-put proc-info :http-status http-status)
|
||||||
(plist-put proc-info :http-msg (string-trim http-msg)))))
|
(plist-put proc-info :status (string-trim http-msg)))))
|
||||||
|
|
||||||
(when-let ((http-msg (plist-get proc-info :http-msg))
|
(when-let ((http-msg (plist-get proc-info :status))
|
||||||
(http-status (plist-get proc-info :http-status)))
|
(http-status (plist-get proc-info :http-status)))
|
||||||
;; Find data chunk(s) and run callback
|
;; Find data chunk(s) and run callback
|
||||||
(when (equal http-status "200")
|
(when (equal http-status "200")
|
||||||
|
@ -222,7 +220,7 @@ See `gptel--url-get-response' for details."
|
||||||
(push content content-strs)))))
|
(push content content-strs)))))
|
||||||
(error
|
(error
|
||||||
(goto-char (match-beginning 0))))
|
(goto-char (match-beginning 0))))
|
||||||
(list :content (apply #'concat (nreverse content-strs)) :status http-msg))
|
(apply #'concat (nreverse content-strs)))
|
||||||
proc-info))))))
|
proc-info))))))
|
||||||
|
|
||||||
(defun gptel-curl--sentinel (process status)
|
(defun gptel-curl--sentinel (process status)
|
||||||
|
@ -233,14 +231,15 @@ PROCESS and STATUS are process parameters."
|
||||||
(when gptel--debug
|
(when gptel--debug
|
||||||
(with-current-buffer proc-buf
|
(with-current-buffer proc-buf
|
||||||
(clone-buffer "*gptel-error*" 'show)))
|
(clone-buffer "*gptel-error*" 'show)))
|
||||||
(if-let* (((eq (process-status process) 'exit))
|
(when-let* (((eq (process-status process) 'exit))
|
||||||
(proc-info (alist-get process gptel-curl--process-alist))
|
(proc-info (alist-get process gptel-curl--process-alist))
|
||||||
(proc-token (plist-get proc-info :token))
|
(proc-token (plist-get proc-info :token))
|
||||||
(proc-callback (plist-get proc-info :callback))
|
(proc-callback (plist-get proc-info :callback)))
|
||||||
(response (gptel-curl--parse-response proc-buf proc-token)))
|
(pcase-let ((`(,response ,http-msg ,error)
|
||||||
(funcall proc-callback response proc-info)
|
(gptel-curl--parse-response proc-buf proc-token)))
|
||||||
;; Failed
|
(plist-put proc-info :status http-msg)
|
||||||
(funcall proc-callback (list :content nil :status status) proc-info))
|
(when error (plist-put proc-info :error error))
|
||||||
|
(funcall proc-callback response proc-info)))
|
||||||
(setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
|
(setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
|
||||||
(kill-buffer proc-buf)))
|
(kill-buffer proc-buf)))
|
||||||
|
|
||||||
|
@ -274,24 +273,21 @@ buffer."
|
||||||
(json-readtable-error 'json-read-error)))))
|
(json-readtable-error 'json-read-error)))))
|
||||||
(cond
|
(cond
|
||||||
((equal http-status "200")
|
((equal http-status "200")
|
||||||
(list :content
|
(list (string-trim
|
||||||
(string-trim
|
|
||||||
(map-nested-elt response '(:choices 0 :message :content)))
|
(map-nested-elt response '(:choices 0 :message :content)))
|
||||||
:status http-msg))
|
http-msg))
|
||||||
((plist-get response :error)
|
((plist-get response :error)
|
||||||
(let* ((error-plist (plist-get response :error))
|
(let* ((error-plist (plist-get response :error))
|
||||||
(error-msg (plist-get error-plist :message))
|
(error-msg (plist-get error-plist :message))
|
||||||
(error-type (plist-get error-plist :type)))
|
(error-type (plist-get error-plist :type)))
|
||||||
(message "ChatGPT error: (%s) %s" http-msg error-msg)
|
(list nil (concat "(" http-msg ") " (string-trim error-type)) error-msg)))
|
||||||
(list :content nil :status (concat "(" http-msg ") " (string-trim error-type)))))
|
|
||||||
((eq response 'json-read-error)
|
((eq response 'json-read-error)
|
||||||
(message "ChatGPT error: (%s) Malformed JSON in response." http-msg)
|
(list nil (concat "(" http-msg ") Malformed JSON in response.")
|
||||||
(list :content nil :status (concat "(" http-msg ") Malformed JSON in response.")))
|
"Malformed JSON in response"))
|
||||||
(t (message "ChatGPT error (%s): Could not parse HTTP response." http-msg)
|
(t (list nil (concat "(" http-msg ") Could not parse HTTP response.")
|
||||||
(list :content nil :status (concat "(" http-msg ") Could not parse HTTP response."))))
|
"Could not parse HTTP response.")))
|
||||||
(message "ChatGPT error: (%s) Could not parse HTTP response." http-msg)
|
(list nil (concat "(" http-msg ") Could not parse HTTP response.")
|
||||||
(list :content nil
|
"Could not parse HTTP response."))))))
|
||||||
:status (concat "(" http-msg ") Could not parse HTTP response.")))))))
|
|
||||||
|
|
||||||
(provide 'gptel-curl)
|
(provide 'gptel-curl)
|
||||||
;;; gptel-curl.el ends here
|
;;; gptel-curl.el ends here
|
||||||
|
|
52
gptel.el
52
gptel.el
|
@ -304,8 +304,8 @@ instead."
|
||||||
(if gptel-use-curl
|
(if gptel-use-curl
|
||||||
#'gptel-curl-get-response #'gptel--url-get-response)
|
#'gptel-curl-get-response #'gptel--url-get-response)
|
||||||
(list :prompt full-prompt
|
(list :prompt full-prompt
|
||||||
:gptel-buffer gptel-buffer
|
:buffer gptel-buffer
|
||||||
:start-marker response-pt)))
|
:position response-pt)))
|
||||||
(gptel--update-header-line " Waiting..." 'warning)))
|
(gptel--update-header-line " Waiting..." 'warning)))
|
||||||
|
|
||||||
(defun gptel--insert-response (response info)
|
(defun gptel--insert-response (response info)
|
||||||
|
@ -313,28 +313,29 @@ instead."
|
||||||
|
|
||||||
INFO is a plist containing information relevant to this buffer.
|
INFO is a plist containing information relevant to this buffer.
|
||||||
See `gptel--url-get-response' for details."
|
See `gptel--url-get-response' for details."
|
||||||
(let* ((content-str (plist-get response :content))
|
(let* ((status-str (plist-get info :status))
|
||||||
(status-str (plist-get response :status))
|
(gptel-buffer (plist-get info :buffer))
|
||||||
(gptel-buffer (plist-get info :gptel-buffer))
|
(start-marker (plist-get info :position)))
|
||||||
(start-marker (plist-get info :start-marker)))
|
|
||||||
(with-current-buffer gptel-buffer
|
(with-current-buffer gptel-buffer
|
||||||
(if content-str
|
(if response
|
||||||
(progn
|
(progn
|
||||||
(setq content-str (gptel--transform-response
|
(setq response (gptel--transform-response
|
||||||
content-str gptel-buffer))
|
response gptel-buffer))
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(put-text-property 0 (length content-str) 'gptel 'response content-str)
|
(put-text-property 0 (length response) 'gptel 'response response)
|
||||||
(message "Querying ChatGPT... done.")
|
(message "Querying ChatGPT... done.")
|
||||||
(goto-char start-marker)
|
(goto-char start-marker)
|
||||||
(unless (bobp) (insert "\n\n"))
|
(unless (bobp) (insert "\n\n"))
|
||||||
(let ((p (point)))
|
(let ((p (point)))
|
||||||
(insert content-str)
|
(insert response)
|
||||||
(pulse-momentary-highlight-region p (point)))
|
(pulse-momentary-highlight-region p (point)))
|
||||||
(when gptel-mode
|
(when gptel-mode
|
||||||
(insert "\n\n" (gptel-prompt-string))
|
(insert "\n\n" (gptel-prompt-string))
|
||||||
(gptel--update-header-line " Ready" 'success))))
|
(gptel--update-header-line " Ready" 'success))))
|
||||||
(gptel--update-header-line
|
(gptel--update-header-line
|
||||||
(format " Response Error: %s" status-str) 'error))
|
(format " Response Error: %s" status-str) 'error)
|
||||||
|
(message "ChatGPT response error: (%s) %s"
|
||||||
|
status-str (plist-get info :error)))
|
||||||
(run-hooks 'gptel-post-response-hook))))
|
(run-hooks 'gptel-post-response-hook))))
|
||||||
|
|
||||||
(defun gptel--create-prompt (&optional prompt-end)
|
(defun gptel--create-prompt (&optional prompt-end)
|
||||||
|
@ -422,8 +423,8 @@ BUFFER is the interaction buffer for ChatGPT."
|
||||||
|
|
||||||
INFO is a plist with the following keys:
|
INFO is a plist with the following keys:
|
||||||
- :prompt (the prompt being sent)
|
- :prompt (the prompt being sent)
|
||||||
- :gptel-buffer (the gptel buffer)
|
- :buffer (the gptel buffer)
|
||||||
- :start-marker (marker at which to insert the response).
|
- :position (marker at which to insert the response).
|
||||||
|
|
||||||
Call CALLBACK with the response and INFO afterwards. If omitted
|
Call CALLBACK with the response and INFO afterwards. If omitted
|
||||||
the response is inserted into the current buffer after point."
|
the response is inserted into the current buffer after point."
|
||||||
|
@ -439,8 +440,10 @@ the response is inserted into the current buffer after point."
|
||||||
'utf-8)))
|
'utf-8)))
|
||||||
(url-retrieve "https://api.openai.com/v1/chat/completions"
|
(url-retrieve "https://api.openai.com/v1/chat/completions"
|
||||||
(lambda (_)
|
(lambda (_)
|
||||||
(let ((response
|
(pcase-let ((`(,response ,http-msg ,error)
|
||||||
(gptel--url-parse-response (current-buffer))))
|
(gptel--url-parse-response (current-buffer))))
|
||||||
|
(plist-put info :status http-msg)
|
||||||
|
(when error (plist-put info :error error))
|
||||||
(funcall (or callback #'gptel--insert-response)
|
(funcall (or callback #'gptel--insert-response)
|
||||||
response info)
|
response info)
|
||||||
(kill-buffer)))
|
(kill-buffer)))
|
||||||
|
@ -465,22 +468,19 @@ the response is inserted into the current buffer after point."
|
||||||
(json-readtable-error 'json-read-error))))))
|
(json-readtable-error 'json-read-error))))))
|
||||||
(cond
|
(cond
|
||||||
((string-match-p "200 OK" http-msg)
|
((string-match-p "200 OK" http-msg)
|
||||||
(list :content (string-trim (map-nested-elt response '(:choices 0 :message :content)))
|
(list (string-trim (map-nested-elt response '(:choices 0 :message :content)))
|
||||||
:status http-msg))
|
http-msg))
|
||||||
((plist-get response :error)
|
((plist-get response :error)
|
||||||
(let* ((error-plist (plist-get response :error))
|
(let* ((error-plist (plist-get response :error))
|
||||||
(error-msg (plist-get error-plist :message))
|
(error-msg (plist-get error-plist :message))
|
||||||
(error-type (plist-get error-plist :type)))
|
(error-type (plist-get error-plist :type)))
|
||||||
(message "ChatGPT error: (%s) %s" http-msg error-msg)
|
(list nil (concat "(" http-msg ") " error-type) error-msg)))
|
||||||
(list :content nil :status (concat "(" http-msg ") " error-type))))
|
|
||||||
((eq response 'json-read-error)
|
((eq response 'json-read-error)
|
||||||
(message "ChatGPT error: (%s) Malformed JSON in response." http-msg)
|
(list nil (concat "(" http-msg ") Malformed JSON in response.") "json-read-error"))
|
||||||
(list :content nil :status (concat http-msg ": Malformed JSON in response.")))
|
(t (list nil (concat "(" http-msg ") Could not parse HTTP response.")
|
||||||
(t (message "ChatGPT error: (%s) Could not parse HTTP response." http-msg)
|
"Could not parse HTTP response.")))
|
||||||
(list :content nil :status (concat "(" http-msg ") Could not parse HTTP response."))))
|
(list nil (concat "(" http-msg ") Could not parse HTTP response.")
|
||||||
(message "ChatGPT error: (%s) Could not parse HTTP response." http-msg)
|
"Could not parse HTTP response.")))))
|
||||||
(list :content nil
|
|
||||||
:status (concat "(" http-msg ") Could not parse HTTP response."))))))
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun gptel (name &optional api-key initial)
|
(defun gptel (name &optional api-key initial)
|
||||||
|
|
Loading…
Add table
Reference in a new issue