Initial Commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user