gptel: Use libjansson support if available
Using the libjansson JSON parser gives us a modest boost in speed. It's not as significant a speedup as it is for LSP clients since our jSON payloads are smaller and less frequent -- but we might as well use it. * gptel.el (gptel--json-read, gptel--json-encode, gptel--url-get-response, gptel--parse-response): Define macros to use the libjansson-supported `json-parse-buffer` and `json-serialize`. Replace use of `json-encode` and `json-read` appropriately. * gptel-openai.el: (gptel-curl--parse-stream) : Use `gptel--json-read` instead of `json-read`. * gptel-ollama.el (gptel-curl--parse-stream): Use `gptel--json-read` instead of `json-read`. * gptel-gemini.el (gptel-curl--parse-stream): Use `gptel--json-read` instead of `json-read`. * gptel-curl.el (gptel-curl--get-args, gptel-curl--get-response, gptel-curl--log-response, gptel-curl--stream-cleanup, gptel-curl--parse-response): Use `gptel--json-read` and `gptel--json-encode` in place of the json.el versions. * gptel-anthropic.el (gptel-curl--parse-stream): Use `gptel--json-read` instead of `json-read`. * test/gptel-org-test.el: Use `gptel--json-read`.
This commit is contained in:
parent
fbb0ee29c4
commit
f58ad9435c
7 changed files with 62 additions and 39 deletions
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
(declare-function prop-match-value "text-property-search")
|
(declare-function prop-match-value "text-property-search")
|
||||||
(declare-function text-property-search-backward "text-property-search")
|
(declare-function text-property-search-backward "text-property-search")
|
||||||
(declare-function json-read "json")
|
(declare-function json-read "json" ())
|
||||||
|
|
||||||
;;; Anthropic (Messages API)
|
;;; Anthropic (Messages API)
|
||||||
(cl-defstruct (gptel-anthropic (:constructor gptel--make-anthropic)
|
(cl-defstruct (gptel-anthropic (:constructor gptel--make-anthropic)
|
||||||
|
@ -40,8 +40,7 @@
|
||||||
(:include gptel-backend)))
|
(:include gptel-backend)))
|
||||||
|
|
||||||
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-anthropic) _info)
|
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-anthropic) _info)
|
||||||
(let* ((json-object-type 'plist)
|
(let* ((content-strs)
|
||||||
(content-strs)
|
|
||||||
(pt (point)))
|
(pt (point)))
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
(while (re-search-forward "^event: " nil t)
|
(while (re-search-forward "^event: " nil t)
|
||||||
|
@ -50,7 +49,7 @@
|
||||||
((looking-at "content_block_\\(?:start\\|delta\\|stop\\)")
|
((looking-at "content_block_\\(?:start\\|delta\\|stop\\)")
|
||||||
(save-match-data
|
(save-match-data
|
||||||
(forward-line 1) (forward-char 5)
|
(forward-line 1) (forward-char 5)
|
||||||
(when-let* ((response (json-read))
|
(when-let* ((response (gptel--json-read))
|
||||||
(content (map-nested-elt
|
(content (map-nested-elt
|
||||||
response '(:delta :text))))
|
response '(:delta :text))))
|
||||||
(push content content-strs))))))
|
(push content content-strs))))))
|
||||||
|
|
|
@ -32,7 +32,9 @@
|
||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
(require 'subr-x))
|
(require 'subr-x))
|
||||||
(require 'map)
|
(require 'map)
|
||||||
(require 'json)
|
|
||||||
|
(declare-function json-read "json" ())
|
||||||
|
(defvar json-object-type)
|
||||||
|
|
||||||
(defconst gptel-curl--common-args
|
(defconst gptel-curl--common-args
|
||||||
(if (memq system-type '(windows-nt ms-dos))
|
(if (memq system-type '(windows-nt ms-dos))
|
||||||
|
@ -53,7 +55,7 @@ PROMPTS is the data to send, TOKEN is a unique identifier."
|
||||||
(if (functionp backend-url)
|
(if (functionp backend-url)
|
||||||
(funcall backend-url) backend-url)))
|
(funcall backend-url) backend-url)))
|
||||||
(data (encode-coding-string
|
(data (encode-coding-string
|
||||||
(json-encode (gptel--request-data gptel-backend prompts))
|
(gptel--json-encode (gptel--request-data gptel-backend prompts))
|
||||||
'utf-8))
|
'utf-8))
|
||||||
(headers
|
(headers
|
||||||
(append '(("Content-Type" . "application/json"))
|
(append '(("Content-Type" . "application/json"))
|
||||||
|
@ -62,7 +64,7 @@ PROMPTS is the data to send, TOKEN is a unique identifier."
|
||||||
(funcall header) header)))))
|
(funcall header) header)))))
|
||||||
(when gptel-log-level
|
(when gptel-log-level
|
||||||
(when (eq gptel-log-level 'debug)
|
(when (eq gptel-log-level 'debug)
|
||||||
(gptel--log (json-encode headers) "request headers"))
|
(gptel--log (gptel--json-encode headers) "request headers"))
|
||||||
(gptel--log data "request body"))
|
(gptel--log data "request body"))
|
||||||
(append
|
(append
|
||||||
gptel-curl--common-args
|
gptel-curl--common-args
|
||||||
|
@ -108,7 +110,7 @@ the response is inserted into the current buffer after point."
|
||||||
(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)))
|
||||||
(when (eq gptel-log-level 'debug)
|
(when (eq gptel-log-level 'debug)
|
||||||
(gptel--log (json-encode (cons "curl" args))
|
(gptel--log (gptel--json-encode (cons "curl" args))
|
||||||
"request Curl command"))
|
"request Curl command"))
|
||||||
(with-current-buffer (process-buffer process)
|
(with-current-buffer (process-buffer process)
|
||||||
(set-process-query-on-exit-flag process nil)
|
(set-process-query-on-exit-flag process nil)
|
||||||
|
@ -154,7 +156,7 @@ PROC-INFO is the plist containing process metadata."
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(when (re-search-forward "
?\n
?\n" nil t)
|
(when (re-search-forward "
?\n
?\n" nil t)
|
||||||
(when (eq gptel-log-level 'debug)
|
(when (eq gptel-log-level 'debug)
|
||||||
(gptel--log (json-encode-string
|
(gptel--log (gptel--json-encode
|
||||||
(buffer-substring-no-properties
|
(buffer-substring-no-properties
|
||||||
(point-min) (1- (point))))
|
(point-min) (1- (point))))
|
||||||
"response headers"))
|
"response headers"))
|
||||||
|
@ -216,10 +218,9 @@ PROCESS and _STATUS are process parameters."
|
||||||
(search-backward (plist-get info :token))
|
(search-backward (plist-get info :token))
|
||||||
(backward-char)
|
(backward-char)
|
||||||
(pcase-let* ((`(,_ . ,header-size) (read (current-buffer)))
|
(pcase-let* ((`(,_ . ,header-size) (read (current-buffer)))
|
||||||
(json-object-type 'plist)
|
|
||||||
(response (progn (goto-char header-size)
|
(response (progn (goto-char header-size)
|
||||||
(condition-case nil (json-read)
|
(condition-case nil (gptel--json-read)
|
||||||
(json-readtable-error 'json-read-error))))
|
(error 'json-read-error))))
|
||||||
(error-data (plist-get response :error)))
|
(error-data (plist-get response :error)))
|
||||||
(cond
|
(cond
|
||||||
(error-data
|
(error-data
|
||||||
|
@ -379,11 +380,10 @@ PROC-INFO is a plist with contextual information."
|
||||||
(save-match-data
|
(save-match-data
|
||||||
(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))))
|
||||||
(json-object-type 'plist)
|
|
||||||
(response (progn (goto-char header-size)
|
(response (progn (goto-char header-size)
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
(json-read)
|
(gptel--json-read)
|
||||||
(json-readtable-error 'json-read-error)))))
|
(error 'json-read-error)))))
|
||||||
(cond
|
(cond
|
||||||
;; FIXME Handle the case where HTTP 100 is followed by HTTP (not 200) BUG #194
|
;; FIXME Handle the case where HTTP 100 is followed by HTTP (not 200) BUG #194
|
||||||
((member http-status '("200" "100"))
|
((member http-status '("200" "100"))
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
(declare-function prop-match-value "text-property-search")
|
(declare-function prop-match-value "text-property-search")
|
||||||
(declare-function text-property-search-backward "text-property-search")
|
(declare-function text-property-search-backward "text-property-search")
|
||||||
(declare-function json-read "json")
|
(declare-function json-read "json")
|
||||||
|
(defvar json-object-type)
|
||||||
|
|
||||||
;;; Gemini
|
;;; Gemini
|
||||||
(cl-defstruct
|
(cl-defstruct
|
||||||
|
@ -38,15 +39,14 @@
|
||||||
(:include gptel-backend)))
|
(:include gptel-backend)))
|
||||||
|
|
||||||
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-gemini) _info)
|
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-gemini) _info)
|
||||||
(let* ((json-object-type 'plist)
|
(let* ((content-strs))
|
||||||
(content-strs))
|
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
;; while-let is Emacs 29.1+ only
|
;; while-let is Emacs 29.1+ only
|
||||||
(while (prog1 (search-forward "{" nil t)
|
(while (prog1 (search-forward "{" nil t)
|
||||||
(backward-char 1))
|
(backward-char 1))
|
||||||
(save-match-data
|
(save-match-data
|
||||||
(when-let*
|
(when-let*
|
||||||
((response (json-read))
|
((response (gptel--json-read))
|
||||||
(text (map-nested-elt
|
(text (map-nested-elt
|
||||||
response '(:candidates 0 :content :parts 0 :text))))
|
response '(:candidates 0 :content :parts 0 :text))))
|
||||||
(push text content-strs))))
|
(push text content-strs))))
|
||||||
|
|
|
@ -26,6 +26,9 @@
|
||||||
(require 'gptel)
|
(require 'gptel)
|
||||||
(require 'cl-generic)
|
(require 'cl-generic)
|
||||||
|
|
||||||
|
(declare-function json-read "json" ())
|
||||||
|
(defvar json-object-type)
|
||||||
|
|
||||||
;;; Ollama
|
;;; Ollama
|
||||||
(cl-defstruct (gptel-ollama (:constructor gptel--make-ollama)
|
(cl-defstruct (gptel-ollama (:constructor gptel--make-ollama)
|
||||||
(:copier nil)
|
(:copier nil)
|
||||||
|
@ -42,15 +45,14 @@ Ollama models.")
|
||||||
(when (bobp)
|
(when (bobp)
|
||||||
(re-search-forward "^{")
|
(re-search-forward "^{")
|
||||||
(forward-line 0))
|
(forward-line 0))
|
||||||
(let* ((json-object-type 'plist)
|
(let* ((content-strs)
|
||||||
(content-strs)
|
|
||||||
(content))
|
(content))
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
(while (setq content (json-read))
|
(while (setq content (gptel--json-read))
|
||||||
(let ((done (map-elt content :done))
|
(let ((done (map-elt content :done))
|
||||||
(response (map-elt content :response)))
|
(response (map-elt content :response)))
|
||||||
(push response content-strs)
|
(push response content-strs)
|
||||||
(unless (eq done json-false)
|
(unless (eq done :json-false)
|
||||||
(with-current-buffer (plist-get info :buffer)
|
(with-current-buffer (plist-get info :buffer)
|
||||||
(setq gptel--ollama-context (map-elt content :context)))
|
(setq gptel--ollama-context (map-elt content :context)))
|
||||||
(goto-char (point-max)))))
|
(goto-char (point-max)))))
|
||||||
|
|
|
@ -44,6 +44,29 @@
|
||||||
(declare-function gptel-prompt-prefix-string "gptel")
|
(declare-function gptel-prompt-prefix-string "gptel")
|
||||||
(declare-function gptel-response-prefix-string "gptel")
|
(declare-function gptel-response-prefix-string "gptel")
|
||||||
|
|
||||||
|
(defmacro gptel--json-read ()
|
||||||
|
(if (fboundp 'json-parse-buffer)
|
||||||
|
`(json-parse-buffer
|
||||||
|
:object-type 'plist
|
||||||
|
:null-object nil
|
||||||
|
:false-object :json-false)
|
||||||
|
(require 'json)
|
||||||
|
(defvar json-object-type)
|
||||||
|
(declare-function json-read "json" ())
|
||||||
|
`(let ((json-object-type 'plist))
|
||||||
|
gptel--json-read)))
|
||||||
|
|
||||||
|
(defmacro gptel--json-encode (object)
|
||||||
|
(if (fboundp 'json-serialize)
|
||||||
|
`(json-serialize ,object
|
||||||
|
:null-object nil
|
||||||
|
:false-object :json-false)
|
||||||
|
(require 'json)
|
||||||
|
(defvar json-false)
|
||||||
|
(declare-function json-encode "json" (object))
|
||||||
|
`(let ((json-false :json-false))
|
||||||
|
(json-encode ,object))))
|
||||||
|
|
||||||
;;; Common backend struct for LLM support
|
;;; Common backend struct for LLM support
|
||||||
(cl-defstruct
|
(cl-defstruct
|
||||||
(gptel-backend (:constructor gptel--make-backend)
|
(gptel-backend (:constructor gptel--make-backend)
|
||||||
|
@ -57,13 +80,12 @@
|
||||||
(:include gptel-backend)))
|
(:include gptel-backend)))
|
||||||
|
|
||||||
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-openai) _info)
|
(cl-defmethod gptel-curl--parse-stream ((_backend gptel-openai) _info)
|
||||||
(let* ((json-object-type 'plist)
|
(let* ((content-strs))
|
||||||
(content-strs))
|
|
||||||
(condition-case nil
|
(condition-case nil
|
||||||
(while (re-search-forward "^data:" nil t)
|
(while (re-search-forward "^data:" nil t)
|
||||||
(save-match-data
|
(save-match-data
|
||||||
(unless (looking-at " *\\[DONE\\]")
|
(unless (looking-at " *\\[DONE\\]")
|
||||||
(when-let* ((response (json-read))
|
(when-let* ((response (gptel--json-read))
|
||||||
(delta (map-nested-elt
|
(delta (map-nested-elt
|
||||||
response '(:choices 0 :delta)))
|
response '(:choices 0 :delta)))
|
||||||
(content (plist-get delta :content)))
|
(content (plist-get delta :content)))
|
||||||
|
|
19
gptel.el
19
gptel.el
|
@ -128,7 +128,6 @@
|
||||||
(require 'cl-lib))
|
(require 'cl-lib))
|
||||||
(require 'compat nil t)
|
(require 'compat nil t)
|
||||||
(require 'url)
|
(require 'url)
|
||||||
(require 'json)
|
|
||||||
(require 'map)
|
(require 'map)
|
||||||
(require 'text-property-search)
|
(require 'text-property-search)
|
||||||
(require 'cl-generic)
|
(require 'cl-generic)
|
||||||
|
@ -615,6 +614,8 @@ in any way.")
|
||||||
(defconst gptel--log-buffer-name "*gptel-log*"
|
(defconst gptel--log-buffer-name "*gptel-log*"
|
||||||
"Log buffer for gptel.")
|
"Log buffer for gptel.")
|
||||||
|
|
||||||
|
(declare-function json-pretty-print "json")
|
||||||
|
|
||||||
(defun gptel--log (data &optional type no-json)
|
(defun gptel--log (data &optional type no-json)
|
||||||
"Log DATA to `gptel--log-buffer-name'.
|
"Log DATA to `gptel--log-buffer-name'.
|
||||||
|
|
||||||
|
@ -1129,12 +1130,12 @@ 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
|
||||||
(json-encode (gptel--request-data
|
(gptel--json-encode (gptel--request-data
|
||||||
gptel-backend (plist-get info :prompt)))
|
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)
|
||||||
(gptel--log (json-encode url-request-extra-headers) "request headers"))
|
(gptel--log (gptel--json-encode url-request-extra-headers) "request headers"))
|
||||||
(gptel--log url-request-data "request body"))
|
(gptel--log url-request-data "request body"))
|
||||||
(url-retrieve (let ((backend-url (gptel-backend-url gptel-backend)))
|
(url-retrieve (let ((backend-url (gptel-backend-url gptel-backend)))
|
||||||
(if (functionp backend-url)
|
(if (functionp backend-url)
|
||||||
|
@ -1169,20 +1170,16 @@ See `gptel-curl--get-response' for its contents.")
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(goto-char url-http-end-of-headers)
|
(goto-char url-http-end-of-headers)
|
||||||
(when (eq gptel-log-level 'debug)
|
(when (eq gptel-log-level 'debug)
|
||||||
(gptel--log (json-encode (buffer-substring-no-properties (point-min) (point)))
|
(gptel--log (gptel--json-encode (buffer-substring-no-properties (point-min) (point)))
|
||||||
"response headers"))
|
"response headers"))
|
||||||
(gptel--log (buffer-substring-no-properties (point) (point-max))
|
(gptel--log (buffer-substring-no-properties (point) (point-max))
|
||||||
"response body")))
|
"response body")))
|
||||||
(if-let* ((http-msg (string-trim (buffer-substring (line-beginning-position)
|
(if-let* ((http-msg (string-trim (buffer-substring (line-beginning-position)
|
||||||
(line-end-position))))
|
(line-end-position))))
|
||||||
(json-object-type 'plist)
|
|
||||||
(response (progn (goto-char url-http-end-of-headers)
|
(response (progn (goto-char url-http-end-of-headers)
|
||||||
(let ((json-str (decode-coding-string
|
(condition-case nil
|
||||||
(buffer-substring-no-properties (point) (point-max))
|
(gptel--json-read)
|
||||||
'utf-8)))
|
(error 'json-read-error)))))
|
||||||
(condition-case nil
|
|
||||||
(json-read-from-string json-str)
|
|
||||||
(json-readtable-error 'json-read-error))))))
|
|
||||||
(cond
|
(cond
|
||||||
;; FIXME Handle the case where HTTP 100 is followed by HTTP (not 200) BUG #194
|
;; FIXME Handle the case where HTTP 100 is followed by HTTP (not 200) BUG #194
|
||||||
((or (memq url-http-response-status '(200 100))
|
((or (memq url-http-response-status '(200 100))
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
(require 'gptel)
|
(require 'gptel)
|
||||||
(require 'cl-generic)
|
(require 'cl-generic)
|
||||||
|
|
||||||
|
(declare-function json-read "json" ())
|
||||||
|
(defvar json-object-type)
|
||||||
|
|
||||||
;;; Methods for collecting data from HTTP logs
|
;;; Methods for collecting data from HTTP logs
|
||||||
(cl-defgeneric gptel-test--read-response (backend &optional from to))
|
(cl-defgeneric gptel-test--read-response (backend &optional from to))
|
||||||
(cl-defmethod gptel-test--read-response ((_backend gptel-openai) &optional from to)
|
(cl-defmethod gptel-test--read-response ((_backend gptel-openai) &optional from to)
|
||||||
|
@ -11,16 +14,16 @@
|
||||||
(save-restriction
|
(save-restriction
|
||||||
(narrow-to-region from to)
|
(narrow-to-region from to)
|
||||||
(goto-char from)
|
(goto-char from)
|
||||||
(let ((strs) (json-object-type 'plist))
|
(let ((strs))
|
||||||
(while (re-search-forward "^data: *" nil t)
|
(while (re-search-forward "^data: *" nil t)
|
||||||
;; (forward-char)
|
;; (forward-char)
|
||||||
(condition-case-unless-debug err
|
(condition-case-unless-debug err
|
||||||
(thread-first
|
(thread-first
|
||||||
(json-parse-buffer :object-type 'plist)
|
(gptel--json-read :object-type 'plist)
|
||||||
;; (json-read)
|
;; (json-read)
|
||||||
(map-nested-elt '(:choices 0 :delta :content))
|
(map-nested-elt '(:choices 0 :delta :content))
|
||||||
(push strs))
|
(push strs))
|
||||||
(json-readtable-error strs)
|
(error strs)
|
||||||
(:success strs)))
|
(:success strs)))
|
||||||
(setq strs (delq nil (nreverse strs))))))
|
(setq strs (delq nil (nreverse strs))))))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue