Skip to content

Conversation

@ultronozm
Copy link
Contributor

No description provided.

@skissue skissue self-assigned this Mar 22, 2025
Copy link
Owner

@skissue skissue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much! One other nitpick—would you be willing to edit the commit messages to adhere to the format of the previous commits (based on Conventional Commits)?

@ultronozm
Copy link
Contributor Author

ultronozm commented Mar 22, 2025 via email

@skissue
Copy link
Owner

skissue commented Mar 22, 2025

for the commit messages, I’d have a slight preference for adopting the GNU ChangeLog style

To be completely honest, I didn't know this was a thing! I've been using Conventional Commits purely out of habit and experience—I'll definitely take a look at the GNU style when I have a moment.

* llm-tool-collection.el (llm-tool-collection-deftool): Redefine macro
to accept a simpler argument format and convert it internally to the
required specs.  Add edebug instrumentation.
(read-file, list-directory, create-file, create-directory): Update all
tool definitions to use the new argument specification format.
* llm-tool-collection.el (llm-tool-collection-deftool): Add support
for arguments after &optional to be marked with :optional t in
argument specs.
(view-buffer): Add new tool for viewing buffer contents with optional
offset and limit parameters.
* llm-tool-collection.el (llm-tool-collection-deftool): Use
llm-tool-collection--make-llm-name when generating parameter names.
* llm-tool-collection.el: Add Imenu support for LLM tools defined with
`llm-tool-collection-deftool'.
* llm-tool-collection.el (llm-tool-collection-deftool): Replace push
with cl-pushnew when registering new tools in
llm-tool-collection--all-tools, to avoid duplication.
* llm-tool-collection.el (llm-tool-collection--name-to-symbol)
(llm-tool-collection--make-llm-name): Move these helper functions
inside an eval-and-compile form to make them available during macro
expansion.
(llm-tool-collection-deftool): Fix variable name conflict by renaming
'name' variable to 'name-spec' for clarity.
(llm-tool-collection-get-category, llm-tool-collection-get-all): Add
autoload cookies.  Escape apostrophes in usage examples.
(view-buffer): Reformat arguments to avoid long lines.
* llm-tool-collection.el (llm-tool-collection-deftool): Use unquoted
function symbol in the :function property of the generated tool spec
instead of a quoted function value.
@ultronozm
Copy link
Contributor Author

for the commit messages, I’d have a slight preference for adopting the GNU ChangeLog style

To be completely honest, I didn't know this was a thing! I've been using Conventional Commits purely out of habit and experience—I'll definitely take a look at the GNU style when I have a moment.

Very good. These are described in the CONTRIBUTE file in my Emacs installation, and maybe also yours. I'll emphasize that I'm just "following the herd" here and have no strong opinions on commit conventions (besides wanting to be lazy and fit in).

* llm-tool-collection.el (llm-tool-collection-post-define-functions):
New variable to hold functions run after tool definition.
(llm-tool-collection-deftool): Call it, passing the tool's plist
definition as argument.
@ultronozm
Copy link
Contributor Author

I made one further tweak just now, converting category strings to symbols. The rationale is that they're enum-like (belonging to some finite collection of values rather than being general strings), so it's more idiomatic (and more efficient, I suppose) to treat them as symbols rather than strings. I'll be happy to revise if you feel otherwise

@ultronozm
Copy link
Contributor Author

OK, one more little tweak (and sorry for the spam). Since :description is an essentially mandatory field for functions and args, it seemed natural to drop the :description key and instead place its value immediately after the symbol name. This makes it look more like an actual docstring...

@skissue
Copy link
Owner

skissue commented Mar 22, 2025

I made one further tweak just now, converting category strings to symbols. The rationale is that they're enum-like (belonging to some finite collection of values rather than being general strings), so it's more idiomatic (and more efficient, I suppose) to treat them as symbols rather than strings. I'll be happy to revise if you feel otherwise

I agree that symbols would be more appropriate. However, the reason that it is a string is because :category is actually a recognized keyword by gptel, and gptel takes it as a string. Thus, our hand is forced here. I am planning to add support for "tags" as an organization scheme as well; since that will be arbitrary, I will likely make those symbols.

OK, one more little tweak (and sorry for the spam). Since :description is an essentially mandatory field for functions and args, it seemed natural to drop the :description key and instead place its value immediately after the symbol name. This makes it look more like an actual docstring...

No worries—I agree, these are good changes!

* llm-tool-collection.el (llm-tool-collection-deftool): Accept a
separate docstring parameter as second argument, for both functions
and their args.
(read-file, list-directory, create-file, create-directory)
(view-buffer): Update all tool definitions to use the new format.
@ultronozm
Copy link
Contributor Author

I agree that symbols would be more appropriate. However, the reason that it is a string is because :category is actually a recognized keyword by gptel, and gptel takes it as a string. Thus, our hand is forced here. I am planning to add support for "tags" as an organization scheme as well; since that will be arbitrary, I will likely make those symbols.

OK, I see, thanks for clarifying. I've switched it back to strings.

I've checked that the changes needed in gptel to work with symbols (or both) are quite straightforward, so if you'd like, I could submit a PR there and see how karthink feels about it. I'd also be happy to leave things as they are, if you prefer.

One point concerning optional arguments might be worth discussing. With the LLM's, I think any of the arguments may be marked as required or optional? With Emacs, the required must come before the optional. I updated the docstring of llm-tool-collection-deftool to hopefully clarify that point a bit.

@ultronozm
Copy link
Contributor Author

One other thought that came to mind is whether it would make sense to include commands in llm-tool-collection that make it easier to work on tools by having frontends update as soon as the deftool form is evaluated.

For instance, the following code should do the trick with gptel:

(defun llm-tool-collection-register-with-gptel (tool-spec)
  "Register a tool defined by TOOL-SPEC with gptel.
TOOL-SPEC is a plist that can be passed to `gptel-make-tool'."
  (when (featurep 'gptel)
    (declare-function gptel-make-tool "gptel")
    (declare-function gptel-tool-name "gptel")
    (defvar gptel-tools)
    (let ((tool (apply #'gptel-make-tool tool-spec)))
      (setq gptel-tools
            (cons tool (seq-remove
                        (lambda (existing)
                          (string= (gptel-tool-name existing)
                                   (gptel-tool-name tool)))
                        gptel-tools))))))

(add-hook 'llm-tool-collection-post-define-functions
          #'llm-tool-collection-register-with-gptel)

It's not clear to me whether such code belongs in the README, in some docstring, as part of llm-tool-collection, as part of gptel, or wherever else, but figured I'd share it for the sake of discussion.

For my personal package ai-org-chat.el, the corresponding code is

(add-hook 'llm-tool-collection-post-define-functions #'ai-org-chat-register-tool-spec)

@karthink
Copy link

karthink commented Mar 23, 2025

I agree that symbols would be more appropriate. However, the reason that it is a string is because :category is actually a recognized keyword by gptel, and gptel takes it as a string. Thus, our hand is forced here. I am planning to add support for "tags" as an organization scheme as well; since that will be arbitrary, I will likely make those symbols.

OK, I see, thanks for clarifying. I've switched it back to strings.

I've checked that the changes needed in gptel to work with symbols (or both) are quite straightforward, so if you'd like, I could submit a PR there and see how karthink feels about it. I'd also be happy to leave things as they are, if you prefer.

I made the category a string to give the user freedom to name tool groups however they wanted. While I expect that a few standard categories will arise over time, users can also have category names like

  • "retrieval (local, text)"
  • "retrieval (Roam db + Pocket)"
  • "retrieval (web sources, jira)"
  • "slack and irc"
  • "filesystem, read-only"
  • "filesystem, read-write"
  • "eval (watch out!)"

Users have different ways to tag, and not all of these can be represented as symbols. So I wasn't thinking of it as an enum.

I agree that tools contributed here should be categorized using some semi-standard set (like an enum).

@karthink
Copy link

karthink commented Mar 23, 2025

It's not clear to me whether such code belongs in the README, in some docstring, as part of llm-tool-collection, as part of gptel, or wherever else, but figured I'd share it for the sake of discussion.

It can be part of gptel if required. Unlike the more commercial offerings, we're developing this suite of functionality split across several projects (gptel, llm, llm-tool-collection, mcp) so I'm in favor of providing glue code in all these packages to make it easy for the user to integrate them.

@karthink
Copy link

karthink commented Mar 23, 2025

OK, one more little tweak (and sorry for the spam). Since :description is an essentially mandatory field for functions and args, it seemed natural to drop the :description key and instead place its value immediately after the symbol name. This makes it look more like an actual docstring...

See also karthink/gptel#685

As an aside, for elisp tools it usually makes sense to have a function docstring that's different from the description intended for the LLM. The latter focuses on different things. For example, it can contain instructions for the LLM on which tool (or set of tools) should be called next depending on the result of this one. This helps guide chained tool use with LLMs, as in this video and the previous one in this series.

ultronozm added a commit to ultronozm/gptel that referenced this pull request Mar 24, 2025
* gptel.el (gptel-register-tool): New function to register a tool with
gptel, replacing any existing tool with the same name.

Following
skissue/llm-tool-collection#2 (comment)
@ultronozm
Copy link
Contributor Author

As an aside, for elisp tools it usually makes sense to have a function docstring that's different from the description intended for the LLM. The latter focuses on different things. For example, it can contain instructions for the LLM on which tool (or set of tools) should be called next depending on the result of this one. This helps guide chained tool use with LLMs, as in this video and the previous one in this series.

Thanks for this and your other comments. That's a good point about the different purposes of the docstrings, so I guess it'd make sense to add a further argument specifying the docstring. It's not clear to me what would be the best ergonomics for this, so I'll leave it for now.

@skissue
Copy link
Owner

skissue commented Mar 24, 2025

One point concerning optional arguments might be worth discussing. With the LLM's, I think any of the arguments may be marked as required or optional? With Emacs, the required must come before the optional. I updated the docstring of llm-tool-collection-deftool to hopefully clarify that point a bit.

That's a good point. I don't think it should be an issue—is there ever a scenario where required and optional arguments need to be intermixed?

One other thought that came to mind is whether it would make sense to include commands in llm-tool-collection that make it easier to work on tools by having frontends update as soon as the deftool form is evaluated.

It's not clear to me whether such code belongs in the README, in some docstring, as part of llm-tool-collection, as part of gptel, or wherever else, but figured I'd share it for the sake of discussion.

Personally, I don't think I'll be adding this to the codebase itself. To me, users that are only consuming—not iterating on—the tools here should never have a reason to be rapidly evaluating the same tool many times. They can instead add all tools of interest via functions such as llm-tool-collection-get-all. Additionally, I'd like this collection to stay reasonably client-agnostic (though the :category tag shows that sometimes that does have to be traded for pragmatism). However, this is definitely a good thing to have for development, and I will certainly add this to the README when I get around to writing that (hopefully after merging this PR!).

As an aside, for elisp tools it usually makes sense to have a function docstring that's different from the description intended for the LLM. The latter focuses on different things. For example, it can contain instructions for the LLM on which tool (or set of tools) should be called next depending on the result of this one. This helps guide chained tool use with LLMs, as in this video and the previous one in this series.

Thanks for this and your other comments. That's a good point about the different purposes of the docstrings, so I guess it'd make sense to add a further argument specifying the docstring. It's not clear to me what would be the best ergonomics for this, so I'll leave it for now.

Perhaps this is naive or reductive of me, but I am of the (tentative) opinion that, in this specific case, we can simply forgo the Elisp docstring entirely. To me, the most important benefit of the conventions of an Elisp docstring is ensuring that the interface is well defined: arguments, return value, and context. I feel that LLM-friendly descriptions almost always end up being a superset of the information one would have gleaned from an Elisp docstring, since the former also requires documenting context, arguments, and the return value. However, I'm open to examples of this assumption being false.

"Functions called after defining a new LLM tool.
Each function is called with one argument, the tool's plist definition.")

;;;###autoload
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for this macro to be autoloaded? I'm not sure if there is application outside of this package—I was under the impression that this was a macro solely to simplify making definitions for us (and contributors).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seemed necessary at some point to get font-lock and
indentation for the deftool macros working when editing
llm-tool-collection.el before loading the package, but I haven't been able
to reproduce that issue, so I'll be happy to remove the autoload (but will
first wait for your feedback on the other commits).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other commits look good to me; there's a little wording I'd like to tweak, but it's not an issue at all and I'm happy to do it post-merge. Should be ready to merge after this 🎉!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated, should be all set

@ultronozm
Copy link
Contributor Author

ultronozm commented Mar 24, 2025 via email

* llm-tool-collection.el (llm-tool-collection-font-lock-keywords): New
constant.
* llm-tool-collection.el (llm-tool-collection-deftool): Expand docs.
Clarify that optional arguments are specified via the &optional
keyword, rather than with `:optional t'.  Add link to the tool spec.
* llm-tool-collection.el (llm-tool-collection-deftool): Reorder macro
arguments to put description last, after specs and args.  Expand
docstring to explain SPECS and its keywords.  The rationale is to more
closely mimick the ordering for 'defun' and to put the :category
specification on the line below the tool name, so that one can survey
tools by category using C-1 M-x occur.
(read-file, list-directory, create-file, create-directory)
(view-buffer): Update existing tool definitions to follow the new
argument order.
* llm-tool-collection.el (edit-buffer): New tool.
@skissue skissue merged commit 1d70fdd into skissue:restructuring Mar 24, 2025
@skissue
Copy link
Owner

skissue commented Mar 24, 2025

Thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants