Add interface to the llm command-line tool
This commit is contained in:
parent
ef0a072d18
commit
f4da3fc9e2
14
emacs/.emacs.d/config/init-llm.el
Normal file
14
emacs/.emacs.d/config/init-llm.el
Normal file
@ -0,0 +1,14 @@
|
||||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package llm
|
||||
:straight (:type built-in)
|
||||
:ensure nil
|
||||
:load-path "packages/llm"
|
||||
:defer t
|
||||
:commands (llm-call
|
||||
llm-set-model
|
||||
llm-prompt
|
||||
llm-prompt-buffer
|
||||
llm-prompt-region))
|
||||
|
||||
(provide 'init-llm)
|
@ -131,6 +131,7 @@
|
||||
(require 'init-games)
|
||||
(require 'handwriting)
|
||||
(require 'init-navi)
|
||||
(require 'init-llm)
|
||||
(when (string-equal system-type "darwin")
|
||||
(require 'init-mac))
|
||||
|
||||
|
168
emacs/.emacs.d/packages/llm/llm.el
Normal file
168
emacs/.emacs.d/packages/llm/llm.el
Normal file
@ -0,0 +1,168 @@
|
||||
;;; llm.el --- An Emacs interface to the LLM command-line tool -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2024 Jeremy Isaac Dormitzer
|
||||
|
||||
;; Author: Jeremy Isaac Dormitzer <jeremydormitzer@hummingbird.co>
|
||||
;; Version: 0.1
|
||||
;; Package-Requires: ((emacs "24.3") (s "1.13") (markdown-mode "2.7"))
|
||||
;; Keywords: tools
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This package provides an Emacs interface to the LLM command-line tool.
|
||||
|
||||
;;; Code:
|
||||
(require 's)
|
||||
(require 'markdown-mode)
|
||||
|
||||
(defcustom llm-executable "llm"
|
||||
"Path to the llm executable."
|
||||
:type 'string
|
||||
:group 'llm)
|
||||
|
||||
(defcustom llm-model nil
|
||||
"The llm model to use."
|
||||
:type 'string
|
||||
:group 'llm)
|
||||
|
||||
(defcustom llm-max-tokens 5000
|
||||
"The maximum number of tokens to generate."
|
||||
:type 'integer
|
||||
:group 'llm)
|
||||
|
||||
(defun llm--ensure-executable ()
|
||||
"Ensure that the llm executable is available."
|
||||
(unless (executable-find llm-executable)
|
||||
(error
|
||||
"llm executable not found: see https://llm.datasette.io/en/stable/index.html for installation instructions")))
|
||||
|
||||
(defun llm--process-filter (proc string)
|
||||
(let* ((buffer (process-buffer proc))
|
||||
(window (get-buffer-window buffer))
|
||||
(string (replace-regexp-in-string "\r\n" "\n" string)))
|
||||
(when (buffer-live-p buffer)
|
||||
(with-current-buffer buffer
|
||||
(if (not (mark)) (push-mark))
|
||||
(exchange-point-and-mark) ;Use the mark to represent the cursor location
|
||||
(dolist (char (append string nil))
|
||||
(cond ((char-equal char ?\r)
|
||||
(move-beginning-of-line 1))
|
||||
((char-equal char ?\n)
|
||||
(move-end-of-line 1) (newline))
|
||||
(t
|
||||
(if (/= (point) (point-max)) ;Overwrite character
|
||||
(delete-char 1))
|
||||
(insert char))))
|
||||
(exchange-point-and-mark)))
|
||||
(if window
|
||||
(with-selected-window window
|
||||
(goto-char (point-max))))))
|
||||
|
||||
(define-derived-mode llm-mode markdown-mode "llm"
|
||||
"Major mode for LLM output.")
|
||||
|
||||
(define-key llm-mode-map
|
||||
(kbd "q") #'quit-window)
|
||||
|
||||
(when (fboundp #'evil-define-key)
|
||||
(evil-define-key 'normal llm-mode-map
|
||||
(kbd "q") #'quit-window))
|
||||
|
||||
(defun llm--run-async (name buffer-name &rest llm-args)
|
||||
"Run llm with LLM-ARGS asynchronously.
|
||||
|
||||
The process is named NAME and runs in BUFFER-NAME."
|
||||
(llm--ensure-executable)
|
||||
(when-let ((existing-buffer (get-buffer buffer-name)))
|
||||
(kill-buffer existing-buffer))
|
||||
(let ((proc (make-process :name name
|
||||
:buffer buffer-name
|
||||
:command (cons llm-executable llm-args)
|
||||
:filter #'llm--process-filter)))
|
||||
(with-current-buffer (process-buffer proc)
|
||||
(llm-mode))
|
||||
(set-process-sentinel proc #'ignore)))
|
||||
|
||||
;;;###autoload
|
||||
(cl-defun llm-call (callback &rest llm-args)
|
||||
"Call llm with LLM-ARGS and call CALLBACK with the result."
|
||||
(when-let ((buf (get-buffer " *llm-call*")))
|
||||
(kill-buffer buf))
|
||||
(let ((proc (apply #'llm--run-async "llm-call" " *llm-call*" llm-args)))
|
||||
(set-process-sentinel proc
|
||||
(lambda (proc event)
|
||||
(unless (string= event "finished\n")
|
||||
(error "llm-call failed: %s" (s-trim event)))
|
||||
(with-current-buffer (process-buffer proc)
|
||||
(goto-char (point-min))
|
||||
(funcall callback (s-trim
|
||||
(buffer-substring-no-properties
|
||||
(point)
|
||||
(point-max)))))
|
||||
(kill-buffer (process-buffer proc))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun llm-set-model (model)
|
||||
"Set the LLM model to MODEL."
|
||||
(interactive (list (let* ((model-strings
|
||||
(split-string (shell-command-to-string
|
||||
(format "%s models" (executable-find llm-executable)))
|
||||
"\n" t " "))
|
||||
(models (mapcar (lambda (s)
|
||||
(cons s (cadr (s-match ".*?: \\(.*?\\) -" s))))
|
||||
model-strings))
|
||||
(selected (completing-read "Model: " models)))
|
||||
(alist-get selected models nil nil #'equal))))
|
||||
(setq llm-model model))
|
||||
|
||||
(defun llm--prompt-args (query &rest extra-args)
|
||||
"Return the arguments to prompt LLM with QUERY, appending EXTRA-ARGS."
|
||||
(let* ((args (list "-o" "max_tokens" (number-to-string llm-max-tokens)))
|
||||
(args (if llm-model
|
||||
(append (list "--model" llm-model) args)
|
||||
args))
|
||||
(args (append args extra-args)))
|
||||
(append (list "prompt") args (list query))))
|
||||
|
||||
;;;###autoload
|
||||
(defun llm-prompt (query)
|
||||
"Prompt llm with the QUERY."
|
||||
(interactive "sQuery: ")
|
||||
(apply #'llm--run-async "llm-prompt" "*llm-prompt*" (llm--prompt-args query))
|
||||
(switch-to-buffer "*llm-prompt*"))
|
||||
|
||||
;;;###autoload
|
||||
(defun llm-prompt-buffer (query)
|
||||
"Prompt llm with the contents of the current buffer and the QUERY."
|
||||
(interactive "sQuery: ")
|
||||
(let ((extra-args (list "-s" (buffer-substring-no-properties (point-min) (point-max)))))
|
||||
(apply #'llm--run-async
|
||||
"llm-prompt-buffer"
|
||||
"*llm-prompt-buffer*"
|
||||
(apply #'llm--prompt-args query extra-args))
|
||||
(switch-to-buffer "*llm-prompt-buffer*")))
|
||||
|
||||
(defun llm-prompt-region (query)
|
||||
(interactive "sQuery: ")
|
||||
(let ((extra-args (list "-s" (buffer-substring-no-properties (region-beginning) (region-end)))))
|
||||
(apply #'llm--run-async
|
||||
"llm-prompt-region"
|
||||
"*llm-prompt-region*"
|
||||
(apply #'llm--prompt-args query extra-args))
|
||||
(switch-to-buffer "*llm-prompt-region*")))
|
||||
|
||||
(provide 'llm)
|
||||
;;; llm.el ends here
|
Loading…
Reference in New Issue
Block a user