From c9f64096c92c44f05d8eabd1805138928f776b5c Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 20 Apr 2025 07:56:06 -0700 Subject: [PATCH] llm fragments loaders, closes #941 --- docs/fragments.md | 23 +++++++++++++++++++++++ docs/help.md | 20 ++++++++++++++++---- llm/cli.py | 20 ++++++++++++++++++++ tests/test_plugins.py | 13 +++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/docs/fragments.md b/docs/fragments.md index 1b611f8..cc9c8e7 100644 --- a/docs/fragments.md +++ b/docs/fragments.md @@ -146,3 +146,26 @@ Running `llm logs -c` will show that this prompt incorporated 26 fragments, one Running `llm logs -c --usage --expand` includes token usage information and turns each fragment ID into a full copy of that file. [Here's the output of that command](https://gist.github.com/simonw/c9bbbc5f6560b01f4b7882ac0194fb25). See the {ref}`register_fragment_loaders() plugin hook ` documentation for details on writing your own custom fragment plugin. + +(fragments-loaders)= +## Listing available fragment prefixes + +The `llm fragments loaders` command shows all prefixes that have been installed by plugins, along with their documentation: + +```bash +llm install llm-fragments-github +llm fragments loaders +``` +Example output: +``` +github: + Load files from a GitHub repository as fragments. + + Argument is a GitHub repository URL or username/repository + +issue: + Fetch GitHub issue and comments as Markdown + + argument is either "owner/repo/NUMBER" + or "https://github.com/owner/repo/issues/NUMBER" +``` \ No newline at end of file diff --git a/docs/help.md b/docs/help.md index 7bd0ac7..b05a525 100644 --- a/docs/help.md +++ b/docs/help.md @@ -688,10 +688,11 @@ Options: --help Show this message and exit. Commands: - list* List current fragments - remove Remove a fragment alias - set Set an alias for a fragment - show Display the fragment stored under an alias or hash + list* List current fragments + loaders Show fragment loaders registered by plugins + remove Remove a fragment alias + set Set an alias for a fragment + show Display the fragment stored under an alias or hash ``` (help-fragments-list)= @@ -753,6 +754,17 @@ Options: --help Show this message and exit. ``` +(help-fragments-loaders)= +#### llm fragments loaders --help +``` +Usage: llm fragments loaders [OPTIONS] + + Show fragment loaders registered by plugins + +Options: + --help Show this message and exit. +``` + (help-plugins)= ### llm plugins --help ``` diff --git a/llm/cli.py b/llm/cli.py index a4c35e7..01c5bff 100644 --- a/llm/cli.py +++ b/llm/cli.py @@ -2283,6 +2283,26 @@ def fragments_remove(alias): ) +@fragments.command(name="loaders") +def fragments_loaders(): + """Show fragment loaders registered by plugins""" + from llm import get_fragment_loaders + + found = False + for prefix, loader in get_fragment_loaders().items(): + if found: + # Extra newline on all after the first + click.echo("") + found = True + docs = "Undocumented" + if loader.__doc__: + docs = textwrap.dedent(loader.__doc__).strip() + click.echo(f"{prefix}:") + click.echo(textwrap.indent(docs, " ")) + if not found: + click.echo("No fragment loaders found") + + @cli.command(name="plugins") @click.option("--all", help="Include built-in default plugins", is_flag=True) def plugins_list(all): diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 9618f30..97d7445 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -94,6 +94,7 @@ def test_register_fragment_loaders(logs_db): assert get_fragment_loaders() == {} def single_fragment(argument): + "This is the fragment documentation" return llm.Fragment("single", "single") def three_fragments(argument): @@ -127,6 +128,18 @@ def test_register_fragment_loaders(logs_db): assert result.exit_code == 0 expected = "prompt:\n" "one:x\n" "two:x\n" "three:x\n" assert expected in result.output + # And the llm fragments loaders command: + result2 = runner.invoke(cli.cli, ["fragments", "loaders"]) + assert result2.exit_code == 0 + expected2 = ( + "single:\n" + " This is the fragment documentation\n" + "\n" + "three:\n" + " Undocumented\n" + ) + assert result2.output == expected2 + finally: plugins.pm.unregister(name="FragmentLoadersPlugin") assert get_fragment_loaders() == {}