mirror of
https://github.com/Hopiu/llm.git
synced 2026-03-19 14:10:25 +00:00
* Docs and shape of register_fragment_loaders hook, refs #863 * Update docs for fragment loaders returning a list of FragmentString * Support multiple fragments with same content, closes #888 * Call the pm.hook.register_fragment_loaders hook * Test for register_fragment_loaders hook * Rename FragmentString to Fragment Closes #863
154 lines
5.8 KiB
Markdown
154 lines
5.8 KiB
Markdown
(plugin-hooks)=
|
|
# Plugin hooks
|
|
|
|
Plugins use **plugin hooks** to customize LLM's behavior. These hooks are powered by the [Pluggy plugin system](https://pluggy.readthedocs.io/).
|
|
|
|
Each plugin can implement one or more hooks using the @hookimpl decorator against one of the hook function names described on this page.
|
|
|
|
LLM imitates the Datasette plugin system. The [Datasette plugin documentation](https://docs.datasette.io/en/stable/writing_plugins.html) describes how plugins work.
|
|
|
|
(plugin-hooks-register-commands)=
|
|
## register_commands(cli)
|
|
|
|
This hook adds new commands to the `llm` CLI tool - for example `llm extra-command`.
|
|
|
|
This example plugin adds a new `hello-world` command that prints "Hello world!":
|
|
|
|
```python
|
|
from llm import hookimpl
|
|
import click
|
|
|
|
@hookimpl
|
|
def register_commands(cli):
|
|
@cli.command(name="hello-world")
|
|
def hello_world():
|
|
"Print hello world"
|
|
click.echo("Hello world!")
|
|
```
|
|
This new command will be added to `llm --help` and can be run using `llm hello-world`.
|
|
|
|
(plugin-hooks-register-models)=
|
|
## register_models(register)
|
|
|
|
This hook can be used to register one or more additional models.
|
|
|
|
```python
|
|
import llm
|
|
|
|
@llm.hookimpl
|
|
def register_models(register):
|
|
register(HelloWorld())
|
|
|
|
class HelloWorld(llm.Model):
|
|
model_id = "helloworld"
|
|
|
|
def execute(self, prompt, stream, response):
|
|
return ["hello world"]
|
|
```
|
|
If your model includes an async version, you can register that too:
|
|
|
|
```python
|
|
class AsyncHelloWorld(llm.AsyncModel):
|
|
model_id = "helloworld"
|
|
|
|
async def execute(self, prompt, stream, response):
|
|
return ["hello world"]
|
|
|
|
@llm.hookimpl
|
|
def register_models(register):
|
|
register(HelloWorld(), AsyncHelloWorld(), aliases=("hw",))
|
|
```
|
|
This demonstrates how to register a model with both sync and async versions, and how to specify an alias for that model.
|
|
|
|
The {ref}`model plugin tutorial <tutorial-model-plugin>` describes how to use this hook in detail. Asynchronous models {ref}`are described here <advanced-model-plugins-async>`.
|
|
|
|
(plugin-hooks-register-template-loaders)=
|
|
## register_template_loaders(register)
|
|
|
|
Plugins can register new {ref}`template loaders <prompt-templates-loaders>` using the `register_template_loaders` hook.
|
|
|
|
Template loaders work with the `llm -t prefix:name` syntax. The prefix specifies the loader, then the registered loader function is called with the name as an argument. The loader function should return an `llm.Template()` object.
|
|
|
|
This example plugin registers `my-prefix` as a new template loader. Once installed it can be used like this:
|
|
|
|
```bash
|
|
llm -t my-prefix:my-template
|
|
```
|
|
Here's the Python code:
|
|
|
|
```python
|
|
import llm
|
|
|
|
@llm.hookimpl
|
|
def register_template_loaders(register):
|
|
register("my-prefix", my_template_loader)
|
|
|
|
def my_template_loader(template_path: str) -> llm.Template:
|
|
"""
|
|
Documentation for the template loader goes here. It will be displayed
|
|
when users run the 'llm templates loaders' command.
|
|
"""
|
|
try:
|
|
# Your logic to fetch the template content
|
|
# This is just an example:
|
|
prompt = "This is a sample prompt for {}".format(template_path)
|
|
system = "You are an assistant specialized in {}".format(template_path)
|
|
|
|
# Return a Template object with the required fields
|
|
return llm.Template(
|
|
name=template_path,
|
|
prompt=prompt,
|
|
system=system,
|
|
)
|
|
except Exception as e:
|
|
# Raise a ValueError with a clear message if the template cannot be found
|
|
raise ValueError(f"Template '{template_path}' could not be loaded: {str(e)}")
|
|
```
|
|
Consult the latest code in [llm/templates.py](https://github.com/simonw/llm/blob/main/llm/templates.py) for details of that `llm.Template` class.
|
|
|
|
The loader function should raise a `ValueError` if the template cannot be found or loaded correctly, providing a clear error message.
|
|
|
|
(plugin-hooks-register-fragment-loaders)=
|
|
## register_fragment_loaders(register)
|
|
|
|
Plugins can register new fragment loaders using the `register_template_loaders` hook. These can then be used with the `llm -f prefix:argument` syntax.
|
|
|
|
The `prefix` specifies the loader. The `argument` will be passed to that registered callback..
|
|
|
|
The callback works in a very similar way to template loaders, but returns either a single `llm.Fragment` or a list of `llm.Fragment` objects.
|
|
|
|
The `llm.Fragment` constructor takes a required string argument (the content of the fragment) and an optional second `source` argument, which is a string that may be displayed as debug information. For files this is a path and for URLs it is a URL. Your plugin can use anything you like for the `source` value.
|
|
|
|
```python
|
|
import llm
|
|
|
|
@llm.hookimpl
|
|
def register_fragment_loaders(register):
|
|
register("my-fragments", my_fragment_loader)
|
|
|
|
|
|
def my_fragment_loader(argument: str) -> llm.Fragment:
|
|
try:
|
|
fragment = "Fragment content for {}".format(argument)
|
|
source = "my-fragments:{}".format(argument)
|
|
return llm.Fragment(fragment, source)
|
|
except Exception as ex:
|
|
# Raise a ValueError with a clear message if the fragment cannot be loaded
|
|
raise ValueError(
|
|
f"Fragment 'my-fragments:{argument}' could not be loaded: {str(ex)}"
|
|
)
|
|
|
|
# Or for the case where you want to return multiple fragments:
|
|
def my_fragment_loader(argument: str) -> list[llm.Fragment]:
|
|
return [
|
|
llm.Fragment("Fragment 1 content", "my-fragments:{argument}"),
|
|
llm.Fragment("Fragment 2 content", "my-fragments:{argument}"),
|
|
]
|
|
```
|
|
A plugin like this one can be called like so:
|
|
```bash
|
|
llm -f my-fragments:argument
|
|
```
|
|
If multiple fragments are returned they will be used as if the user passed multiple `-f X` arguments to the command.
|
|
|
|
Multiple fragments are useful for things like plugins that return every file in a directory. By giving each file its own fragment we can avoid having multiple copies of the full collection stored if only a single file has changed.
|