[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/gptel d5ad620555 059/273: gptel-curl: process filter for s
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/gptel d5ad620555 059/273: gptel-curl: process filter for streaming support |
Date: |
Wed, 1 May 2024 10:01:42 -0400 (EDT) |
branch: elpa/gptel
commit d5ad620555ced59c5626a8e7e22602fee8d24c53
Author: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
Commit: Karthik Chikmagalur <karthikchikmagalur@gmail.com>
gptel-curl: process filter for streaming support
* gptel.el (gptel--request-data): Request a streaming message if
`gptel-stream' is non-nil.
* gptel-curl.el (gptel-curl-get-response,
gptel-curl--cleanup-stream, gptel-curl--filter): Add a process
filter and sentinel for Curl to stream ChatGPT's response into
Emacs in real-time.
---
gptel-curl.el | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
gptel.el | 3 +-
2 files changed, 111 insertions(+), 3 deletions(-)
diff --git a/gptel-curl.el b/gptel-curl.el
index 6aec3ff90e..e6ca0dd299 100644
--- a/gptel-curl.el
+++ b/gptel-curl.el
@@ -80,9 +80,116 @@ the response is inserted into the current buffer after
point."
(set-process-query-on-exit-flag process nil)
(setf (alist-get process gptel-curl--process-alist)
(nconc (list :token token
- :callback (or callback #'gptel--insert-response))
+ :callback (or callback
+ (if gptel-playback
+ #'gptel--insert-response-stream
+ #'gptel--insert-response)))
info))
- (set-process-sentinel process #'gptel-curl--sentinel))))
+ (if gptel-playback
+ (progn (set-process-sentinel process #'gptel-curl--cleanup-stream)
+ (set-process-filter process #'gptel-curl--filter))
+ (set-process-sentinel process #'gptel-curl--sentinel)))))
+
+(defun gptel-curl--cleanup-stream (process status)
+ "Process sentinel for GPTel curl requests.
+
+PROCESS and STATUS are process parameters."
+ (let ((proc-buf (process-buffer process)))
+ (when gptel--debug
+ (with-current-buffer proc-buf
+ (clone-buffer "*gptel-error*" 'show)))
+ (let* ((info (alist-get process gptel-curl--process-alist))
+ (gptel-buffer (plist-get info :gptel-buffer))
+ (tracking-marker (plist-get info :tracking-marker))
+ (start-marker (plist-get info :insert-marker)))
+ (when start-marker (goto-char start-marker))
+ (pulse-momentary-highlight-region (+ start-marker 2) tracking-marker)
+ (when (equal (plist-get info :http-status) "200")
+ (with-current-buffer gptel-buffer
+ (gptel--update-header-line " Ready" 'success)
+ (when gptel-mode
+ (save-excursion (goto-char tracking-marker)
+ (insert "\n\n" (gptel-prompt-string)))))))
+ (setf (alist-get process gptel-curl--process-alist nil 'remove) nil)
+ (kill-buffer proc-buf)))
+
+(defun gptel--insert-response-stream (response info)
+ "Insert streaming RESPONSE from ChatGPT into the gptel buffer.
+
+INFO is a mutable plist containing information relevant to this buffer.
+See `gptel--url-get-response' for details."
+ (let ((content-str (plist-get response :content))
+ (status-str (plist-get response :status))
+ (gptel-buffer (plist-get info :gptel-buffer))
+ (start-marker (plist-get info :insert-marker))
+ (tracking-marker (plist-get info :tracking-marker)))
+ (if content-str
+ (with-current-buffer gptel-buffer
+ (save-excursion
+ (unless tracking-marker
+ (gptel--update-header-line " Typing..." 'success)
+ (goto-char start-marker)
+ (insert "\n\n")
+ (setq tracking-marker (set-marker (make-marker) (point)))
+ (set-marker-insertion-type tracking-marker t)
+ (plist-put info :tracking-marker tracking-marker))
+
+ (setq content-str (gptel--transform-response
+ content-str gptel-buffer))
+ (put-text-property 0 (length content-str) 'gptel 'response
content-str)
+ (goto-char tracking-marker)
+ (insert content-str)))
+ (gptel--update-header-line
+ (format " Response Error: %s" status-str) 'error))))
+
+(defun gptel-curl--filter (process output)
+ (let* ((content-strs)
+ (proc-info (alist-get process gptel-curl--process-alist)))
+ (with-current-buffer (process-buffer process)
+ ;; Insert output
+ (save-excursion
+ (goto-char (process-mark process))
+ (insert output)
+ (set-marker (process-mark process) (point)))
+
+ ;; Find HTTP status
+ (unless (plist-get proc-info :http-status)
+ (save-excursion
+ (goto-char (point-min))
+ (when-let* (((not (= (line-end-position) (point-max))))
+ (http-msg (buffer-substring (line-beginning-position)
+ (line-end-position)))
+ (http-status
+ (save-match-data
+ (and (string-match "HTTP/[.0-9]+ +\\([0-9]+\\)"
http-msg)
+ (match-string 1 http-msg)))))
+ (plist-put proc-info :http-status http-status)
+ (plist-put proc-info :http-msg http-msg)
+ (unless (equal http-status "200")
+ (message "%s" (concat (string-trim http-msg) ": Could not parse
HTTP response."))))))
+
+ (when-let ((http-msg (plist-get proc-info :http-msg))
+ (http-status (plist-get proc-info :http-status)))
+ ;; Find data chunk(s) and run callback
+ (funcall (or (plist-get proc-info :callback)
+ #'gptel--insert-response-stream)
+ (if (equal http-status "200")
+ (let* ((json-object-type 'plist)
+ (response) (content-str))
+ (condition-case nil
+ (while (re-search-forward "^data:" nil t)
+ (save-match-data
+ (unless (looking-at " *\\[DONE\\]")
+ (when-let* ((response (json-read))
+ (delta (map-nested-elt
+ response '(:choices 0
:delta)))
+ (content (plist-get delta
:content)))
+ (push content content-strs)))))
+ (error
+ (goto-char (match-beginning 0))))
+ (list :content (apply #'concat (nreverse content-strs))
:status http-msg))
+ (list :content nil :status http-msg))
+ proc-info)))))
(defun gptel-curl--sentinel (process status)
"Process sentinel for GPTel curl requests.
diff --git a/gptel.el b/gptel.el
index 035e547709..a1836b5589 100644
--- a/gptel.el
+++ b/gptel.el
@@ -370,7 +370,8 @@ there."
"JSON encode PROMPTS for sending to ChatGPT."
(let ((prompts-plist
`(:model ,gptel-model
- :messages [,@prompts])))
+ :messages [,@prompts]
+ :stream ,(and gptel-stream gptel-use-curl))))
(when gptel-temperature
(plist-put prompts-plist :temperature (gptel--numberize
gptel-temperature)))
(when gptel-max-tokens
- [nongnu] elpa/gptel 77d1010fbc 010/273: gptel-curl: Add package version, (continued)
- [nongnu] elpa/gptel 77d1010fbc 010/273: gptel-curl: Add package version, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 03113afd50 008/273: gptel: Rename internal functions, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel b212c24c4a 018/273: gptel: tweak prompt, rename url functions, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 172059060a 012/273: gptel-curl: Autoload gptel-curl-get-response, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel c8f87f5554 030/273: Update README with transient menu details, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 9f8fc0e519 021/273: gptel-transient: Commands to act on region, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel f0eba0cf4f 039/273: README: Update README for MELPA, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 87d9090b7a 036/273: gptel-curl: Fix process sentinel, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 552939b2f6 047/273: gptel: Fix free-variable error, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 30161850ad 032/273: gptel-transient: Allow setting num past messages to 0, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel d5ad620555 059/273: gptel-curl: process filter for streaming support,
ELPA Syncer <=
- [nongnu] elpa/gptel 5a0deda7fc 065/273: gptel: Turn on streaming replies by default, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 937c754e57 076/273: gptel-transient: Add refactor transient, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 3d98ce8eee 099/273: gptel: Add new turbo 0613 models (#77), ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel b92fc389d7 105/273: gptel: Reduce verbosity of gptel--save-state, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 33d8434f3e 006/273: gptel: Tweak header line format, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel c2ad1c004d 054/273: Decode response body as utf-8 and then parse as json, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 1f03655e2d 056/273: Add Doom Emacs installation instructions (#28), ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 936c27e28b 058/273: gptel: Fix header-line-format update, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel acf12ee6e3 073/273: gptel: return the gptel buffer, ELPA Syncer, 2024/05/01
- [nongnu] elpa/gptel 706ad703db 090/273: gptel-transient: Allow arbitrary system prompts/directives, ELPA Syncer, 2024/05/01