80 lines
3.1 KiB
Python
80 lines
3.1 KiB
Python
"""Which tools exist, read from configs/tools.toml.
|
|
|
|
WHY THIS IS A REMOVAL AND NOT A FLAG
|
|
|
|
Open WebUI has a Function Name Filter List on the tool connection, and it
|
|
was the only way to turn a tool off before this file. It hides a tool
|
|
from one model. The schema is still fetched, the endpoint still answers,
|
|
and the setting lives in Open WebUI's database rather than anywhere you
|
|
can read in the repo - so "which tools does this deployment offer" had no
|
|
answer on disk at all.
|
|
|
|
This drops the route instead. An operation that is not registered is not
|
|
in the spec, so the model is never told it exists and no context is spent
|
|
describing it; and the path 404s, so a stale cached spec somewhere else
|
|
cannot call it either.
|
|
|
|
THE SEAM IS main.py, NOT toolbase.tool
|
|
|
|
`tool()` wraps the handler, which runs long after FastAPI has registered
|
|
the route and built its description. A check there could refuse a call
|
|
but could never stop the model being told about the tool - which is the
|
|
expensive half.
|
|
|
|
FAIL OPEN, DELIBERATELY
|
|
|
|
A missing or unparseable file leaves every tool on. This is config that
|
|
removes capability, so the failure mode of losing it should be the
|
|
system it replaces, not a server with no tools. Both cases log a line
|
|
saying what happened; silence here would look identical to "everything is
|
|
switched on", which is exactly the confusion to avoid.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import tomllib
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
CONFIG_PATH = os.environ.get("OWUITOOLS_TOOLS_CONFIG", "/configs/tools.toml")
|
|
|
|
|
|
def load(path: str | None = None) -> dict[str, bool]:
|
|
"""The file's contents as {tool_name: enabled}, or {} if unreadable."""
|
|
path = path or CONFIG_PATH
|
|
try:
|
|
with open(path, "rb") as handle:
|
|
table = tomllib.load(handle).get("tools", {})
|
|
except FileNotFoundError:
|
|
log.info("no tool config at %s - every tool stays enabled", path)
|
|
return {}
|
|
except (OSError, tomllib.TOMLDecodeError) as exc:
|
|
log.warning("tool config at %s could not be read (%s) - every tool stays enabled",
|
|
path, exc)
|
|
return {}
|
|
return {str(name): bool(value) for name, value in table.items()}
|
|
|
|
|
|
def disabled(known: set[str], path: str | None = None) -> set[str]:
|
|
"""Which of `known` this deployment has switched off.
|
|
|
|
`known` is what the server actually implements. Anything named in the
|
|
file that is not in it is reported and ignored - a typo would
|
|
otherwise be indistinguishable from a tool that is switched off, and
|
|
the whole point of the file is to be able to read what is on.
|
|
|
|
A known tool the file does not mention stays ON, so a config written
|
|
against an older version never silently removes a new tool.
|
|
"""
|
|
config = load(path)
|
|
unknown = sorted(set(config) - known)
|
|
if unknown:
|
|
log.warning("tool config names %d tool(s) this server does not have: %s",
|
|
len(unknown), ", ".join(unknown))
|
|
off = {name for name, on in config.items() if not on and name in known}
|
|
if off:
|
|
log.info("tools switched off in config: %s", ", ".join(sorted(off)))
|
|
return off
|