branch: externals/ellama
commit f6cc81e9992d981523823d793def4bede5efe8ba
Author: Sergey Kostyaev <[email protected]>
Commit: Sergey Kostyaev <[email protected]>
Refactor text insertion and handling in `ellama.el`
Refactored the text insertion and handling logic in `ellama.el`. Introduced
new
functions `ellama--insert` and `ellama--handle-partial` to manage text
streaming
and partial responses more effectively. Updated the `ellama-stream`
function to
utilize these new helpers, improving modularity and maintainability.
Additionally, enhanced reasoning handling by showing it in separate buffer.
---
ellama.el | 193 ++++++++++++++++++++++++++++++---------------------
tests/test-ellama.el | 6 +-
2 files changed, 115 insertions(+), 84 deletions(-)
diff --git a/ellama.el b/ellama.el
index 653b76cd4a..e8d4d0e0fd 100644
--- a/ellama.el
+++ b/ellama.el
@@ -5,7 +5,7 @@
;; Author: Sergey Kostyaev <[email protected]>
;; URL: http://github.com/s-kostyaev/ellama
;; Keywords: help local tools
-;; Package-Requires: ((emacs "28.1") (llm "0.22.0") (plz "0.8") (transient
"0.7") (compat "29.1"))
+;; Package-Requires: ((emacs "28.1") (llm "0.24.0") (plz "0.8") (transient
"0.7") (compat "29.1"))
;; Version: 1.5.6
;; SPDX-License-Identifier: GPL-3.0-or-later
;; Created: 8th Oct 2023
@@ -1159,6 +1159,74 @@ Otherwire return current active session."
(defvar ellama-global-system nil)
+(defvar-local ellama--stop-scroll nil)
+
+(defun ellama--insert (buffer point filter)
+ "Insert text during streaming.
+
+Works inside BUFFER starting at POINT.
+If POINT is nil, current point will be used.
+FILTER is a function for text transformation."
+ (with-current-buffer buffer
+ (let ((start (make-marker))
+ (end (make-marker))
+ (distance-to-end (- (point-max) (point))))
+ (ellama-set-markers start end (or point (point)))
+ (lambda (text)
+ ;; Erase and insert the new text between the marker cons.
+ (with-current-buffer buffer
+ ;; Manually save/restore point as save-excursion doesn't
+ ;; restore the point into the middle of replaced text.
+ (let* ((pt (point))
+ (new-distance-to-end (- (point-max) (point)))
+ (new-pt))
+ (save-excursion
+ (if (and (eq (window-buffer (selected-window))
+ buffer)
+ (not (equal distance-to-end new-distance-to-end)))
+ (setq ellama--stop-scroll t)
+ (setq ellama--stop-scroll nil))
+ (goto-char start)
+ (delete-region start end)
+ (insert (funcall filter text))
+ (when (and ellama-fill-paragraphs
+ (pcase ellama-fill-paragraphs
+ ((cl-type function) (funcall ellama-fill-paragraphs))
+ ((cl-type boolean) ellama-fill-paragraphs)
+ ((cl-type list) (and (apply #'derived-mode-p
+ ellama-fill-paragraphs)
+ (not (equal major-mode
'org-mode))))))
+ (fill-region start (point)))
+ (setq new-pt (point)))
+ (if (and ellama-auto-scroll (not ellama--stop-scroll))
+ (ellama--scroll buffer new-pt)
+ (goto-char pt)))
+ (undo-amalgamate-change-group ellama--change-group))))))
+
+(defun ellama--handle-partial (insert-text insert-reasoning reasoning-buffer)
+ "Handle partial llm callback.
+INSERT-TEXT is a function for text insertion.
+INSERT-REASONING is a function for reasoning insertion.
+REASONING-BUFFER is a buffer for reasoning."
+ (lambda (response)
+ (let ((text (plist-get response :text))
+ (reasoning (plist-get response :reasoning)))
+ (funcall
+ insert-text
+ (concat
+ (when reasoning
+ (if
+ (or (not ellama-output-remove-reasoning)
+ ellama--current-session)
+ (format "<think>%s</think>" reasoning)
+ (progn
+ (with-current-buffer reasoning-buffer
+ (funcall insert-reasoning reasoning)
+ (display-buffer reasoning-buffer))
+ nil)))
+ (when text
+ (string-trim text)))))))
+
(defun ellama-stream (prompt &rest args)
"Query ellama for PROMPT.
ARGS contains keys for fine control.
@@ -1204,6 +1272,8 @@ failure (with BUFFER current).
(when (ellama-session-p session)
(ellama-get-session-buffer (ellama-session-id session)))
(current-buffer)))
+ (reasoning-buffer (get-buffer-create
+ (concat (make-temp-name "*ellama-reasoning-") "*")))
(point (or (plist-get args :point)
(with-current-buffer buffer (point))))
(filter (or (plist-get args :filter) #'identity))
@@ -1227,93 +1297,54 @@ failure (with BUFFER current).
(ellama-session-prompt session))
(setf (ellama-session-prompt session)
(llm-make-chat-prompt prompt-with-ctx :context
system)))
- (llm-make-chat-prompt prompt-with-ctx :context system)))
- (stop-scroll))
+ (llm-make-chat-prompt prompt-with-ctx :context system))))
+ (with-current-buffer reasoning-buffer
+ (org-mode))
(with-current-buffer buffer
(ellama-request-mode +1)
- (let* ((start (make-marker))
- (end (make-marker))
- (distance-to-end (- (point-max) (point)))
- (new-pt)
- (insert-text
- (lambda (text)
- ;; Erase and insert the new text between the marker cons.
- (with-current-buffer buffer
- ;; Manually save/restore point as save-excursion doesn't
- ;; restore the point into the middle of replaced text.
- (let* ((pt (point))
- (new-distance-to-end (- (point-max) (point))))
- (save-excursion
- (if (and (eq (window-buffer (selected-window))
- buffer)
- (not (equal distance-to-end
new-distance-to-end)))
- (setq stop-scroll t)
- (setq stop-scroll nil))
- (goto-char start)
- (delete-region start end)
- (insert (funcall filter text))
- (when (and ellama-fill-paragraphs
- (pcase ellama-fill-paragraphs
- ((cl-type function) (funcall
ellama-fill-paragraphs))
- ((cl-type boolean) ellama-fill-paragraphs)
- ((cl-type list) (and (apply #'derived-mode-p
-
ellama-fill-paragraphs)
- (not (equal major-mode
'org-mode))))))
- (fill-region start (point)))
- (setq new-pt (point)))
- (if (and ellama-auto-scroll (not stop-scroll))
- (ellama--scroll buffer new-pt)
- (goto-char pt)))
- (undo-amalgamate-change-group ellama--change-group)))))
+ (let* ((insert-text
+ (ellama--insert buffer point filter))
+ (insert-reasoning
+ (ellama--insert reasoning-buffer nil
#'ellama--translate-markdown-to-org-filter)))
(setq ellama--change-group (prepare-change-group))
(activate-change-group ellama--change-group)
- (ellama-set-markers start end point)
(when ellama-spinner-enabled
(require 'spinner)
(spinner-start ellama-spinner-type))
- (let ((request (llm-chat-streaming
- provider
- llm-prompt
- insert-text
- (lambda (text)
- (funcall insert-text
- (string-trim
- (if (and ellama-output-remove-reasoning
- (not session))
- (ellama-remove-reasoning text)
- text)))
- (with-current-buffer buffer
- (accept-change-group ellama--change-group)
- (when ellama-spinner-enabled
- (spinner-stop))
- (if (and (listp donecb)
- (functionp (car donecb)))
- (mapc (lambda (fn) (funcall fn text))
- donecb)
- (funcall donecb text))
- (when ellama-session-hide-org-quotes
- (ellama-collapse-org-quotes))
- (when (and ellama--current-session
- ellama-session-remove-reasoning)
- (mapc (lambda (interaction)
- (setf (llm-chat-prompt-interaction-content
- interaction)
- (ellama-remove-reasoning
-
(llm-chat-prompt-interaction-content
- interaction))))
- (llm-chat-prompt-interactions
- (ellama-session-prompt
- ellama--current-session))))
- (setq ellama--current-request nil)
- (ellama-request-mode -1)))
- (lambda (_ msg)
- (with-current-buffer buffer
- (cancel-change-group ellama--change-group)
- (when ellama-spinner-enabled
- (spinner-stop))
- (funcall errcb msg)
- (setq ellama--current-request nil)
- (ellama-request-mode -1))))))
+ (let* ((handler (ellama--handle-partial insert-text insert-reasoning
reasoning-buffer))
+ (request (llm-chat-streaming
+ provider
+ llm-prompt
+ handler
+ (lambda (response)
+ (let ((text (plist-get response :text))
+ (reasoning (plist-get response :reasoning)))
+ (funcall handler response)
+ (when (or ellama--current-session
+ (not reasoning))
+ (kill-buffer reasoning-buffer))
+ (with-current-buffer buffer
+ (accept-change-group ellama--change-group)
+ (when ellama-spinner-enabled
+ (spinner-stop))
+ (if (and (listp donecb)
+ (functionp (car donecb)))
+ (mapc (lambda (fn) (funcall fn text))
+ donecb)
+ (funcall donecb text))
+ (when ellama-session-hide-org-quotes
+ (ellama-collapse-org-quotes))
+ (setq ellama--current-request nil)
+ (ellama-request-mode -1))))
+ (lambda (_ msg)
+ (with-current-buffer buffer
+ (cancel-change-group ellama--change-group)
+ (when ellama-spinner-enabled
+ (spinner-stop))
+ (funcall errcb msg)
+ (setq ellama--current-request nil)
+ (ellama-request-mode -1)))
+ t)))
(with-current-buffer buffer
(setq ellama--current-request request)))))))
diff --git a/tests/test-ellama.el b/tests/test-ellama.el
index 022c0605b0..6ed47a72ed 100644
--- a/tests/test-ellama.el
+++ b/tests/test-ellama.el
@@ -41,11 +41,11 @@
(with-temp-buffer
(insert original)
(cl-letf (((symbol-function 'llm-chat-streaming)
- (lambda (_provider prompt partial-callback response-callback
_error-callback)
+ (lambda (_provider prompt partial-callback response-callback
_error-callback _multi-output)
(should (string-match original (llm-chat-prompt-to-text
prompt)))
(cl-loop for i from 0 to (- (length improved) 1)
- do (funcall partial-callback (substring improved 0
i)))
- (funcall response-callback improved))))
+ do (funcall partial-callback `(:text ,(substring
improved 0 i))))
+ (funcall response-callback `(:text ,improved)))))
(ellama-code-improve)
(should (equal original (buffer-string)))))))