gptel: Consolidate HTTP request process

* gptel.el (gptel-request, gptel-send, gptel--url-get-response):
Consolidate the HTTP query construction into `gptel-request`,
and use it as the single point for sending gptel queries.  This
simplifies the code, makes it easier to debug and (later) advise
or otherwise modify.  To this end, `gptel-send` reuses
`gptel-request` and no longer does its own thing.  Adjust other
HTTP request-related functions accordingly.

* gptel-curl.el (gptel-curl--get-args, gptel-curl--get-response):
Receive the full request data instead of constructing it partly in
`gptel-curl--get-args`.
This commit is contained in:
Karthik Chikmagalur 2024-03-13 21:48:46 -07:00
parent e5f54d1d09
commit 260be9d8d4
2 changed files with 30 additions and 35 deletions

View file

@ -47,16 +47,14 @@
(defvar gptel-curl--process-alist nil (defvar gptel-curl--process-alist nil
"Alist of active GPTel curl requests.") "Alist of active GPTel curl requests.")
(defun gptel-curl--get-args (prompts token) (defun gptel-curl--get-args (data token)
"Produce list of arguments for calling Curl. "Produce list of arguments for calling Curl.
PROMPTS is the data to send, TOKEN is a unique identifier." REQUEST-DATA is the data to send, TOKEN is a unique identifier."
(let* ((url (let ((backend-url (gptel-backend-url gptel-backend))) (let* ((url (let ((backend-url (gptel-backend-url gptel-backend)))
(if (functionp backend-url) (if (functionp backend-url)
(funcall backend-url) backend-url))) (funcall backend-url) backend-url)))
(data (encode-coding-string (data-json (encode-coding-string (gptel--json-encode data) 'utf-8))
(gptel--json-encode (gptel--request-data gptel-backend prompts))
'utf-8))
(headers (headers
(append '(("Content-Type" . "application/json")) (append '(("Content-Type" . "application/json"))
(when-let ((header (gptel-backend-header gptel-backend))) (when-let ((header (gptel-backend-header gptel-backend)))
@ -68,15 +66,15 @@ PROMPTS is the data to send, TOKEN is a unique identifier."
(mapcar (lambda (pair) (cons (intern (car pair)) (cdr pair))) (mapcar (lambda (pair) (cons (intern (car pair)) (cdr pair)))
headers)) headers))
"request headers")) "request headers"))
(gptel--log data "request body")) (gptel--log data-json "request body"))
(append (append
gptel-curl--common-args gptel-curl--common-args
(gptel-backend-curl-args gptel-backend) (gptel-backend-curl-args gptel-backend)
(list (format "-w(%s . %%{size_header})" token)) (list (format "-w(%s . %%{size_header})" token))
(if (length< data gptel-curl-file-size-threshold) (if (length< data-json gptel-curl-file-size-threshold)
(list (format "-d%s" data)) (list (format "-d%s" data-json))
(letrec (letrec
((temp-filename (make-temp-file "gptel-curl-data" nil ".json" data)) ((temp-filename (make-temp-file "gptel-curl-data" nil ".json" data-json))
(cleanup-fn (lambda (&rest _) (cleanup-fn (lambda (&rest _)
(when (file-exists-p temp-filename) (when (file-exists-p temp-filename)
(delete-file temp-filename) (delete-file temp-filename)
@ -99,7 +97,7 @@ PROMPTS is the data to send, TOKEN is a unique identifier."
"Retrieve response to prompt in INFO. "Retrieve response to prompt in INFO.
INFO is a plist with the following keys: INFO is a plist with the following keys:
- :prompt (the prompt being sent) - :data (the data being sent)
- :buffer (the gptel buffer) - :buffer (the gptel buffer)
- :position (marker at which to insert the response). - :position (marker at which to insert the response).
@ -108,7 +106,7 @@ the response is inserted into the current buffer after point."
(let* ((token (md5 (format "%s%s%s%s" (let* ((token (md5 (format "%s%s%s%s"
(random) (emacs-pid) (user-full-name) (random) (emacs-pid) (user-full-name)
(recent-keys)))) (recent-keys))))
(args (gptel-curl--get-args (plist-get info :prompt) token)) (args (gptel-curl--get-args (plist-get info :data) token))
(stream (and gptel-stream (gptel-backend-stream gptel-backend))) (stream (and gptel-stream (gptel-backend-stream gptel-backend)))
(process (apply #'start-process "gptel-curl" (process (apply #'start-process "gptel-curl"
(generate-new-buffer "*gptel-curl*") "curl" args))) (generate-new-buffer "*gptel-curl*") "curl" args)))

View file

@ -805,11 +805,14 @@ file."
(cl-defun gptel-request (cl-defun gptel-request
(&optional prompt &key callback (&optional prompt &key callback
(buffer (current-buffer)) (buffer (current-buffer))
position context position context dry-run
(stream nil) (in-place nil) (stream nil) (in-place nil)
(system gptel--system-message)) (system gptel--system-message))
"Request a response from the `gptel-backend' for PROMPT. "Request a response from the `gptel-backend' for PROMPT.
The request is asynchronous, the function immediately returns
with the data that was sent.
Note: This function is not fully self-contained. Consider Note: This function is not fully self-contained. Consider
let-binding the parameters `gptel-backend' and `gptel-model' let-binding the parameters `gptel-backend' and `gptel-model'
around calls to it as required. around calls to it as required.
@ -832,7 +835,7 @@ with the RESPONSE (a string) and INFO (a plist):
RESPONSE is nil if there was no response or an error. RESPONSE is nil if there was no response or an error.
The INFO plist has (at least) the following keys: The INFO plist has (at least) the following keys:
:prompt - The full prompt that was sent with the request :data - The request data included with the query
:position - marker at the point the request was sent, unless :position - marker at the point the request was sent, unless
POSITION is specified. POSITION is specified.
:buffer - The buffer current when the request was sent, :buffer - The buffer current when the request was sent,
@ -887,6 +890,9 @@ STREAM is a boolean that determines if the response should be
streamed, as in `gptel-stream'. Do not set this if you are streamed, as in `gptel-stream'. Do not set this if you are
specifying a custom CALLBACK! specifying a custom CALLBACK!
If DRY-RUN is non-nil, construct and return the full
query data as usual, but do not send the request.
Model parameters can be let-bound around calls to this function." Model parameters can be let-bound around calls to this function."
(declare (indent 1)) (declare (indent 1))
(let* ((gptel-stream stream) (let* ((gptel-stream stream)
@ -908,19 +914,22 @@ Model parameters can be let-bound around calls to this function."
;; FIXME Dear reader, welcome to Jank City: ;; FIXME Dear reader, welcome to Jank City:
(with-temp-buffer (with-temp-buffer
(let ((gptel--system-message system) (let ((gptel--system-message system)
(gptel-model (buffer-local-value 'gptel-model buffer))
(gptel-backend (buffer-local-value 'gptel-backend buffer))) (gptel-backend (buffer-local-value 'gptel-backend buffer)))
(insert prompt) (insert prompt)
(gptel--create-prompt)))) (gptel--create-prompt))))
((consp prompt) prompt))) ((consp prompt) prompt)))
(info (list :prompt full-prompt (request-data (gptel--request-data gptel-backend full-prompt))
(info (list :data request-data
:buffer buffer :buffer buffer
:position start-marker))) :position start-marker)))
(when context (plist-put info :context context)) (when context (plist-put info :context context))
(when in-place (plist-put info :in-place in-place)) (when in-place (plist-put info :in-place in-place))
(funcall (unless dry-run
(if gptel-use-curl (funcall (if gptel-use-curl
#'gptel-curl-get-response #'gptel--url-get-response) #'gptel-curl-get-response #'gptel--url-get-response)
info callback))) info callback))
request-data))
;; TODO: Handle multiple requests(#15). (Only one request from one buffer at a time?) ;; TODO: Handle multiple requests(#15). (Only one request from one buffer at a time?)
;;;###autoload ;;;###autoload
@ -943,19 +952,8 @@ waiting for the response."
(call-interactively #'gptel-menu) (call-interactively #'gptel-menu)
(message "Querying %s..." (gptel-backend-name gptel-backend)) (message "Querying %s..." (gptel-backend-name gptel-backend))
(gptel--sanitize-model) (gptel--sanitize-model)
(let* ((response-pt (gptel-request nil :stream gptel-stream)
(if (use-region-p) (gptel--update-status " Waiting..." 'warning)))
(set-marker (make-marker) (region-end))
(gptel--at-word-end (point-marker))))
(gptel-buffer (current-buffer))
(full-prompt (gptel--create-prompt response-pt)))
(funcall
(if gptel-use-curl
#'gptel-curl-get-response #'gptel--url-get-response)
(list :prompt full-prompt
:buffer gptel-buffer
:position response-pt)))
(gptel--update-status " Waiting..." 'warning)))
(defun gptel--insert-response (response info) (defun gptel--insert-response (response info)
"Insert the LLM RESPONSE into the gptel buffer. "Insert the LLM RESPONSE into the gptel buffer.
@ -1113,8 +1111,8 @@ BUFFER is the LLM interaction buffer."
"Fetch response to prompt in INFO from the LLM. "Fetch response to prompt in INFO from the LLM.
INFO is a plist with the following keys: INFO is a plist with the following keys:
- :prompt (the prompt being sent) - :data (the data being sent)
- :buffer (the gptel buffer) - :buffer (the gptel buffer)
- :position (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
@ -1130,8 +1128,7 @@ the response is inserted into the current buffer after point."
(funcall header) header)))) (funcall header) header))))
(url-request-data (url-request-data
(encode-coding-string (encode-coding-string
(gptel--json-encode (gptel--request-data (gptel--json-encode (plist-get info :data))
gptel-backend (plist-get info :prompt)))
'utf-8))) 'utf-8)))
(when gptel-log-level ;logging (when gptel-log-level ;logging
(when (eq gptel-log-level 'debug) (when (eq gptel-log-level 'debug)