Files
Varaverk/AI/openwebui_tool.py
T
Gmer4Lfe a537ae4217 Add the Open-WebUI tool that calls the retrieval bridge
Pasted into Open-WebUI rather than installed by a script: tools live in its own
database, and writing there means guessing at the schema and access control of
a live app. The secret is a Valve so this file stays committable.
2026-08-02 17:11:57 -04:00

130 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
title: Varaverk Docs
description: Search Varaverk's own documentation and return grounding context.
author: Varaverk
version: 1.0.0
required_open_webui_version: 0.4.0
"""
# ═══════════════════════════════════════════════════════════════════════════════════════════════
# PURPOSE
# Open-WebUI tool that lets the chat model search Varaverk's documentation index and answer
# from it, instead of from whatever it happens to remember about a private project it has
# never seen.
#
# OPERATIONAL MODEL
# Not installed by any Varaverk script. Open-WebUI stores tools in its own database, and
# writing there directly would mean guessing at its schema, IDs and access control on a live
# app. Paste this into Open-WebUI → Workspace → Tools → +, then set the two Valves.
#
# Calls AI/ai_serve.js over HTTP because Open-WebUI runs in a container that cannot see
# Varaverk's filesystem and has no WebGUI session, so the plugin's PHP API is unreachable to
# it. The bridge returns chunks; the model already loaded in Open-WebUI does the generating.
#
# CONFIGURATION (Valves — set in the Open-WebUI tool editor, not here)
# base_url http://<host-lan-ip>:7822 — AI_HTTP_PORT from master.conf
# secret AI_HTTP_SECRET from master.conf
#
# The secret is a Valve rather than a constant so this file stays committable. Do not paste
# it into the code — this path is git-tracked and pushed to a remote.
#
# OPERATIONAL SAFEGUARDS
# Retrieval only. The bridge exposes one read-only verb over documentation already in git;
# this tool cannot write conf, run a script, or change anything.
#
# Returns "no relevant documentation found" rather than an empty string on a miss, so the
# model states that plainly instead of filling the silence from memory — the failure this
# whole retrieval path exists to prevent.
#
# Every failure is returned as readable text, never raised. An exception inside a tool call
# surfaces to the user as an opaque error; a sentence explaining that the bridge is
# unreachable is something they can act on.
#
# Time-boxed at 30s. A hung retrieval must not hold the chat turn open indefinitely.
# ═══════════════════════════════════════════════════════════════════════════════════════════════
import json
import urllib.parse
import urllib.request
from pydantic import BaseModel, Field
class Tools:
class Valves(BaseModel):
base_url: str = Field(
default="http://192.168.50.2:7822",
description="AI retrieval bridge — host LAN IP and AI_HTTP_PORT. Not localhost: "
"Open-WebUI is a container and localhost is itself.",
)
secret: str = Field(
default="",
description="AI_HTTP_SECRET from Configurations/master.conf",
)
results: int = Field(
default=8,
description="Chunks to retrieve per query (1-25)",
)
def __init__(self):
self.valves = self.Valves()
def search_varaverk_docs(self, query: str, kind: str = "") -> str:
"""
Search the Varaverk documentation index for passages relevant to a question about
this specific home-media system: its scripts, configuration variables, safeguards,
orchestrators, rsync behaviour, watchdogs, fallback logic or plugin internals.
Always use this before answering any question about Varaverk. Varaverk is a private
project and is not in your training data; without this tool you do not know what it
is and must not guess.
:param query: The question or topic to search for, in natural language.
:param kind: Optional filter on where the text comes from. Use "readme" for
definitional or narrative questions such as "what is Varaverk" or "why does this
exist" — otherwise per-script header sections outrank the top-level prose and the
answer will look absent when it is not. Leave empty for specific technical
questions. One of: header, readme, manual, template, doc.
:return: Numbered passages with their source paths, or a message saying nothing matched.
"""
if not self.valves.secret:
return ("The Varaverk docs tool is not configured: its 'secret' Valve is empty. "
"Set it to AI_HTTP_SECRET from Configurations/master.conf.")
params = {
"key": self.valves.secret,
"q": query,
"k": max(1, min(int(self.valves.results), 25)),
}
if kind:
params["kind"] = kind
url = f"{self.valves.base_url.rstrip('/')}/search?" + urllib.parse.urlencode(params)
try:
with urllib.request.urlopen(url, timeout=30) as r:
data = json.load(r)
except Exception as e:
return (f"Could not reach the Varaverk retrieval bridge at "
f"{self.valves.base_url} ({e}). It is started by AI/start_ai_server.sh; "
f"check that AI_ENABLED and AI_HTTP_PORT are set and the index exists.")
if not data.get("ok"):
return f"Varaverk retrieval failed: {data.get('error', 'unknown error')}"
results = data.get("results") or []
if not results:
return (f"No relevant documentation found for '{query}'. Say so plainly rather "
f"than answering from general knowledge — Varaverk is private and is not "
f"in your training data.")
out = [f"{len(results)} passage(s) from the Varaverk documentation index:", ""]
for i, r in enumerate(results, 1):
label = " ".join(x for x in (r.get("path"), r.get("section"), r.get("heading")) if x)
out.append(f"[{i}] {label} (score {r.get('score')})")
out.append(r.get("content", "").strip())
out.append("")
out.append("Answer only from the passages above, and cite them by their [n] markers. "
"If they do not contain the answer, say so and name what is missing.")
return "\n".join(out)