Free & open source

Give hexiel new tools with one Python file

A plugin is a plain Python file. Drop it in the plugins folder and the methods you wrote show up as tools the model can call. hexiel reads the same Tools, Pipe and Filter files that OpenWebUI uses, so community plugins work as they are and the ones you write work in both.

Quick start

mkdir -p ~/.local/share/hexiel/plugins
cp weather.py ~/.local/share/hexiel/plugins/     # any OpenWebUI "Tools" file
hexiel                                           # restart: plugins are read at startup

Ask hexiel something the plugin can answer. Every public method of its Tools class is now a tool named tools_<method>, for example tools_get_weather. Plugin tools always ask for your approval before they run, because hexiel cannot know what a plugin touches.

Plugins are loaded once, when hexiel starts. After adding or editing one, restart hexiel.

Where plugins live

FolderLoads
~/.local/share/hexiel/plugins/Always. This is your own folder, so it is the place for plugins you chose to install. It follows HEXIEL_DATA if you set it.
plugins/ inside a projectOnly after you trust that project (see below), so code in a repository you just cloned never runs by surprise.

Each *.py file in these folders is imported. A file can hold a Tools, a Pipe and a Filter class together.

Trust and safety

A plugin is code that runs on your machine with your permissions, the moment hexiel starts. Treat it like any program you install: read it, or take it from someone you trust.

cd my-project
hexiel --trust-project      # remember this project; its plugins/ folder now loads

Untrusted projects

If a project has a plugins/ folder and you have not trusted it, hexiel skips the files and prints one line saying how many it skipped and how to trust the project. Nothing in them is imported.

Approvals still apply

Even a trusted plugin's tools go through the normal approval prompt. A Pipe only forwards text to a model, so it is the one kind that runs without asking.

The list of trusted projects is a small file, trusted-projects.json, in the hexiel data folder. Delete a line to un-trust a project. Setting HEXIEL_TRUST_PROJECT=1 trusts every project for that run, which is meant for throwaway CI containers.

Write your own

Save this as ~/.local/share/hexiel/plugins/notes.py:

from pathlib import Path


class Tools:
    """Search my notes folder."""

    class Valves:                      # optional settings, with their defaults
        notes_dir: str = str(Path.home() / "notes")
        max_hits: int = 5

    def search_notes(self, query: str, limit: int = 3) -> str:
        """Find notes that mention a word or phrase.

        Returns the file name and matching line for each hit."""
        root = Path(self.valves.notes_dir)
        hits = []
        for path in sorted(root.glob("**/*.md")):
            for line in path.read_text(errors="replace").splitlines():
                if query.lower() in line.lower():
                    hits.append(f"{path.name}: {line.strip()}")
        return "\n".join(hits[: min(limit, self.valves.max_hits)]) or "no matches"

Restart hexiel and it can call tools_search_notes. How hexiel reads your file:

In your fileWhat hexiel does with it
A class named ToolsEvery method whose name does not start with _ becomes a tool, named tools_<method>. startup, shutdown and valves are skipped.
The method's docstringIts first paragraph is the description the model reads when it decides whether to call the tool. Write it for the model: say what the tool does and returns.
Type hintsint, float and bool become numbers and booleans, list and dict become arrays and objects, and anything else is text.
DefaultsA parameter with a default is optional. One without a default is required, and the model must supply it.
A Valves classhexiel builds it with its defaults and sets it as self.valves, so your methods can read their settings.
The return valueText is passed back as is. A dict or list is turned into indented JSON. async def methods work too.

Keep each tool small and give its text result a sensible size. The result goes into the model's context, and a short, relevant answer saves tokens and money.

Pipes and filters

Pipe

A class named Pipe with a pipe(self, body, ...) method. hexiel offers it as one tool named pipe_<id> (the class's id attribute), passing the conversation messages in body["messages"]. Use it to forward a question to another model or service.

Filter

A class named Filter with an inlet(self, body, ...) method. It sees each message you send, before the model does, and can return the changed text in body["messages"][-1]["content"]. Only inlet is applied today; outlet is not.

class Filter:
    def inlet(self, body: dict, __user__=None) -> dict:
        body["messages"][-1]["content"] += "\n(Answer in British English.)"
        return body

If a filter raises an error, hexiel shows a short note and sends your message unchanged.

Using hexiel from OpenWebUI

The compatibility also works the other way. In OpenWebUI add a connection to http://127.0.0.1:8777/v1/chat/completions while hexiel runs with hexiel --serve. Over that API hexiel keeps its read-only tools and turns its write tools off.

Troubleshooting

hexiel says it is not loading plugin files

They are in a project's plugins/ folder and the project is not trusted. If you trust it, run hexiel --trust-project in that folder. Otherwise copy the file into ~/.local/share/hexiel/plugins/.

My tool does not show up

Restart hexiel, since plugins load at startup. Check the class is named exactly Tools (capital T), the method name does not start with an underscore, and the file ends in .py.

"plugin … failed to import"

The file raised an error when imported, usually a missing package. Install it into the Python that runs hexiel. A broken plugin never stops hexiel from starting; the rest still load.

The model never calls my tool

Improve the docstring's first paragraph. It is the only thing the model reads to decide. Say what the tool is for, in plain words, and what it returns.

It asks for approval every time

That is by design for plugin tools. Choose "always" at the prompt to allow that kind of call for the session.

Still stuck? Run hexiel --doctor, or open an issue.

Build the tool you wish it had.