From 260be9d8d4e195662fb34d51efe447c421c27d20 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Wed, 13 Mar 2024 21:48:46 -0700 Subject: [PATCH] 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`. --- gptel-curl.el | 20 +++++++++----------- gptel.el | 45 +++++++++++++++++++++------------------------ 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/gptel-curl.el b/gptel-curl.el index d5fac7a..7426135 100644 --- a/gptel-curl.el +++ b/gptel-curl.el @@ -47,16 +47,14 @@ (defvar gptel-curl--process-alist nil "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. -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))) (if (functionp backend-url) (funcall backend-url) backend-url))) - (data (encode-coding-string - (gptel--json-encode (gptel--request-data gptel-backend prompts)) - 'utf-8)) + (data-json (encode-coding-string (gptel--json-encode data) 'utf-8)) (headers (append '(("Content-Type" . "application/json")) (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))) headers)) "request headers")) - (gptel--log data "request body")) + (gptel--log data-json "request body")) (append gptel-curl--common-args (gptel-backend-curl-args gptel-backend) (list (format "-w(%s . %%{size_header})" token)) - (if (length< data gptel-curl-file-size-threshold) - (list (format "-d%s" data)) + (if (length< data-json gptel-curl-file-size-threshold) + (list (format "-d%s" data-json)) (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 _) (when (file-exists-p 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. INFO is a plist with the following keys: -- :prompt (the prompt being sent) +- :data (the data being sent) - :buffer (the gptel buffer) - :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" (random) (emacs-pid) (user-full-name) (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))) (process (apply #'start-process "gptel-curl" (generate-new-buffer "*gptel-curl*") "curl" args))) diff --git a/gptel.el b/gptel.el index c72de32..a75d905 100644 --- a/gptel.el +++ b/gptel.el @@ -805,11 +805,14 @@ file." (cl-defun gptel-request (&optional prompt &key callback (buffer (current-buffer)) - position context + position context dry-run (stream nil) (in-place nil) (system gptel--system-message)) "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 let-binding the parameters `gptel-backend' and `gptel-model' 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. 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 is specified. :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 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." (declare (indent 1)) (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: (with-temp-buffer (let ((gptel--system-message system) + (gptel-model (buffer-local-value 'gptel-model buffer)) (gptel-backend (buffer-local-value 'gptel-backend buffer))) (insert prompt) (gptel--create-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 :position start-marker))) (when context (plist-put info :context context)) (when in-place (plist-put info :in-place in-place)) - (funcall - (if gptel-use-curl - #'gptel-curl-get-response #'gptel--url-get-response) - info callback))) + (unless dry-run + (funcall (if gptel-use-curl + #'gptel-curl-get-response #'gptel--url-get-response) + info callback)) + request-data)) ;; TODO: Handle multiple requests(#15). (Only one request from one buffer at a time?) ;;;###autoload @@ -943,19 +952,8 @@ waiting for the response." (call-interactively #'gptel-menu) (message "Querying %s..." (gptel-backend-name gptel-backend)) (gptel--sanitize-model) - (let* ((response-pt - (if (use-region-p) - (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))) + (gptel-request nil :stream gptel-stream) + (gptel--update-status " Waiting..." 'warning))) (defun gptel--insert-response (response info) "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. INFO is a plist with the following keys: -- :prompt (the prompt being sent) -- :buffer (the gptel buffer) +- :data (the data being sent) +- :buffer (the gptel buffer) - :position (marker at which to insert the response). 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)))) (url-request-data (encode-coding-string - (gptel--json-encode (gptel--request-data - gptel-backend (plist-get info :prompt))) + (gptel--json-encode (plist-get info :data)) 'utf-8))) (when gptel-log-level ;logging (when (eq gptel-log-level 'debug)