"""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