Add automatic per-request MCP selection
This commit is contained in:
1 parent
ddde433224
commit
e9b647e579
7 files changed
+344
-20
No files matched your search
@@ -117,6 +117,67 @@ class StabilityGuardTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertLess(len(result["messages"][1]["content"]), 3000)
|
self.assertLess(len(result["messages"][1]["content"]), 3000)
|
||||||
|
|
||||||
|
|
||||||
|
class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
||||||
|
async def asyncSetUp(self):
|
||||||
|
self.module = _load("auto_tool_selector")
|
||||||
|
self.selector = self.module.Filter()
|
||||||
|
|
||||||
|
async def _select(self, prompt: str, existing=None):
|
||||||
|
body = {
|
||||||
|
"model": "mikeai-medium",
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
}
|
||||||
|
if existing is not None:
|
||||||
|
body["tool_ids"] = existing
|
||||||
|
return await self.selector.inlet(body)
|
||||||
|
|
||||||
|
async def test_homeassistant_is_selected_for_room_temperature(self):
|
||||||
|
result = await self._select("Wie warm ist es gerade in der Küche?")
|
||||||
|
self.assertEqual(
|
||||||
|
result["tool_ids"], ["server:mcp:homeassistant-local"]
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_weather_uses_web_not_homeassistant(self):
|
||||||
|
result = await self._select("Soll es heute in Rastatt regnen?")
|
||||||
|
self.assertEqual(result["tool_ids"], ["server:mcp:web-local"])
|
||||||
|
|
||||||
|
async def test_unraid_uses_readonly_not_mua(self):
|
||||||
|
result = await self._select(
|
||||||
|
"Welche Docker-Container laufen aktuell auf Unraid?"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
result["tool_ids"], ["server:mcp:unraid-readonly-local"]
|
||||||
|
)
|
||||||
|
self.assertNotIn("server:mcp:mua", result["tool_ids"])
|
||||||
|
|
||||||
|
async def test_github_and_explicit_web_are_bounded_to_two(self):
|
||||||
|
result = await self._select(
|
||||||
|
"Prüfe dieses GitHub Repository und suche zusätzlich im Netz nach Nutzerstimmen."
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
result["tool_ids"],
|
||||||
|
["server:mcp:github-local", "server:mcp:web-local"],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_manual_tool_is_preserved(self):
|
||||||
|
result = await self._select(
|
||||||
|
"Prüfe Sonarr.", ["server:mcp:manually-selected"]
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
result["tool_ids"],
|
||||||
|
["server:mcp:manually-selected", "server:mcp:arr-local"],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_plain_chat_gets_no_tools(self):
|
||||||
|
result = await self._select("Erkläre mir den Unterschied zwischen RAM und SSD.")
|
||||||
|
self.assertNotIn("tool_ids", result)
|
||||||
|
|
||||||
|
async def test_selection_adds_write_safety_rule(self):
|
||||||
|
result = await self._select("Zeige mir die Home Assistant Automatisierungen.")
|
||||||
|
self.assertEqual(result["messages"][0]["role"], "system")
|
||||||
|
self.assertIn("Availability is not authorization", result["messages"][0]["content"])
|
||||||
|
|
||||||
|
|
||||||
class MetricsTests(unittest.IsolatedAsyncioTestCase):
|
class MetricsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
async def test_metrics_file_contains_no_chat_content_or_ids(self):
|
async def test_metrics_file_contains_no_chat_content_or_ids(self):
|
||||||
module = _load("local_performance_metrics")
|
module = _load("local_performance_metrics")
|
||||||
|
|||||||
@@ -166,6 +166,14 @@ Aktuell existieren funktionale Adapter für:
|
|||||||
- Unraid read-only
|
- Unraid read-only
|
||||||
- eigener Unraid-Administrationsserver
|
- eigener Unraid-Administrationsserver
|
||||||
|
|
||||||
|
OpenWebUI bindet diese Kataloge nicht pauschal an jedes Modellprofil. Der
|
||||||
|
lokale `MikeAI Auto Tool Selector` ergänzt anhand der jüngsten Nutzernachricht
|
||||||
|
höchstens zwei passende MCP-Verbindungen pro Anfrage. Dadurch bleiben normale
|
||||||
|
Chats schemafrei und kurze Profile verlieren keinen unnötigen Kontext. MUA
|
||||||
|
mit erweiterten Verwaltungsrechten bleibt von der Automatik ausgeschlossen;
|
||||||
|
eine automatisch bereitgestellte Verbindung erteilt niemals Schreibrechte
|
||||||
|
oder eine Änderungsfreigabe.
|
||||||
|
|
||||||
Der GitHub-Container läuft produktiv. Token-Datei, interner
|
Der GitHub-Container läuft produktiv. Token-Datei, interner
|
||||||
Streamable-HTTP-Handshake, fehlende Host-Portfreigabe und exakt vier
|
Streamable-HTTP-Handshake, fehlende Host-Portfreigabe und exakt vier
|
||||||
read-only Werkzeuge wurden am 23. August 2026 verifiziert.
|
read-only Werkzeuge wurden am 23. August 2026 verifiziert.
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ laufen, sondern alle fachlichen Funktionen geprüft wurden.
|
|||||||
- [ ] rohe `qwen-*`-Routermodelle sind ausgeblendet
|
- [ ] rohe `qwen-*`-Routermodelle sind ausgeblendet
|
||||||
- [ ] Medium ist die gespeicherte Standardauswahl
|
- [ ] Medium ist die gespeicherte Standardauswahl
|
||||||
- [ ] Filter und Quick Actions sind allen fünf Presets zugeordnet
|
- [ ] Filter und Quick Actions sind allen fünf Presets zugeordnet
|
||||||
|
- [ ] Auto Tool Selector wählt bei harmlosen Testfragen Web, GitHub, Home
|
||||||
|
Assistant, ARR, Navidrome, Unraid read-only und Athena korrekt
|
||||||
|
- [ ] normale Unterhaltung erhält kein MCP; MUA wird niemals automatisch gewählt
|
||||||
|
|
||||||
- [ ] SearXNG und TinySearch gesund
|
- [ ] SearXNG und TinySearch gesund
|
||||||
- [ ] Websuche liefert kompakte, quellengebundene Ergebnisse
|
- [ ] Websuche liefert kompakte, quellengebundene Ergebnisse
|
||||||
@@ -67,7 +70,8 @@ laufen, sondern alle fachlichen Funktionen geprüft wurden.
|
|||||||
- [ ] offizieller GitHub-MCP gesund; exakt vier read-only Repository-Werkzeuge
|
- [ ] offizieller GitHub-MCP gesund; exakt vier read-only Repository-Werkzeuge
|
||||||
- [ ] GitHub-Token liegt nur in `/etc/mike-ai/github-mcp.env` (0600), nicht in OpenWebUI
|
- [ ] GitHub-Token liegt nur in `/etc/mike-ai/github-mcp.env` (0600), nicht in OpenWebUI
|
||||||
- [ ] Unraid read-only Diagnose geprüft
|
- [ ] Unraid read-only Diagnose geprüft
|
||||||
- [ ] schreibende Werkzeuge standardmäßig nicht geladen
|
- [ ] schreibende Werkzeuge standardmäßig nicht geladen; automatische Auswahl
|
||||||
|
gilt nicht als Änderungsfreigabe
|
||||||
- [ ] Tool-Schemas bleiben innerhalb des festgelegten Kontextbudgets
|
- [ ] Tool-Schemas bleiben innerhalb des festgelegten Kontextbudgets
|
||||||
- [ ] kein Secret erscheint in Toolantworten oder Logs
|
- [ ] kein Secret erscheint in Toolantworten oder Logs
|
||||||
- [ ] `PLATFORM_OVERVIEW.md`, `QWEN_OPERATOR_CONTEXT.md` und der Operator-
|
- [ ] `PLATFORM_OVERVIEW.md`, `QWEN_OPERATOR_CONTEXT.md` und der Operator-
|
||||||
|
|||||||
+22
-7
@@ -9,14 +9,21 @@ Reihenfolge ist absichtlich festgelegt:
|
|||||||
`reasoning_effort=none`.
|
`reasoning_effort=none`.
|
||||||
2. `Thinking`, Priorität 20: läuft nur bei aktiviertem Brain-Schalter und
|
2. `Thinking`, Priorität 20: läuft nur bei aktiviertem Brain-Schalter und
|
||||||
überschreibt den Standard mit Low, Medium oder High.
|
überschreibt den Standard mit Low, Medium oder High.
|
||||||
3. `MikeAI Stability Guard`, Priorität 30: begrenzt einzelne und gesamte
|
3. `MikeAI Auto Tool Selector`, Priorität 25: betrachtet ausschließlich die
|
||||||
|
jüngste Nutzernachricht und stellt pro Anfrage höchstens zwei passende MCPs
|
||||||
|
bereit. Er erkennt Web, GitHub, Home Assistant, Sonarr/Radarr, Navidrome,
|
||||||
|
Unraid-Diagnose und Athena-Plattformwissen. Manuell gewählte Werkzeuge
|
||||||
|
bleiben erhalten. MUA mit erweiterten Verwaltungsrechten wird nie
|
||||||
|
automatisch zugeschaltet. Die Auswahl eines MCP ist ausdrücklich keine
|
||||||
|
Freigabe für eine Zustandsänderung.
|
||||||
|
4. `MikeAI Stability Guard`, Priorität 30: begrenzt einzelne und gesamte
|
||||||
Werkzeugresultate, verdichtet bei Bedarf zuerst alte Tool-Ausgaben und
|
Werkzeugresultate, verdichtet bei Bedarf zuerst alte Tool-Ausgaben und
|
||||||
Dialogteile und stoppt identische beziehungsweise ausufernde Tool-Schleifen.
|
Dialogteile und stoppt identische beziehungsweise ausufernde Tool-Schleifen.
|
||||||
4. `MikeAI Secret Redaction`, Priorität 40: entfernt übliche API-Keys, Tokens,
|
5. `MikeAI Secret Redaction`, Priorität 40: entfernt übliche API-Keys, Tokens,
|
||||||
Passwörter, JWTs und private Schlüssel aus Tool-Ergebnissen, bevor sie das
|
Passwörter, JWTs und private Schlüssel aus Tool-Ergebnissen, bevor sie das
|
||||||
Modell erreichen, sowie aus fertigen Modellantworten. Nutzereingaben und
|
Modell erreichen, sowie aus fertigen Modellantworten. Nutzereingaben und
|
||||||
Authentifizierungswege werden nicht verändert.
|
Authentifizierungswege werden nicht verändert.
|
||||||
5. `MikeAI Spoken Tool Status`, Priorität 80: erkennt den ersten echten
|
6. `MikeAI Spoken Tool Status`, Priorität 80: erkennt den ersten echten
|
||||||
Werkzeugaufruf eines Antwortlaufs und löst im Browser genau eine kurze,
|
Werkzeugaufruf eines Antwortlaufs und löst im Browser genau eine kurze,
|
||||||
passende Ansage für Web, Unraid, Home Assistant, Medienverwaltung oder
|
passende Ansage für Web, Unraid, Home Assistant, Medienverwaltung oder
|
||||||
sonstige Werkzeuge aus. Die fünf MP3-Clips sind vorgerendert und liegen
|
sonstige Werkzeuge aus. Die fünf MP3-Clips sind vorgerendert und liegen
|
||||||
@@ -24,7 +31,7 @@ Reihenfolge ist absichtlich festgelegt:
|
|||||||
weder XTTS noch das Werkzeug. Sie wird nur abgespielt, wenn der Benutzer in
|
weder XTTS noch das Werkzeug. Sie wird nur abgespielt, wenn der Benutzer in
|
||||||
OpenWebUI die automatische Sprachausgabe aktiviert hat. Endet ein sehr
|
OpenWebUI die automatische Sprachausgabe aktiviert hat. Endet ein sehr
|
||||||
schneller Lauf innerhalb der kurzen Wartezeit, wird die Ansage verworfen.
|
schneller Lauf innerhalb der kurzen Wartezeit, wird die Ansage verworfen.
|
||||||
6. `MikeAI Local Performance Metrics`, Priorität 90: erfasst nach Abschluss
|
7. `MikeAI Local Performance Metrics`, Priorität 90: erfasst nach Abschluss
|
||||||
ausschließlich technische Zahlen wie Laufzeit, Tokenzähler, Token/s und
|
ausschließlich technische Zahlen wie Laufzeit, Tokenzähler, Token/s und
|
||||||
Tool-Anzahl. Nutzer-, Chat- und Nachrichten-IDs sowie sämtliche Textinhalte
|
Tool-Anzahl. Nutzer-, Chat- und Nachrichten-IDs sowie sämtliche Textinhalte
|
||||||
werden weder geschrieben noch gehasht gespeichert.
|
werden weder geschrieben noch gehasht gespeichert.
|
||||||
@@ -49,9 +56,17 @@ Dadurch erscheinen ausschließlich `MikeAI · Fast`, `MikeAI · Medium`,
|
|||||||
fünf erhalten die geprüften Filter und Quick Actions sowie Piper-Stimme
|
fünf erhalten die geprüften Filter und Quick Actions sowie Piper-Stimme
|
||||||
`alloy`. Vision ist bei Fast, Medium, Large und Uncensored aktiviert; Ultra bleibt
|
`alloy`. Vision ist bei Fast, Medium, Large und Uncensored aktiviert; Ultra bleibt
|
||||||
bewusst text-only. Bildgenerierung wird erst als Fähigkeit freigeschaltet,
|
bewusst text-only. Bildgenerierung wird erst als Fähigkeit freigeschaltet,
|
||||||
wenn ein echter Generator-Worker im Stack aktiv ist. Websuche und Codewerkzeuge
|
wenn ein echter Generator-Worker im Stack aktiv ist. Kein MCP ist statisch an
|
||||||
bleiben verfügbar, werden aber nicht automatisch jeder Nachricht beigelegt,
|
jedes Profil gebunden. Der Auto Tool Selector stellt nur die zur jüngsten
|
||||||
damit Werkzeugschemas den Kontext nicht unnötig füllen.
|
Anfrage passenden Werkzeuge bereit, damit irrelevante Schemas weder Kontext
|
||||||
|
verbrauchen noch die Werkzeugwahl des Modells verschlechtern. Eine manuelle
|
||||||
|
Auswahl im Chat bleibt zusätzlich möglich.
|
||||||
|
|
||||||
|
Die Auswahl arbeitet bewusst regelbasiert und lokal. Sie sendet keine Texte an
|
||||||
|
einen Klassifizierungsdienst, speichert keine Prompts und führt selbst keine
|
||||||
|
Werkzeugaktion aus. Erkennt sie keine eindeutige Absicht, wird kein MCP
|
||||||
|
automatisch ergänzt. Schreibende oder kritische Rechte werden weiterhin durch
|
||||||
|
das jeweilige MCP, Bestätigungsregeln und die manuelle MUA-Auswahl begrenzt.
|
||||||
|
|
||||||
Alle fünf Modelle erhalten außerdem dieselbe Evidenzregel: Aussagen über
|
Alle fünf Modelle erhalten außerdem dieselbe Evidenzregel: Aussagen über
|
||||||
aktuelle externe oder Systemzustände benötigen im aktuellen Turn einen
|
aktuelle externe oder Systemzustände benötigen im aktuellen Turn einen
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
"""
|
||||||
|
title: MikeAI Auto Tool Selector
|
||||||
|
author: MikeAI
|
||||||
|
version: 1.0.0
|
||||||
|
description: Selects a small, relevant set of MCP servers for each user request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Filter:
|
||||||
|
class Valves(BaseModel):
|
||||||
|
priority: int = 25
|
||||||
|
enabled: bool = True
|
||||||
|
max_automatic_tools: int = 2
|
||||||
|
show_selection_status: bool = True
|
||||||
|
|
||||||
|
TOOL_IDS = {
|
||||||
|
"web": "server:mcp:web-local",
|
||||||
|
"github": "server:mcp:github-local",
|
||||||
|
"homeassistant": "server:mcp:homeassistant-local",
|
||||||
|
"arr": "server:mcp:arr-local",
|
||||||
|
"unraid": "server:mcp:unraid-readonly-local",
|
||||||
|
"navidrome": "server:mcp:navidrome-local",
|
||||||
|
"platform": "server:mcp:athena-platform",
|
||||||
|
}
|
||||||
|
|
||||||
|
LABELS = {
|
||||||
|
"web": "Web",
|
||||||
|
"github": "GitHub",
|
||||||
|
"homeassistant": "Home Assistant",
|
||||||
|
"arr": "Sonarr/Radarr",
|
||||||
|
"unraid": "Unraid-Diagnose",
|
||||||
|
"navidrome": "Navidrome",
|
||||||
|
"platform": "Athena-Plattformwissen",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.valves = self.Valves()
|
||||||
|
self.toggle = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalise(text: str) -> str:
|
||||||
|
text = unicodedata.normalize("NFKC", text or "").casefold()
|
||||||
|
return re.sub(r"\s+", " ", text).strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _matches(text: str, patterns: tuple[str, ...]) -> bool:
|
||||||
|
return any(re.search(pattern, text, flags=re.IGNORECASE) for pattern in patterns)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _latest_user_text(body: dict) -> str:
|
||||||
|
for message in reversed(body.get("messages") or []):
|
||||||
|
if message.get("role") != "user":
|
||||||
|
continue
|
||||||
|
content = message.get("content", "")
|
||||||
|
if isinstance(content, str):
|
||||||
|
return content
|
||||||
|
if isinstance(content, list):
|
||||||
|
return " ".join(
|
||||||
|
part.get("text", "")
|
||||||
|
for part in content
|
||||||
|
if isinstance(part, dict) and isinstance(part.get("text"), str)
|
||||||
|
)
|
||||||
|
return str(content)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_system_rule(body: dict, labels: list[str]) -> None:
|
||||||
|
rule = (
|
||||||
|
"MikeAI automatic tool selection made these capabilities available for "
|
||||||
|
f"this request: {', '.join(labels)}. Availability is not authorization. "
|
||||||
|
"First inspect read-only state. Never perform a state-changing action "
|
||||||
|
"unless the user's current message explicitly requests that exact change "
|
||||||
|
"and every required preview, confirmation, backup, and validation rule "
|
||||||
|
"of the tool is satisfied. Do not call unrelated tools merely because "
|
||||||
|
"they are available. If the selected tool cannot verify the claim, say so."
|
||||||
|
)
|
||||||
|
messages = body.setdefault("messages", [])
|
||||||
|
for message in messages:
|
||||||
|
if message.get("role") == "system" and isinstance(message.get("content"), str):
|
||||||
|
message["content"] += "\n\n" + rule
|
||||||
|
return
|
||||||
|
messages.insert(0, {"role": "system", "content": rule})
|
||||||
|
|
||||||
|
def _classify(self, raw_text: str) -> list[str]:
|
||||||
|
text = self._normalise(raw_text)
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
|
||||||
|
selected: list[str] = []
|
||||||
|
|
||||||
|
platform = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\bathena\b", r"\bmikeai\b", r"\bki[- ]host\b",
|
||||||
|
r"\bai[- ]profile[- ]router\b", r"\bprofil[- ]router\b",
|
||||||
|
r"\brecovery[- ](?:koffer|bundle|skript)\b",
|
||||||
|
r"\b(?:disaster|bare metal)[- ]recovery\b",
|
||||||
|
r"\b(?:installations?|reinstall|setup)[- ]skript\b",
|
||||||
|
r"\bplattform(?:wissen|dokumentation)?\b",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
homeassistant = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\bhome\s*assistant\b", r"\bhomeassistant\b", r"\bhass\b",
|
||||||
|
r"\bautomatisierung(?:en)?\b", r"\bentit[aä]t(?:en)?\b",
|
||||||
|
r"\b(?:sensor|light|switch|climate|automation)\.[\w.-]+",
|
||||||
|
r"\b(?:temperatur|luftfeuchtigkeit|wie warm)\b.*\b"
|
||||||
|
r"(?:k[uü]che|wohnzimmer|schlafzimmer|bad|toilette|keller|haus)\b",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
arr = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\bsonarr\b", r"\bradarr\b", r"\barr[- ]mcp\b",
|
||||||
|
r"\b(?:staffel|episode|folge)(?:n)?\b.*\b"
|
||||||
|
r"(?:fehl|such|download|indexer|release|serie)\w*",
|
||||||
|
r"\b(?:serie|film)\b.*\b(?:sonarr|radarr|indexer|release)\b",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
navidrome = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\bnavidrome\b", r"\bmusikbibliothek\b", r"\bplaylist\b",
|
||||||
|
r"\b(?:titel|song|track)\b.*\b(?:[aä]hnlich|empfehl|playlist|bibliothek)\w*",
|
||||||
|
r"\b(?:album|k[uü]nstler|artist)\b.*\b(?:navidrome|bibliothek|playlist)\b",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
unraid = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\bunraid\b", r"\bhome\s*server\b", r"\bhomeserver\b",
|
||||||
|
r"\bparity\b", r"\barray\b", r"\bmua\b",
|
||||||
|
r"\bdocker[- ]container\b", r"\bcontainer(?:n)?\b.*\b"
|
||||||
|
r"(?:docker|log|update|l[aä]uft|laufen|gestoppt|unraid)\b",
|
||||||
|
r"\b(?:docker|container)\b.*\b(?:unraid|homeserver|nas)\b",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
github = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\bgithub\b", r"github\.com/", r"\brepositor(?:y|ium|ies)\b",
|
||||||
|
r"\bgit[- ]repo\b", r"\bsource[- ]tree\b",
|
||||||
|
r"\b(?:readme|quellcode|sourcecode|code)\b.*\b(?:repo|github)\b",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# A specialist source is more precise than public web search. Platform
|
||||||
|
# wins over a generic Docker mention when Athena is explicitly named.
|
||||||
|
if platform:
|
||||||
|
selected.append("platform")
|
||||||
|
elif homeassistant:
|
||||||
|
selected.append("homeassistant")
|
||||||
|
elif arr:
|
||||||
|
selected.append("arr")
|
||||||
|
elif navidrome:
|
||||||
|
selected.append("navidrome")
|
||||||
|
elif unraid:
|
||||||
|
selected.append("unraid")
|
||||||
|
elif github:
|
||||||
|
selected.append("github")
|
||||||
|
|
||||||
|
explicit_web = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\b(?:im|ins|das|dem) (?:internet|netz|web)\b",
|
||||||
|
r"\bwebsuche\b", r"\bonline (?:such|nachschau|recherch|pr[uü]f)\w*",
|
||||||
|
r"\b(?:internet|web) (?:such|nachschau|recherch|pr[uü]f)\w*",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
current_public = self._matches(
|
||||||
|
text,
|
||||||
|
(
|
||||||
|
r"\b(?:wetter|regen|regnet|regnen|regenradar|vorhersage)\b",
|
||||||
|
r"\b(?:nachrichten|news|schlagzeilen)\b",
|
||||||
|
r"\b(?:preis|preise|verf[uü]gbar|verf[uü]gbarkeit)\b",
|
||||||
|
r"\b(?:neueste|neuestes|neuerungen|release)\b",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (explicit_web or (current_public and not selected)) and "web" not in selected:
|
||||||
|
selected.append("web")
|
||||||
|
|
||||||
|
# A direct GitHub reference should still use GitHub even if broader web
|
||||||
|
# research is also requested.
|
||||||
|
if github and "github" not in selected:
|
||||||
|
selected.insert(0, "github")
|
||||||
|
|
||||||
|
return selected[: max(0, self.valves.max_automatic_tools)]
|
||||||
|
|
||||||
|
async def _notify(self, emitter, labels: list[str]) -> None:
|
||||||
|
if emitter is None or not self.valves.show_selection_status:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await emitter(
|
||||||
|
{
|
||||||
|
"type": "status",
|
||||||
|
"data": {
|
||||||
|
"description": "Automatisch bereitgestellt: " + ", ".join(labels),
|
||||||
|
"done": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def inlet(self, body: dict, __event_emitter__=None, **kwargs) -> dict:
|
||||||
|
if not self.valves.enabled:
|
||||||
|
return body
|
||||||
|
|
||||||
|
selected = self._classify(self._latest_user_text(body))
|
||||||
|
if not selected:
|
||||||
|
return body
|
||||||
|
|
||||||
|
existing = body.get("tool_ids")
|
||||||
|
if not isinstance(existing, list):
|
||||||
|
existing = []
|
||||||
|
merged = list(existing)
|
||||||
|
for category in selected:
|
||||||
|
tool_id = self.TOOL_IDS[category]
|
||||||
|
if tool_id not in merged:
|
||||||
|
merged.append(tool_id)
|
||||||
|
body["tool_ids"] = merged
|
||||||
|
|
||||||
|
labels = [self.LABELS[category] for category in selected]
|
||||||
|
self._add_system_rule(body, labels)
|
||||||
|
await self._notify(__event_emitter__, labels)
|
||||||
|
return body
|
||||||
@@ -9,7 +9,7 @@ ACTION_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/actions" && pwd)
|
|||||||
|
|
||||||
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
|
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
|
||||||
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
|
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
|
||||||
for file in reasoning_default_off.py thinking.py stability_guard.py secret_redaction.py spoken_tool_status.py local_performance_metrics.py; do
|
for file in reasoning_default_off.py thinking.py auto_tool_selector.py stability_guard.py secret_redaction.py spoken_tool_status.py local_performance_metrics.py; do
|
||||||
[[ -s $FILTER_DIR/$file ]] || die "Filterdatei fehlt: $file"
|
[[ -s $FILTER_DIR/$file ]] || die "Filterdatei fehlt: $file"
|
||||||
done
|
done
|
||||||
[[ -s $ACTION_DIR/quick_actions.py ]] || die "Actiondatei fehlt: quick_actions.py"
|
[[ -s $ACTION_DIR/quick_actions.py ]] || die "Actiondatei fehlt: quick_actions.py"
|
||||||
@@ -67,9 +67,9 @@ if not {"key", "value", "updated_at"} <= config_columns:
|
|||||||
owner = requested_owner
|
owner = requested_owner
|
||||||
if not owner:
|
if not owner:
|
||||||
existing = con.execute(
|
existing = con.execute(
|
||||||
"select user_id from function where id in (?, ?, ?, ?, ?, ?, ?) order by id limit 1",
|
"select user_id from function where id in (?, ?, ?, ?, ?, ?, ?, ?) order by id limit 1",
|
||||||
(
|
(
|
||||||
"reasoning_default_off", "thinking", "stability_guard",
|
"reasoning_default_off", "thinking", "auto_tool_selector", "stability_guard",
|
||||||
"secret_redaction", "spoken_tool_status", "local_performance_metrics",
|
"secret_redaction", "spoken_tool_status", "local_performance_metrics",
|
||||||
"quick_actions",
|
"quick_actions",
|
||||||
),
|
),
|
||||||
@@ -88,6 +88,11 @@ now = int(time.time())
|
|||||||
functions = [
|
functions = [
|
||||||
("reasoning_default_off", "Reasoning Default Off", "filter", 10, filter_dir, ""),
|
("reasoning_default_off", "Reasoning Default Off", "filter", 10, filter_dir, ""),
|
||||||
("thinking", "Thinking", "filter", 20, filter_dir, ""),
|
("thinking", "Thinking", "filter", 20, filter_dir, ""),
|
||||||
|
(
|
||||||
|
"auto_tool_selector", "MikeAI Auto Tool Selector", "filter", 25, filter_dir,
|
||||||
|
"Stellt pro Anfrage höchstens zwei passende MCP-Werkzeuge bereit. "
|
||||||
|
"Die Auswahl ist keine Freigabe für schreibende Aktionen.",
|
||||||
|
),
|
||||||
("stability_guard", "MikeAI Stability Guard", "filter", 30, filter_dir, ""),
|
("stability_guard", "MikeAI Stability Guard", "filter", 30, filter_dir, ""),
|
||||||
("secret_redaction", "MikeAI Secret Redaction", "filter", 40, filter_dir, ""),
|
("secret_redaction", "MikeAI Secret Redaction", "filter", 40, filter_dir, ""),
|
||||||
(
|
(
|
||||||
@@ -312,7 +317,7 @@ with con:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
"OpenWebUI konfiguriert: Default Off=10, Thinking=20, "
|
"OpenWebUI konfiguriert: Default Off=10, Thinking=20, Auto Tool Selector=25, "
|
||||||
"Stability Guard=30, Secret Redaction=40, Spoken Tool Status=80, Local Metrics=90, "
|
"Stability Guard=30, Secret Redaction=40, Spoken Tool Status=80, Local Metrics=90, "
|
||||||
"Quick Actions=100, Folgefragen=aus, Piper-TTS=aktiv, Werkzeugwahl=optimiert"
|
"Quick Actions=100, Folgefragen=aus, Piper-TTS=aktiv, Werkzeugwahl=optimiert"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -93,17 +93,16 @@ now = int(time.time())
|
|||||||
filter_ids = [
|
filter_ids = [
|
||||||
"reasoning_default_off",
|
"reasoning_default_off",
|
||||||
"thinking",
|
"thinking",
|
||||||
|
"auto_tool_selector",
|
||||||
"stability_guard",
|
"stability_guard",
|
||||||
"secret_redaction",
|
"secret_redaction",
|
||||||
"local_performance_metrics",
|
"local_performance_metrics",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Open WebUI addresses a global MCP server as server:mcp:<connection-id>.
|
# The Auto Tool Selector attaches a bounded, relevant MCP set per request.
|
||||||
# Attach the bounded web relay and, when its dedicated secret exists, the four
|
# Keeping this list empty prevents unrelated schemas from consuming context.
|
||||||
# read-only official GitHub tools. This is not Open WebUI's built-in web search.
|
# Users can still enable additional tools manually in a chat.
|
||||||
default_tool_ids = ["server:mcp:web-local"]
|
default_tool_ids = []
|
||||||
if github_enabled:
|
|
||||||
default_tool_ids.append("server:mcp:github-local")
|
|
||||||
|
|
||||||
def capabilities(vision: bool) -> dict:
|
def capabilities(vision: bool) -> dict:
|
||||||
return {
|
return {
|
||||||
@@ -310,8 +309,7 @@ with con:
|
|||||||
"tags": [{"name": tag} for tag in profile["tags"]],
|
"tags": [{"name": tag} for tag in profile["tags"]],
|
||||||
"toolIds": default_tool_ids,
|
"toolIds": default_tool_ids,
|
||||||
# Built-in features remain available but are not forced on every
|
# Built-in features remain available but are not forced on every
|
||||||
# request. Only the bounded web MCP and four read-only GitHub
|
# request. The Auto Tool Selector supplies relevant MCPs per turn.
|
||||||
# repository tools are attached by default.
|
|
||||||
"defaultFeatureIds": [],
|
"defaultFeatureIds": [],
|
||||||
"filterIds": filter_ids,
|
"filterIds": filter_ids,
|
||||||
"actionIds": ["quick_actions"],
|
"actionIds": ["quick_actions"],
|
||||||
|
|||||||
Reference in new issue
Block a user