branch: externals/ellama
commit 025c56b0543cc70dfcfe38e646a743de3964e6c7
Merge: dfda86230d d72767e9b8
Author: Sergey Kostyaev <[email protected]>
Commit: GitHub <[email protected]>
Merge pull request #358 from s-kostyaev/add-tools
Add tools support
---
NEWS.org | 14 ++
README.org | 13 +-
ellama-tools.el | 556 ++++++++++++++++++++++++++++++++++++++++++++++++++++
ellama-transient.el | 25 ++-
ellama.el | 164 +++++++---------
ellama.info | 66 ++++---
6 files changed, 720 insertions(+), 118 deletions(-)
diff --git a/NEWS.org b/NEWS.org
index 71e3c9d2ce..9a6298c2fc 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -1,3 +1,17 @@
+* Version 1.10.0
+- Added comprehensive tool support, enabling filesystem operations, shell
+ commands, and utilities through LLM tools, including tool registration, file
+ operations, directory tree exploration, and date/time utilities.
+- Integrated tool system into ~ellama.el~, adding dependencies, modifying
+ session creation, and refactoring error and response handlers.
+- Implemented a new transient menu for managing tools with enable/disable
+ functionalities, accessible via the main transient interface.
+- Added user confirmation system for potentially dangerous operations like file
+ and shell command executions.
+- Refactored tool confirmation logic and response handling in ~ellama-tools.el~
+ and ~ellama.el~ for improved structure and JSON encoding.
+- Fixed directory tree function errors, ensuring proper checks and formatted
+ output.
* Version 1.9.1
- Restricted template variable expansion to self-contained variables that don't
span multiple lines to avoid conflicts with code snippets.
diff --git a/README.org b/README.org
index be991be778..79cfa69fda 100644
--- a/README.org
+++ b/README.org
@@ -222,7 +222,18 @@ More sofisticated configuration example:
Options include streaming for real-time output, async for asynchronous
processing, or skipping every N messages to reduce resource usage.
- ~ellama-blueprint-variable-regexp~: Regular expression to match blueprint
- variables like ~{var_name}~.
+ variables like ~{var_name}~.
+- ~ellama-tools-enable-by-name~: Enable a specific tool by its name. Use
+ this command to activate individual tools. Requires the tool name as input.
+- ~ellama-tools-enable-all~: Enable all available tools at once. Use this
+ command to activate every tool in the system for comprehensive
functionality
+ without manual selection.
+- ~ellama-tools-disable-by-name~: Disable a specific tool by its name. Use
+ this command to deactivate individual tools when their functionality is no
+ longer needed.
+- ~ellama-tools-disable-all~: Disable all enabled tools simultaneously. Use
+ this command to reset the system to a minimal state, ensuring no tools are
+ active.
* Keymap
diff --git a/ellama-tools.el b/ellama-tools.el
new file mode 100644
index 0000000000..cf7fbf7518
--- /dev/null
+++ b/ellama-tools.el
@@ -0,0 +1,556 @@
+;;; ellama-tools.el --- Working with tools -*- lexical-binding: t;
package-lint-main-file: "ellama.el"; -*-
+
+;; Copyright (C) 2023-2025 Free Software Foundation, Inc.
+
+;; Author: Sergey Kostyaev <[email protected]>
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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, or (at your option)
+;; any later version.
+
+;; This file 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; Ellama is a tool for interacting with large language models from Emacs.
+;; It allows you to ask questions and receive responses from the
+;; LLMs. Ellama can perform various tasks such as translation, code
+;; review, summarization, enhancing grammar/spelling or wording and
+;; more through the Emacs interface. Ellama natively supports streaming
+;; output, making it effortless to use with your preferred text editor.
+;;
+
+;;; Code:
+(require 'project)
+
+(defvar ellama-tools-available nil
+ "Alist containing all registered tools.")
+
+(defvar ellama-tools-enabled nil
+ "List of tools that have been enabled.")
+
+(defvar-local ellama-tools-confirm-allowed (make-hash-table)
+ "Contains hash table of allowed functions.
+Key is a function name symbol. Value is a boolean t.")
+
+(defun ellama-tools-confirm (prompt function &optional args)
+ "Ask user for confirmation before calling FUNCTION with ARGS.
+PROMPT is the message to display to the user. FUNCTION is the function
+to call if confirmed. ARGS are the arguments to pass to FUNCTION. User
+can approve once (y), approve for all future calls (a), or forbid (n).
+Returns the result of FUNCTION if approved, \"Forbidden by the user\"
+otherwise."
+ (let ((confirmation (gethash function ellama-tools-confirm-allowed nil)))
+ (cond
+ ;; If user has approved all calls, just execute the function
+ ((when confirmation
+ (if args
+ (apply function args)
+ (funcall function))))
+ ;; Otherwise, ask for confirmation
+ (t
+ (let* ((answer (read-char-choice
+ (format "%s (y)es, (a)lways, (n)o: " prompt)
+ '(?y ?a ?n)))
+ (result (cond
+ ;; Yes - execute function once
+ ((eq answer ?y)
+ (if args
+ (apply function args)
+ (funcall function)))
+ ;; Always - remember approval and execute function
+ ((eq answer ?a)
+ (puthash function t ellama-tools-confirm-allowed)
+ (if args
+ (apply function args)
+ (funcall function)))
+ ;; No - return nil
+ ((eq answer ?n)
+ "Forbidden by the user"))))
+ (if (stringp result)
+ result
+ (json-encode result)))))))
+
+(defun ellama-tools--enable-by-name (name)
+ "Add to `ellama-tools-enabled' each tool that matches NAME."
+ (let* ((tool-name name)
+ (tool (seq-find (lambda (tool) (string= tool-name (llm-tool-name
tool)))
+ ellama-tools-available)))
+ (add-to-list 'ellama-tools-enabled tool)
+ nil))
+
+(defun ellama-tools-enable-by-name (&optional name)
+ "Add to `ellama-tools-enabled' each tool that matches NAME."
+ (interactive)
+ (let ((tool-name (or name
+ (completing-read
+ "Tool to enable: "
+ (cl-remove-if
+ (lambda (tname)
+ (cl-find-if
+ (lambda (tool)
+ (string= tname (llm-tool-name tool)))
+ ellama-tools-enabled))
+ (mapcar (lambda (tool) (llm-tool-name tool))
ellama-tools-available))))))
+ (ellama-tools--enable-by-name tool-name)))
+
+(defun ellama-tools-enable-by-name-tool (name)
+ "Add to `ellama-tools-enabled' each tool that matches NAME."
+ (ellama-tools-confirm
+ (format "Allow enabling tool %s?" name)
+ 'ellama-tools--enable-by-name
+ (list name)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-enable-by-name-tool
+ :name
+ "enable_tool"
+ :args
+ (list '(:name
+ "name"
+ :type
+ string
+ :description
+ "Name of the tool to enable."))
+ :description
+ "Enable each tool that matches NAME. You need to reply to the
user before using newly enabled tool."))
+
+(defun ellama-tools--disable-by-name (name)
+ "Remove from `ellama-tools-enabled' each tool that matches NAME."
+ (let* ((tool (seq-find (lambda (tool) (string= name (llm-tool-name tool)))
+ ellama-tools-enabled)))
+ (setq ellama-tools-enabled (seq-remove (lambda (enabled-tool) (eq
enabled-tool tool))
+ ellama-tools-enabled))))
+
+(defun ellama-tools-disable-by-name (&optional name)
+ "Remove from `ellama-tools-enabled' each tool that matches NAME."
+ (interactive)
+ (let* ((tool-name (or name
+ (completing-read
+ "Tool to disable: "
+ (mapcar (lambda (tool) (llm-tool-name tool))
ellama-tools-enabled)))))
+ (ellama-tools--disable-by-name tool-name)))
+
+(defun ellama-tools-disable-by-name-tool (name)
+ "Remove from `ellama-tools-enabled' each tool that matches NAME."
+ (ellama-tools-confirm
+ (format "Allow disabling tool %s?" name)
+ 'ellama-tools--disable-by-name
+ (list name)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-disable-by-name-tool
+ :name
+ "disable_tool"
+ :args
+ (list '(:name
+ "name"
+ :type
+ string
+ :description
+ "Name of the tool to disable."))
+ :description
+ "Disable each tool that matches NAME."))
+
+(defun ellama-tools-enable-all ()
+ "Enable all available tools."
+ (interactive)
+ (setq ellama-tools-enabled ellama-tools-available))
+
+(defun ellama-tools-disable-all ()
+ "Disable all enabled tools."
+ (interactive)
+ (setq ellama-tools-enabled nil))
+
+(defun ellama-tools--read-file (path)
+ "Read the file located at the specified PATH."
+ (if (not (file-exists-p path))
+ (format "File %s doesn't exists." path)
+ (with-temp-buffer
+ (insert-file-contents-literally path)
+ (buffer-string))))
+
+(defun ellama-tools-read-file (path)
+ "Read the file located at the specified PATH."
+ (ellama-tools-confirm
+ (format "Allow reading file %s?" path)
+ 'ellama-tools--read-file
+ (list path)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-read-file
+ :name
+ "read_file"
+ :args
+ (list '(:name
+ "path"
+ :type
+ string
+ :description
+ "Path to the file."))
+ :description
+ "Read the file located at the specified PATH."))
+
+(defun ellama-tools--write-file (path content)
+ "Write CONTENT to the file located at the specified PATH."
+ (with-temp-buffer
+ (insert content)
+ (setq buffer-file-name path)
+ (save-buffer)))
+
+(defun ellama-tools-write-file (path content)
+ "Write CONTENT to the file located at the specified PATH."
+ (ellama-tools-confirm
+ (format "Allow writing file %s?" path)
+ 'ellama-tools--write-file
+ (list path content)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-write-file
+ :name
+ "write_file"
+ :args
+ (list '(:name
+ "path"
+ :type
+ string
+ :description
+ "Path to the file.")
+ '(:name
+ "content"
+ :type
+ string
+ :description
+ "Content to write to the file."))
+ :description
+ "Write CONTENT to the file located at the specified PATH."))
+
+(defun ellama-tools--directory-tree (dir &optional depth)
+ "Return a string representing the directory tree under DIR.
+DEPTH is the current recursion depth, used internally."
+ (if (not (file-exists-p dir))
+ (format "Directory %s doesn't exists" dir)
+ (let ((indent (make-string (* (or depth 0) 2) ? ))
+ (tree ""))
+ (dolist (f (sort (cl-remove-if
+ (lambda (f)
+ (string-prefix-p "." f))
+ (directory-files dir))
+ #'string-lessp))
+ (let* ((full (expand-file-name f dir))
+ (name (file-name-nondirectory f))
+ (type (if (file-directory-p full) "|-" "`-"))
+ (line (concat indent type name "\n")))
+ (setq tree (concat tree line))
+ (when (file-directory-p full)
+ (setq tree (concat tree
+ (ellama-tools--directory-tree full (+ (or depth
0) 1)))))))
+ tree)))
+
+(defun ellama-tools-directory-tree (dir)
+ "Return a string representing the directory tree under DIR."
+ (ellama-tools-confirm
+ (format "Allow LLM to see %s directory tree?" dir)
+ 'ellama-tools--directory-tree
+ (list dir nil)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-directory-tree
+ :name
+ "directory_tree"
+ :args
+ (list '(:name
+ "dir"
+ :type
+ string
+ :description
+ "Directory path to generate tree for."))
+ :description
+ "Return a string representing the directory tree under DIR."))
+
+(defun ellama-tools--move-file (path newpath)
+ "Move the file from the specified PATH to the NEWPATH."
+ (if (and (file-exists-p path)
+ (not (file-exists-p newpath)))
+ (progn
+ (rename-file path newpath))
+ (error "Cannot move file: source file does not exist or destination
already exists")))
+
+(defun ellama-tools-move-file (path newpath)
+ "Move the file from the specified PATH to the NEWPATH."
+ (ellama-tools-confirm
+ (format "Allow moving file %s to %s?" path newpath)
+ 'ellama-tools--move-file
+ (list path newpath)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-move-file
+ :name
+ "move_file"
+ :args
+ (list '(:name
+ "path"
+ :type
+ string
+ :description
+ "Current path of the file.")
+ '(:name
+ "newpath"
+ :type
+ string
+ :description
+ "New path for the file."))
+ :description
+ "Move the file from the specified PATH to the NEWPATH."))
+
+(defun ellama-tools--edit-file (path oldcontent newcontent)
+ "Edit file located at PATH.
+Replace OLDCONTENT with NEWCONTENT."
+ (let ((content (with-temp-buffer
+ (insert-file-contents-literally path)
+ (buffer-string))))
+ (when (string-match oldcontent content)
+ (with-temp-buffer
+ (insert content)
+ (goto-char (match-beginning 0))
+ (delete-region (match-beginning 0) (match-end 0))
+ (insert newcontent)
+ (write-region (point-min) (point-max) path)))))
+
+(defun ellama-tools-edit-file (path oldcontent newcontent)
+ "Edit file located at PATH.
+Replace OLDCONTENT with NEWCONTENT."
+ (ellama-tools-confirm
+ (format "Allow editing file %s?" path)
+ 'ellama-tools--edit-file
+ (list path oldcontent newcontent)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-edit-file
+ :name
+ "edit_file"
+ :args
+ (list '(:name
+ "path"
+ :type
+ string
+ :description
+ "Path to the file.")
+ '(:name
+ "oldcontent"
+ :type
+ string
+ :description
+ "Old content to be replaced.")
+ '(:name
+ "newcontent"
+ :type
+ string
+ :description
+ "New content to replace with."))
+ :description
+ "Edit file located at PATH. Replace OLDCONTENT with
NEWCONTENT."))
+
+(defun ellama-tools--shell-command (cmd)
+ "Execute shell command CMD."
+ (shell-command-to-string cmd))
+
+(defun ellama-tools-shell-command (cmd)
+ "Execute shell command CMD."
+ (ellama-tools-confirm
+ (format "Allow executing shell command %s?" cmd)
+ 'ellama-tools--shell-command
+ (list cmd)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-shell-command
+ :name
+ "shell_command"
+ :args
+ (list '(:name
+ "cmd"
+ :type
+ string
+ :description
+ "Shell command to execute."))
+ :description
+ "Execute shell command CMD."))
+
+(defun ellama-tools--grep (search-string)
+ "Grep SEARCH-STRING in directory files."
+ (shell-command-to-string (format "find . -type f -exec grep --color=never
-nh -e %s \\{\\} +" search-string)))
+
+(defun ellama-tools-grep (search-string)
+ "Grep SEARCH-STRING in directory files."
+ (ellama-tools-confirm
+ (format "Allow grepping for %s in directory files?" search-string)
+ 'ellama-tools--grep
+ (list search-string)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-grep
+ :name
+ "grep"
+ :args
+ (list '(:name
+ "search-string"
+ :type
+ string
+ :description
+ "String to search for."))
+ :description
+ "Grep SEARCH-STRING in directory files."))
+
+(defun ellama-tools--list ()
+ "List all available tools."
+ (json-encode (mapcar
+ (lambda (tool)
+ `(("name" . ,(llm-tool-name tool))
+ ("description" . ,(llm-tool-description tool))))
+ ellama-tools-available)))
+
+(defun ellama-tools-list ()
+ "List all available tools."
+ (ellama-tools-confirm
+ "Allow LLM to see available tools?"
+ 'ellama-tools--list))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-list
+ :name
+ "list_tools"
+ :args
+ nil
+ :description
+ "List all available tools."))
+
+(defun ellama-tools--search (search-string)
+ "Search available tools that matches SEARCH-STRING."
+ (json-encode
+ (cl-remove-if-not
+ (lambda (item)
+ (or (string-match-p search-string (alist-get "name" item nil nil
'string=))
+ (string-match-p search-string (alist-get "description" item nil nil
'string=))))
+ (mapcar
+ (lambda (tool)
+ `(("name" . ,(llm-tool-name tool))
+ ("description" . ,(llm-tool-description tool))))
+ ellama-tools-available))))
+
+(defun ellama-tools-search (search-string)
+ "Search available tools that matches SEARCH-STRING."
+ (ellama-tools-confirm
+ (format "Allow searching tools with pattern %s?" search-string)
+ 'ellama-tools--search
+ (list search-string)))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-search
+ :name
+ "search_tools"
+ :args
+ (list '(:name
+ "search-string"
+ :type
+ string
+ :description
+ "String to search for in tool names or descriptions."))
+ :description
+ "Search available tools that matches SEARCH-STRING."))
+
+(defun ellama-tools--today ()
+ "Return current date."
+ (format-time-string "%Y-%m-%d"))
+
+(defun ellama-tools-today ()
+ "Return current date."
+ (ellama-tools-confirm
+ "Allow reading current date?"
+ 'ellama-tools--today))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-today
+ :name
+ "today"
+ :args
+ nil
+ :description
+ "Return current date."))
+
+(defun ellama-tools--now ()
+ "Return current date, time and timezone."
+ (format-time-string "%Y-%m-%d %H:%M:%S %Z"))
+
+(defun ellama-tools-now ()
+ "Return current date, time and timezone."
+ (ellama-tools-confirm
+ "Allow reading current date, time and timezone?"
+ 'ellama-tools--now))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-now
+ :name
+ "now"
+ :args
+ nil
+ :description
+ "Return current date, time and timezone."))
+
+(defun ellama-tools--project-root ()
+ "Return current project root directory."
+ (when (project-current)
+ (project-root (project-current))))
+
+(defun ellama-tools-project-root ()
+ "Return current project root directory."
+ (ellama-tools-confirm
+ "Allow LLM to know the project root directory?"
+ 'ellama-tools--project-root))
+
+(add-to-list
+ 'ellama-tools-available
+ (llm-make-tool :function
+ 'ellama-tools-project-root
+ :name
+ "project_root"
+ :args
+ nil
+ :description
+ "Return current project root directory."))
+
+(provide 'ellama-tools)
+;;; ellama-tools.el ends here
diff --git a/ellama-transient.el b/ellama-transient.el
index 81c9a56feb..77a9d6a445 100644
--- a/ellama-transient.el
+++ b/ellama-transient.el
@@ -430,6 +430,28 @@ ARGS used for transient arguments."
("k" "Kill" ellama-kill-current-buffer)
("q" "Quit" transient-quit-one)]])
+;;;###autoload (autoload 'ellama-transient-tools-menu "ellama-transient" nil t)
+(transient-define-prefix ellama-transient-tools-menu ()
+ ["Tools Commands"
+ :description (lambda ()
+ (format "Enabled tools:\n%s"
+ (string-join (mapcar (lambda (tool)
+ (llm-tool-name tool))
+ ellama-tools-enabled)
+ " ")))
+ ["Tools"
+ ("e" "Enable tool" ellama-tools-enable-by-name
+ :transient t)
+ ("E" "Enable all tools" ellama-tools-enable-all
+ :transient t)
+ ("d" "Disable tool" ellama-tools-disable-by-name
+ :transient t)
+ ("D" "Disable all tools" ellama-tools-disable-all
+ :transient t)]
+ ["Quit"
+ ("k" "Kill" ellama-kill-current-buffer)
+ ("q" "Quit" transient-quit-one)]])
+
(transient-define-suffix ellama-transient-chat (&optional args)
"Chat with Ellama. ARGS used for transient arguments."
(interactive (list (transient-args transient-current-command)))
@@ -450,7 +472,8 @@ ARGS used for transient arguments."
["Main"
[("c" "Chat" ellama-transient-chat)
("b" "Chat with blueprint" ellama-blueprint-select)
- ("B" "Blueprint Commands" ellama-transient-blueprint-menu)]
+ ("B" "Blueprint Commands" ellama-transient-blueprint-menu)
+ ("T" "Tools Commands" ellama-transient-tools-menu)]
[("a" "Ask Commands" ellama-transient-ask-menu)
("C" "Code Commands" ellama-transient-code-menu)]]
["Text"
diff --git a/ellama.el b/ellama.el
index e60167da14..ecc5d6c53f 100644
--- a/ellama.el
+++ b/ellama.el
@@ -6,7 +6,7 @@
;; URL: http://github.com/s-kostyaev/ellama
;; Keywords: help local tools
;; Package-Requires: ((emacs "28.1") (llm "0.24.0") (plz "0.8") (transient
"0.7") (compat "29.1"))
-;; Version: 1.9.1
+;; Version: 1.10.0
;; SPDX-License-Identifier: GPL-3.0-or-later
;; Created: 8th Oct 2023
@@ -40,6 +40,7 @@
(require 'llm-provider-utils)
(require 'compat)
(eval-when-compile (require 'rx))
+(require 'ellama-tools)
(defgroup ellama nil
"Tool for interacting with LLMs."
@@ -859,7 +860,8 @@ Defaults to md, but supports org. Depends on
`ellama-major-mode'."
"Create new ellama session with unique id.
Provided PROVIDER and PROMPT will be used in new session.
If EPHEMERAL non nil new session will not be associated with any file."
- (let* ((name (ellama-generate-name provider 'ellama prompt))
+ (let* ((dir default-directory)
+ (name (ellama-generate-name provider 'ellama prompt))
(count 1)
(name-with-suffix (format "%s %d" name count))
(id (if (and (not (ellama-get-session-buffer name))
@@ -889,7 +891,7 @@ If EPHEMERAL non nil new session will not be associated
with any file."
(setq ellama--current-session-id id)
(puthash id buffer ellama--active-sessions)
(with-current-buffer buffer
- (setq default-directory ellama-sessions-directory)
+ (setq default-directory dir)
(funcall ellama-major-mode)
(setq ellama--current-session session)
(ellama-session-mode +1))
@@ -1361,6 +1363,66 @@ REASONING-BUFFER is a buffer for reasoning."
(concat "</think>\n" (string-trim text))
(string-trim text))))))))
+(defun ellama--error-handler (buffer errcb)
+ "Error handler function.
+BUFFER is the current ellama buffer.
+ERRCB is an error callback."
+ (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))))
+
+(defun ellama--response-handler (handler reasoning-buffer buffer donecb errcb
provider llm-prompt async)
+ "Response handler function.
+HANDLER handles text insertion.
+REASONING-BUFFER used for reasoning output.
+BUFFER is the current ellama buffer.
+DONECB is a done callback.
+PROVIDER is an llm provider.
+ERRCB is an error callback.
+LLM-PROMPT is current llm prompt.
+ASYNC flag is for asyncronous requests."
+ (lambda (response)
+ (let ((text (plist-get response :text))
+ (reasoning (plist-get response :reasoning))
+ (tool-result (plist-get response :tool-results)))
+ (if tool-result
+ (if async
+ (llm-chat-async
+ provider
+ llm-prompt
+ (ellama--response-handler handler reasoning-buffer buffer donecb
errcb provider llm-prompt async)
+ (ellama--error-handler buffer errcb)
+ t)
+ (llm-chat-streaming
+ provider
+ llm-prompt
+ handler
+ (ellama--response-handler handler reasoning-buffer buffer donecb
errcb provider llm-prompt async)
+ (ellama--error-handler buffer errcb)
+ t))
+ (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))))))
+
(defun ellama-stream (prompt &rest args)
"Query ellama for PROMPT.
ARGS contains keys for fine control.
@@ -1430,8 +1492,10 @@ failure (with BUFFER current).
system 'system))
(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))))
+ (llm-make-chat-prompt prompt-with-ctx :context
system
+ :tools
ellama-tools-enabled)))
+ (llm-make-chat-prompt prompt-with-ctx :context system
+ :tools ellama-tools-enabled))))
(with-current-buffer reasoning-buffer
(org-mode))
(with-current-buffer buffer
@@ -1450,67 +1514,15 @@ failure (with BUFFER current).
('async (llm-chat-async
provider
llm-prompt
- (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)))
+ (ellama--response-handler handler
reasoning-buffer buffer donecb errcb provider llm-prompt t)
+ (ellama--error-handler buffer errcb)
t))
('streaming (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)))
+ (ellama--response-handler handler
reasoning-buffer buffer donecb errcb provider llm-prompt nil)
+ (ellama--error-handler buffer errcb)
t))
((pred integerp)
(let* ((cnt 0)
@@ -1525,34 +1537,8 @@ failure (with BUFFER current).
provider
llm-prompt
skip-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)))
+ (ellama--response-handler handler
reasoning-buffer buffer donecb errcb provider llm-prompt t)
+ (ellama--error-handler buffer errcb)
t))))))
(with-current-buffer buffer
(setq ellama--current-request request)))))))
diff --git a/ellama.info b/ellama.info
index 75001aa9fa..08b080ed55 100644
--- a/ellama.info
+++ b/ellama.info
@@ -326,6 +326,18 @@ File: ellama.info, Node: Commands, Next: Keymap, Prev:
Installation, Up: Top
resource usage.
• ‘ellama-blueprint-variable-regexp’: Regular expression to match
blueprint variables like ‘{var_name}’.
+ • ‘ellama-tools-enable-by-name’: Enable a specific tool by its name.
+ Use this command to activate individual tools. Requires the tool
+ name as input.
+ • ‘ellama-tools-enable-all’: Enable all available tools at once. Use
+ this command to activate every tool in the system for comprehensive
+ functionality without manual selection.
+ • ‘ellama-tools-disable-by-name’: Disable a specific tool by its
+ name. Use this command to deactivate individual tools when their
+ functionality is no longer needed.
+ • ‘ellama-tools-disable-all’: Disable all enabled tools
+ simultaneously. Use this command to reset the system to a minimal
+ state, ensuring no tools are active.
File: ellama.info, Node: Keymap, Next: Configuration, Prev: Commands, Up:
Top
@@ -1424,33 +1436,33 @@ Tag Table:
Node: Top1379
Node: Installation3613
Node: Commands8621
-Node: Keymap15330
-Node: Configuration18163
-Node: Context Management23762
-Node: Transient Menus for Context Management24670
-Node: Managing the Context26284
-Node: Considerations27059
-Node: Minor modes27652
-Node: ellama-context-header-line-mode29640
-Node: ellama-context-header-line-global-mode30465
-Node: ellama-context-mode-line-mode31185
-Node: ellama-context-mode-line-global-mode32033
-Node: Ellama Session Header Line Mode32737
-Node: Enabling and Disabling33306
-Node: Customization33753
-Node: Ellama Session Mode Line Mode34041
-Node: Enabling and Disabling (1)34626
-Node: Customization (1)35073
-Node: Using Blueprints35367
-Node: Key Components of Ellama Blueprints35986
-Node: Creating and Managing Blueprints36593
-Node: Variable Management37574
-Node: Keymap and Mode38043
-Node: Transient Menus38979
-Node: Running Blueprints programmatically39525
-Node: Acknowledgments40112
-Node: Contributions40825
-Node: GNU Free Documentation License41209
+Node: Keymap16060
+Node: Configuration18893
+Node: Context Management24492
+Node: Transient Menus for Context Management25400
+Node: Managing the Context27014
+Node: Considerations27789
+Node: Minor modes28382
+Node: ellama-context-header-line-mode30370
+Node: ellama-context-header-line-global-mode31195
+Node: ellama-context-mode-line-mode31915
+Node: ellama-context-mode-line-global-mode32763
+Node: Ellama Session Header Line Mode33467
+Node: Enabling and Disabling34036
+Node: Customization34483
+Node: Ellama Session Mode Line Mode34771
+Node: Enabling and Disabling (1)35356
+Node: Customization (1)35803
+Node: Using Blueprints36097
+Node: Key Components of Ellama Blueprints36716
+Node: Creating and Managing Blueprints37323
+Node: Variable Management38304
+Node: Keymap and Mode38773
+Node: Transient Menus39709
+Node: Running Blueprints programmatically40255
+Node: Acknowledgments40842
+Node: Contributions41555
+Node: GNU Free Documentation License41939
End Tag Table