fix: bound broad tool workflows
This commit is contained in:
+1
-1
@@ -714,7 +714,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: platform/openwebui/Dockerfile
|
dockerfile: platform/openwebui/Dockerfile
|
||||||
image: ${OPENWEBUI_IMAGE:-mike-ai/openwebui:main-01f4282-agent-loop-v6}
|
image: ${OPENWEBUI_IMAGE:-mike-ai/openwebui:main-01f4282-agent-loop-v7}
|
||||||
container_name: mike-ai-open-webui
|
container_name: mike-ai-open-webui
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -212,6 +212,14 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result["tool_ids"], ["server:mcp:homeassistant-local"]
|
result["tool_ids"], ["server:mcp:homeassistant-local"]
|
||||||
)
|
)
|
||||||
|
self.assertIn("Never call ha_list_states merely", result["messages"][0]["content"])
|
||||||
|
self.assertIn("Never infer an automation entity_id", result["messages"][0]["content"])
|
||||||
|
|
||||||
|
async def test_hyphenated_homeassistant_and_tool_name_are_selected(self):
|
||||||
|
result = await self._select(
|
||||||
|
"Führe einen Home-Assistant-Test aus und nutze ha_list_states genau einmal."
|
||||||
|
)
|
||||||
|
self.assertEqual(result["tool_ids"], ["server:mcp:homeassistant-local"])
|
||||||
|
|
||||||
async def test_general_web_is_available_without_site_specific_rules(self):
|
async def test_general_web_is_available_without_site_specific_rules(self):
|
||||||
result = await self._select("Erkläre mir kurz, wie ein Fahrrad funktioniert.")
|
result = await self._select("Erkläre mir kurz, wie ein Fahrrad funktioniert.")
|
||||||
@@ -219,15 +227,15 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertTrue(result["metadata"]["features"]["web_search"])
|
self.assertTrue(result["metadata"]["features"]["web_search"])
|
||||||
self.assertNotIn("tool_ids", result)
|
self.assertNotIn("tool_ids", result)
|
||||||
|
|
||||||
async def test_weather_uses_native_web_without_mcp(self):
|
async def test_weather_gets_native_web_and_compact_fallback(self):
|
||||||
result = await self._select("Soll es heute in Rastatt regnen?")
|
result = await self._select("Soll es heute in Rastatt regnen?")
|
||||||
self.assertNotIn("tool_ids", result)
|
self.assertEqual(result["tool_ids"], ["server:mcp:web-general-local"])
|
||||||
|
|
||||||
async def test_youtube_channel_question_uses_native_web(self):
|
async def test_youtube_channel_question_gets_general_web_fallback(self):
|
||||||
result = await self._select(
|
result = await self._select(
|
||||||
"Welches Video steht aktuell oben auf dem YouTube-Kanal The Proper People?"
|
"Welches Video steht aktuell oben auf dem YouTube-Kanal The Proper People?"
|
||||||
)
|
)
|
||||||
self.assertNotIn("tool_ids", result)
|
self.assertEqual(result["tool_ids"], ["server:mcp:web-general-local"])
|
||||||
|
|
||||||
async def test_unraid_uses_readonly_not_mua(self):
|
async def test_unraid_uses_readonly_not_mua(self):
|
||||||
result = await self._select(
|
result = await self._select(
|
||||||
@@ -272,7 +280,10 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"aktuelle offizielle Liste online und prüfe jede fehlende Folge bei Deezer. "
|
"aktuelle offizielle Liste online und prüfe jede fehlende Folge bei Deezer. "
|
||||||
"Nur lesen, ohne Downloads oder Änderungen."
|
"Nur lesen, ohne Downloads oder Änderungen."
|
||||||
)
|
)
|
||||||
self.assertEqual(result["tool_ids"], ["server:mcp:mua-readonly-local"])
|
self.assertEqual(
|
||||||
|
result["tool_ids"],
|
||||||
|
["server:mcp:mua-readonly-local", "server:mcp:web-general-local"],
|
||||||
|
)
|
||||||
self.assertTrue(result["features"]["web_search"])
|
self.assertTrue(result["features"]["web_search"])
|
||||||
self.assertTrue(result["metadata"]["features"]["web_search"])
|
self.assertTrue(result["metadata"]["features"]["web_search"])
|
||||||
self.assertIn("search_web/fetch_url", result["messages"][0]["content"])
|
self.assertIn("search_web/fetch_url", result["messages"][0]["content"])
|
||||||
@@ -329,20 +340,36 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
)
|
)
|
||||||
self.assertIn("integrate or relay", result["messages"][0]["content"])
|
self.assertIn("integrate or relay", result["messages"][0]["content"])
|
||||||
|
|
||||||
async def test_github_and_explicit_web_only_adds_github_mcp(self):
|
async def test_readonly_integration_plan_does_not_attach_unraid_admin(self):
|
||||||
|
result = await self._select(
|
||||||
|
"Analysiere https://github.com/foo/deemix und prüfe den auf Unraid "
|
||||||
|
"laufenden Container und entwirf einen Athena-MCP. Erstelle keinen zweiten Container, ändere nichts "
|
||||||
|
"und lies keine Secrets."
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
result["tool_ids"],
|
||||||
|
[
|
||||||
|
"server:mcp:github-local",
|
||||||
|
"server:mcp:athena-operator-local",
|
||||||
|
"server:mcp:mua-readonly-local",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_github_and_explicit_web_adds_compact_general_web(self):
|
||||||
result = await self._select(
|
result = await self._select(
|
||||||
"Prüfe dieses GitHub Repository und suche zusätzlich im Netz nach Nutzerstimmen."
|
"Prüfe dieses GitHub Repository und suche zusätzlich im Netz nach Nutzerstimmen."
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result["tool_ids"],
|
result["tool_ids"],
|
||||||
["server:mcp:github-local"],
|
["server:mcp:github-local", "server:mcp:web-general-local"],
|
||||||
)
|
)
|
||||||
|
|
||||||
async def test_plain_public_web_request_does_not_attach_legacy_web_mcp(self):
|
async def test_plain_public_web_request_attaches_general_not_legacy_web(self):
|
||||||
result = await self._select(
|
result = await self._select(
|
||||||
"Suche im Netz auf MakerWorld einen Schlümpfe-Schlüsselanhänger."
|
"Suche im Netz auf MakerWorld einen Schlümpfe-Schlüsselanhänger."
|
||||||
)
|
)
|
||||||
self.assertNotIn("server:mcp:web-local", result.get("tool_ids", []))
|
self.assertNotIn("server:mcp:web-local", result.get("tool_ids", []))
|
||||||
|
self.assertIn("server:mcp:web-general-local", result.get("tool_ids", []))
|
||||||
|
|
||||||
async def test_manual_tool_is_preserved(self):
|
async def test_manual_tool_is_preserved(self):
|
||||||
result = await self._select(
|
result = await self._select(
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ Der isolierte Eignungs- und Ausfalltest ist in
|
|||||||
|
|
||||||
- Docker Compose
|
- Docker Compose
|
||||||
- SearXNG, per Digest gepinnt
|
- SearXNG, per Digest gepinnt
|
||||||
- TinySearch 0.5.1, per Digest gepinnt
|
- TinySearch 0.6.1, per Digest gepinnt
|
||||||
- TinySearch ausschließlich im internen Docker-Netz, ohne Host-Port
|
- TinySearch ausschließlich im internen Docker-Netz, ohne Host-Port
|
||||||
- lokale ONNX-Embeddings
|
- lokale ONNX-Embeddings
|
||||||
- OpenWebUI-native allgemeine Suche und Seitenabruf in allen normalen Profilen
|
- OpenWebUI-native allgemeine Suche und Seitenabruf in allen normalen Profilen
|
||||||
@@ -209,7 +209,16 @@ höchstens zwölf Aufrufe desselben Werkzeugnamens und höchstens zweimal exakt
|
|||||||
dieselbe Signatur. Nach Ende des Budgets stehen zusätzliche interne Runden ausschließlich
|
dieselbe Signatur. Nach Ende des Budgets stehen zusätzliche interne Runden ausschließlich
|
||||||
für eine sichtbare werkzeugfreie Schlussantwort bereit. Das produktive
|
für eine sichtbare werkzeugfreie Schlussantwort bereit. Das produktive
|
||||||
OpenWebUI-Derivat trägt den Tag
|
OpenWebUI-Derivat trägt den Tag
|
||||||
`mike-ai/openwebui:main-01f4282-agent-loop-v6`.
|
`mike-ai/openwebui:main-01f4282-agent-loop-v7`. V7 begrenzt zusätzlich die
|
||||||
|
Werkzeugantworten in OpenWebUIs internen Fortsetzungsrunden auf 12.000 Zeichen
|
||||||
|
je Ergebnis und 64.000 Zeichen pro Antwortlauf. Damit greift die Begrenzung
|
||||||
|
auch bei Ergebnissen, die erst nach dem ersten Request entstehen.
|
||||||
|
|
||||||
|
Auto Tool Selector 4.2 hält native Websuche immer verfügbar, ergänzt bei
|
||||||
|
expliziter Webrecherche den TinySearch-Fallback und erkennt unter anderem
|
||||||
|
`Home Assistant`, `Home-Assistant` sowie direkte `ha_*`-Werkzeugbezüge. Für
|
||||||
|
Home Assistant verlangt er gezielte Zustandsabfragen und verbietet die
|
||||||
|
Ableitung einer `entity_id` aus einer YAML-`id`.
|
||||||
|
|
||||||
Der Platform Context MCP läuft ohne Docker-Socket, Shell, Egress oder Secrets.
|
Der Platform Context MCP läuft ohne Docker-Socket, Shell, Egress oder Secrets.
|
||||||
Ein root-eigener Minutentimer erzeugt nur einen begrenzten Laufzeitsnapshot.
|
Ein root-eigener Minutentimer erzeugt nur einen begrenzten Laufzeitsnapshot.
|
||||||
|
|||||||
@@ -39,8 +39,11 @@ benötigte deshalb kleinere, klarere Werkzeuge und harte Abbruchgrenzen.
|
|||||||
erhält der Benutzer deshalb eine sichtbare Antwort aus den vorhandenen
|
erhält der Benutzer deshalb eine sichtbare Antwort aus den vorhandenen
|
||||||
Befunden samt ehrlicher Angabe fehlender Belege. Inlet-Filter allein können
|
Befunden samt ehrlicher Angabe fehlender Belege. Inlet-Filter allein können
|
||||||
dies nicht erzwingen, weil sie zwischen OpenWebUIs internen Werkzeugrunden
|
dies nicht erzwingen, weil sie zwischen OpenWebUIs internen Werkzeugrunden
|
||||||
nicht erneut ausgeführt werden. Ein einzelnes Resultat ist auf 12.000, alle
|
nicht erneut ausgeführt werden. Seit OpenWebUI-Derivat V7 sitzt die
|
||||||
Resultate zusammen auf 64.000 Zeichen begrenzt.
|
Größenbegrenzung deshalb direkt in der internen Fortsetzungsschleife: ein
|
||||||
|
einzelnes Resultat ist auf 12.000, alle Resultate zusammen auf 64.000
|
||||||
|
Zeichen begrenzt. Zitate und sichtbarer Werkzeugstatus werden zuvor
|
||||||
|
verarbeitet; das Modell erhält eine markierte Kopf-/Ende-Verdichtung.
|
||||||
Die Basis ist unveränderlich auf OpenWebUI-Revision
|
Die Basis ist unveränderlich auf OpenWebUI-Revision
|
||||||
`01f4282f1ffe0d6212f58d3afbeae21fffd0c4be` beziehungsweise Image-Digest
|
`01f4282f1ffe0d6212f58d3afbeae21fffd0c4be` beziehungsweise Image-Digest
|
||||||
`sha256:6a773e5c3a246b65cbe74ce942b294292c0e5f81c138f703d111bc162f7d7c3d`
|
`sha256:6a773e5c3a246b65cbe74ce942b294292c0e5f81c138f703d111bc162f7d7c3d`
|
||||||
@@ -65,7 +68,7 @@ benötigte deshalb kleinere, klarere Werkzeuge und harte Abbruchgrenzen.
|
|||||||
|
|
||||||
## Abnahme
|
## Abnahme
|
||||||
|
|
||||||
- OpenWebUI-Filtertests: 34
|
- OpenWebUI-Filtertests: 36
|
||||||
- Web-MCP-Tests: 9
|
- Web-MCP-Tests: 9
|
||||||
- Athena-Operator-Tests: 13
|
- Athena-Operator-Tests: 13
|
||||||
- Platform-Context-Test: bestanden
|
- Platform-Context-Test: bestanden
|
||||||
@@ -82,6 +85,16 @@ sudo /opt/mike-ai/stack/dev/verify_mcp_catalogs.sh
|
|||||||
|
|
||||||
Er muss mit `MCP_CATALOG_SUITE_OK` enden.
|
Er muss mit `MCP_CATALOG_SUITE_OK` enden.
|
||||||
|
|
||||||
|
## Reale Browserabnahme
|
||||||
|
|
||||||
|
Die angemeldete OpenWebUI-Sitzung bestand am 24. August 2026 folgende Läufe:
|
||||||
|
|
||||||
|
- unbekannte Website MakerWorld über native Suche plus TinySearch 0.6.1
|
||||||
|
- GitHub-Repository plus vorhandener Unraid-Container plus Athena-Planung
|
||||||
|
- mehrstufige Home-Assistant-YAML-Analyse ohne Vollinventar
|
||||||
|
- bewusst angeforderte 223-KB-Klasse einer Home-Assistant-Zustandsliste; V7
|
||||||
|
lieferte trotz 2.169 Zuständen nach 30,9 Sekunden eine sichtbare Kurzantwort
|
||||||
|
|
||||||
## Noch manuell zu prüfen
|
## Noch manuell zu prüfen
|
||||||
|
|
||||||
Ein echter Browsertest mit einer bewusst synthetischen CSV benötigt eine
|
Ein echter Browsertest mit einer bewusst synthetischen CSV benötigt eine
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ MCPs sind über feste WireGuard-Ports auch für Hermes und Pi erreichbar.
|
|||||||
- bei ausgeschöpftem Budget folgt zwingend eine werkzeugfreie, sichtbare
|
- bei ausgeschöpftem Budget folgt zwingend eine werkzeugfreie, sichtbare
|
||||||
Schlussantwort aus den bereits erhobenen Befunden
|
Schlussantwort aus den bereits erhobenen Befunden
|
||||||
- Werkzeugausgaben bleiben kurz: 12.000 Zeichen je Ergebnis und 64.000 Zeichen
|
- Werkzeugausgaben bleiben kurz: 12.000 Zeichen je Ergebnis und 64.000 Zeichen
|
||||||
über den Verlauf; ältere Resultate werden zuerst verdichtet
|
über den Verlauf. Die Begrenzung sitzt in OpenWebUIs interner
|
||||||
|
Fortsetzungsschleife und greift daher auch auf Resultate, die erst nach dem
|
||||||
|
ersten Modellschritt entstehen.
|
||||||
|
|
||||||
Damit stoppt die Plattform bewiesene Schleifen, nicht normale lange Recherche.
|
Damit stoppt die Plattform bewiesene Schleifen, nicht normale lange Recherche.
|
||||||
Die früheren Grenzen von zwölf Gesamtaufrufen und vier Aufrufen je Werkzeug
|
Die früheren Grenzen von zwölf Gesamtaufrufen und vier Aufrufen je Werkzeug
|
||||||
@@ -54,7 +56,7 @@ waren für Qwen3.8-Agentenaufgaben zu klein.
|
|||||||
| Client | Standardweg |
|
| Client | Standardweg |
|
||||||
|---|---|
|
|---|---|
|
||||||
| OpenWebUI | native `search_web` und `fetch_url`, immer verfügbar |
|
| OpenWebUI | native `search_web` und `fetch_url`, immer verfügbar |
|
||||||
| Hermes/Pi/andere MCP-Clients | `http://192.168.1.212:8203/mcp` (TinySearch) |
|
| Hermes/Pi/andere MCP-Clients | `http://192.168.1.212:8203/mcp` (TinySearch; laufender Alt-Gateway zusätzlich 8211 bis zum nächsten geplanten WireGuard-Neustart) |
|
||||||
| Spezial-/Rollbackbedarf | historischer `mcp-web` nur mit Compose-Profil `legacy-web` |
|
| Spezial-/Rollbackbedarf | historischer `mcp-web` nur mit Compose-Profil `legacy-web` |
|
||||||
|
|
||||||
TinySearch stellt die vier Upstream-Werkzeuge `search`, `scrape_urls`,
|
TinySearch stellt die vier Upstream-Werkzeuge `search`, `scrape_urls`,
|
||||||
@@ -81,3 +83,14 @@ Nach Änderungen müssen mindestens folgende Prüfungen erfolgreich sein:
|
|||||||
6. Browserlauf mit einer unbekannten öffentlichen Website, GitHub plus
|
6. Browserlauf mit einer unbekannten öffentlichen Website, GitHub plus
|
||||||
Laufzeitprüfung sowie einer mehrstufigen Home-/Unraid-Aufgabe
|
Laufzeitprüfung sowie einer mehrstufigen Home-/Unraid-Aufgabe
|
||||||
|
|
||||||
|
Produktive Abnahme am 24. August 2026:
|
||||||
|
|
||||||
|
- MakerWorld ohne Site-Adapter: 12 Werkzeugaufrufe, verifizierter Treffer mit
|
||||||
|
Downloadzahl und Direktlink, sichtbare Antwort nach 102,5 Sekunden
|
||||||
|
- GitHub + Unraid + Athena: 12 gezielte Repository-Leseaufrufe plus
|
||||||
|
Laufzeitprüfung; vorhandenen Deemix-Backendcontainer korrekt wiederverwendet
|
||||||
|
- Home Assistant YAML: drei auskommentierte Automatisierungen gefunden, ohne
|
||||||
|
vollständige Zustandsliste und mit sichtbarer Abschlussantwort nach 88,0 Sekunden
|
||||||
|
- absichtlicher Großausgabetest: 2.169 Home-Assistant-Zustände in einem
|
||||||
|
Werkzeugresultat; durch V7 intern verdichtet und nach 30,9 Sekunden korrekt
|
||||||
|
mit ausschließlich Anzahl und Testsatz beantwortet
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ TinySearch bleibt als Ganzes read-only. Nur das flüchtige tmpfs-Verzeichnis
|
|||||||
temporären Browser- und Sitzungszustand erzeugt. Es wird bei jedem
|
temporären Browser- und Sitzungszustand erzeugt. Es wird bei jedem
|
||||||
Container-Neustart vollständig verworfen.
|
Container-Neustart vollständig verworfen.
|
||||||
|
|
||||||
TinySearch ist der allgemeine portable Web-MCP. Auf VPN-Port 8203 können Hermes,
|
TinySearch 0.6.1 ist der allgemeine portable Web-MCP. Auf VPN-Port 8203 können Hermes,
|
||||||
Pi und andere Clients seine vier Upstream-Werkzeuge direkt nutzen. SearXNG ist
|
Pi und andere Clients seine vier Upstream-Werkzeuge direkt nutzen. SearXNG ist
|
||||||
der Such-Backenddienst. Die historische eigene Web-Fassade ist nur Rollback.
|
der Such-Backenddienst. Die historische eigene Web-Fassade ist nur Rollback.
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ services:
|
|||||||
|
|
||||||
tinysearch:
|
tinysearch:
|
||||||
<<: *tool-common
|
<<: *tool-common
|
||||||
image: marcellm01/tinysearch@sha256:5a03d5a1f1b0fabe48f2a26e05db4a84bcb611106a57ec51e42db4549976aa9c
|
# TinySearch v0.6.1. This release fixes the crawler behavior observed with
|
||||||
|
# v0.5.1 and adds the current search -> scrape_urls workflow.
|
||||||
|
image: marcellm01/tinysearch@sha256:7a7d0585f5000f462e699e42b97409715826a9e2edcd09a166afa93a4b7cba31
|
||||||
container_name: mike-ai-tools-tinysearch
|
container_name: mike-ai-tools-tinysearch
|
||||||
dns: ["${AI_DNS:-1.1.1.1}"]
|
dns: ["${AI_DNS:-1.1.1.1}"]
|
||||||
# Crawl4AI keeps transient browser/session state here. The container stays
|
# Crawl4AI keeps transient browser/session state here. The container stays
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ fi
|
|||||||
# redundant download can hang indefinitely. Prepare only the persistent ONNX
|
# redundant download can hang indefinitely. Prepare only the persistent ONNX
|
||||||
# bundle that is actually absent on a fresh installation.
|
# bundle that is actually absent on a fresh installation.
|
||||||
docker volume create mike-ai-tools_tinysearch-models >/dev/null
|
docker volume create mike-ai-tools_tinysearch-models >/dev/null
|
||||||
tiny_image="marcellm01/tinysearch@sha256:5a03d5a1f1b0fabe48f2a26e05db4a84bcb611106a57ec51e42db4549976aa9c"
|
tiny_image="marcellm01/tinysearch@sha256:7a7d0585f5000f462e699e42b97409715826a9e2edcd09a166afa93a4b7cba31"
|
||||||
if ! docker run --rm --entrypoint test \
|
if ! docker run --rm --entrypoint test \
|
||||||
-v mike-ai-tools_tinysearch-models:/data/models "$tiny_image" \
|
-v mike-ai-tools_tinysearch-models:/data/models "$tiny_image" \
|
||||||
-f /data/models/all-minilm-l6-v2-onnx/model.onnx; then
|
-f /data/models/all-minilm-l6-v2-onnx/model.onnx; then
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
title: MikeAI Auto Tool Selector
|
title: MikeAI Auto Tool Selector
|
||||||
author: MikeAI
|
author: MikeAI
|
||||||
version: 4.0.0
|
version: 4.2.0
|
||||||
description: Keeps broad web access available and adds relevant portable MCP domains without acting as a gate.
|
description: Keeps broad web access available and adds relevant portable MCP domains without acting as a gate.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ class Filter:
|
|||||||
"navidrome": "server:mcp:navidrome-local",
|
"navidrome": "server:mcp:navidrome-local",
|
||||||
"platform": "server:mcp:athena-platform",
|
"platform": "server:mcp:athena-platform",
|
||||||
"operator": "server:mcp:athena-operator-local",
|
"operator": "server:mcp:athena-operator-local",
|
||||||
|
"web": "server:mcp:web-general-local",
|
||||||
}
|
}
|
||||||
|
|
||||||
LABELS = {
|
LABELS = {
|
||||||
@@ -42,6 +43,7 @@ class Filter:
|
|||||||
"navidrome": "Navidrome",
|
"navidrome": "Navidrome",
|
||||||
"platform": "Athena-Plattformwissen",
|
"platform": "Athena-Plattformwissen",
|
||||||
"operator": "Athena Operator",
|
"operator": "Athena Operator",
|
||||||
|
"web": "allgemeine Websuche (TinySearch)",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -83,6 +85,7 @@ class Filter:
|
|||||||
r"\b(?:web|internet|online|websuche|webrecherche)\b",
|
r"\b(?:web|internet|online|websuche|webrecherche)\b",
|
||||||
r"\b(?:such|recherchier|pr[uü]f|schau)\w*\b.{0,80}\b(?:netz|web|internet|online)\b",
|
r"\b(?:such|recherchier|pr[uü]f|schau)\w*\b.{0,80}\b(?:netz|web|internet|online)\b",
|
||||||
r"\b(?:deezer|youtube|makerworld|wikipedia|docker\s*hub)\b",
|
r"\b(?:deezer|youtube|makerworld|wikipedia|docker\s*hub)\b",
|
||||||
|
r"\b(?:wetter|regen|regnen|vorhersage)\b",
|
||||||
r"\b(?:aktuell|neueste|heute|bis heute)\w*\b.{0,100}\b(?:quelle|liste|verf[uü]gbar|erschienen|video|folge)\w*\b",
|
r"\b(?:aktuell|neueste|heute|bis heute)\w*\b.{0,100}\b(?:quelle|liste|verf[uü]gbar|erschienen|video|folge)\w*\b",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -123,6 +126,20 @@ class Filter:
|
|||||||
"contents. Use the read-only shell only when that purpose-built tool "
|
"contents. Use the read-only shell only when that purpose-built tool "
|
||||||
"cannot answer."
|
"cannot answer."
|
||||||
)
|
)
|
||||||
|
if "homeassistant" in selected:
|
||||||
|
rule += (
|
||||||
|
" For Home Assistant, start with ha_search, ha_get_state, or an "
|
||||||
|
"exact entity/configuration query. Never call ha_list_states merely "
|
||||||
|
"to verify a few referenced entities or automations; use the full "
|
||||||
|
"state inventory only when the user explicitly requests a complete "
|
||||||
|
"inventory. For YAML work, inspect the exact file or section first "
|
||||||
|
"and keep any proposed edit separate from live state verification. "
|
||||||
|
"Never infer an automation entity_id from its YAML id or alias. After "
|
||||||
|
"a reload, resolve the actual entity_id through targeted search or the "
|
||||||
|
"entity registry before calling an automation service. Avoid repeating "
|
||||||
|
"the same YAML read, state lookup, or validation unless new evidence "
|
||||||
|
"or a changed configuration makes the repetition necessary."
|
||||||
|
)
|
||||||
messages = body.setdefault("messages", [])
|
messages = body.setdefault("messages", [])
|
||||||
for message in messages:
|
for message in messages:
|
||||||
if message.get("role") == "system" and isinstance(message.get("content"), str):
|
if message.get("role") == "system" and isinstance(message.get("content"), str):
|
||||||
@@ -161,7 +178,8 @@ class Filter:
|
|||||||
homeassistant = self._matches(
|
homeassistant = self._matches(
|
||||||
text,
|
text,
|
||||||
(
|
(
|
||||||
r"\bhome\s*assist(?:ant|ent)s?\b", r"\bhomeassistants?\b", r"\bhass\b",
|
r"\bhome[-\s]*assist(?:ant|ent)s?\b", r"\bhomeassistants?\b", r"\bhass\b",
|
||||||
|
r"\bha_[a-z0-9_]+\b",
|
||||||
r"\bautomatisierung(?:en)?\b", r"\bentit[aä]t(?:en)?\b",
|
r"\bautomatisierung(?:en)?\b", r"\bentit[aä]t(?:en)?\b",
|
||||||
r"\bautomations?\.ya?ml\b", r"\bconfiguration\.ya?ml\b",
|
r"\bautomations?\.ya?ml\b", r"\bconfiguration\.ya?ml\b",
|
||||||
r"\b(?:sensor|light|switch|climate|automation)\.[\w.-]+",
|
r"\b(?:sensor|light|switch|climate|automation)\.[\w.-]+",
|
||||||
@@ -219,6 +237,7 @@ class Filter:
|
|||||||
r"\b(?:keine|keinerlei|nichts?)\b.{0,40}\b(?:[aä]nder|update|aktualisier|durchf[uü]hr|installier|download|umbenenn)\w*\b",
|
r"\b(?:keine|keinerlei|nichts?)\b.{0,40}\b(?:[aä]nder|update|aktualisier|durchf[uü]hr|installier|download|umbenenn)\w*\b",
|
||||||
r"\bohne\b.{0,50}\b(?:[aä]nder|update|aktualisier|durchf[uü]hr|installier|download|umbenenn)\w*\b",
|
r"\bohne\b.{0,50}\b(?:[aä]nder|update|aktualisier|durchf[uü]hr|installier|download|umbenenn)\w*\b",
|
||||||
r"\b(?:nicht|nichts?)\b.{0,20}\b(?:durchf[uü]hr|ausf[uü]hr|aktualisier|installier)\w*\b",
|
r"\b(?:nicht|nichts?)\b.{0,20}\b(?:durchf[uü]hr|ausf[uü]hr|aktualisier|installier)\w*\b",
|
||||||
|
r"\b(?:[aä]nder|erstell|installier|l[oö]sch|entfern|download|umbenenn)\w*\b.{0,25}\b(?:nichts?|keine[nrms]?|keinerlei)\b",
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
unraid_write = False
|
unraid_write = False
|
||||||
@@ -282,8 +301,14 @@ class Filter:
|
|||||||
return body
|
return body
|
||||||
|
|
||||||
latest_text = self._latest_user_text(body)
|
latest_text = self._latest_user_text(body)
|
||||||
selected = self._classify(latest_text)
|
|
||||||
needs_native_web = self._needs_native_web(latest_text)
|
needs_native_web = self._needs_native_web(latest_text)
|
||||||
|
selected = self._classify(latest_text)
|
||||||
|
# Web work gets two independent, general-purpose engines. Native
|
||||||
|
# OpenWebUI search has good result rendering but can return empty sets
|
||||||
|
# for an otherwise valid query. TinySearch adds only four compact
|
||||||
|
# tools and provides a portable fallback without site-specific MCPs.
|
||||||
|
if needs_native_web and "web" not in selected:
|
||||||
|
selected.append("web")
|
||||||
# General web search is a basic capability, not a site-specific MCP.
|
# General web search is a basic capability, not a site-specific MCP.
|
||||||
# Keep it available on every normal request so an unfamiliar website
|
# Keep it available on every normal request so an unfamiliar website
|
||||||
# (MakerWorld, eBay, a vendor page tomorrow) never requires another
|
# (MakerWorld, eBay, a vendor page tomorrow) never requires another
|
||||||
@@ -298,8 +323,6 @@ class Filter:
|
|||||||
metadata_features["web_search"] = True
|
metadata_features["web_search"] = True
|
||||||
|
|
||||||
if not selected:
|
if not selected:
|
||||||
if needs_native_web:
|
|
||||||
await self._notify(__event_emitter__, ["native Websuche"])
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
existing = body.get("tool_ids")
|
existing = body.get("tool_ids")
|
||||||
@@ -335,10 +358,12 @@ class Filter:
|
|||||||
for message in body.get("messages", []):
|
for message in body.get("messages", []):
|
||||||
if message.get("role") == "system" and isinstance(message.get("content"), str):
|
if message.get("role") == "system" and isinstance(message.get("content"), str):
|
||||||
message["content"] += (
|
message["content"] += (
|
||||||
" Native Open WebUI web search is also enabled for this request. "
|
" Two broad web engines are available: native Open WebUI "
|
||||||
"After collecting local or specialist-tool evidence, continue with "
|
"search_web/fetch_url and TinySearch search/scrape_urls/research. "
|
||||||
"search_web/fetch_url for current public evidence; do not stop merely "
|
"Start with one focused query. If it returns no results, an access "
|
||||||
"because no separate web MCP appears in the MCP tool list."
|
"block, or weak evidence, switch once to the other engine with a "
|
||||||
|
"reformulated query. Do not repeat many near-identical searches and "
|
||||||
|
"do not request or wait for a site-specific MCP."
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
await self._notify(__event_emitter__, labels)
|
await self._notify(__event_emitter__, labels)
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ replacement = """ tool_call_iterations = 0
|
|||||||
tool_call_executions = 0
|
tool_call_executions = 0
|
||||||
max_tool_call_executions = 40
|
max_tool_call_executions = 40
|
||||||
max_executions_per_tool = 12
|
max_executions_per_tool = 12
|
||||||
|
# Inlet filters only see the first request, not later internal
|
||||||
|
# tool continuations. Bound the evidence injected into those
|
||||||
|
# continuations so one broad inventory cannot consume context.
|
||||||
|
max_single_tool_result_chars = 12000
|
||||||
|
max_total_tool_result_chars = 64000
|
||||||
|
tool_result_chars_used = 0
|
||||||
tool_execution_counts = {}
|
tool_execution_counts = {}
|
||||||
tool_signature_counts = {}
|
tool_signature_counts = {}
|
||||||
max_tool_call_iterations = getattr(
|
max_tool_call_iterations = getattr(
|
||||||
@@ -96,6 +102,64 @@ if source.count(needle) != 1:
|
|||||||
)
|
)
|
||||||
source = source.replace(needle, replacement)
|
source = source.replace(needle, replacement)
|
||||||
|
|
||||||
|
needle = """ results.append(
|
||||||
|
{
|
||||||
|
'tool_call_id': tool_call_id,
|
||||||
|
'content': str(tool_result) if tool_result else '',
|
||||||
|
**({'files': tool_result_files} if tool_result_files else {}),
|
||||||
|
**({'embeds': tool_result_embeds} if tool_result_embeds else {}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
replacement = """ # Citations and the visible terminal event above use the
|
||||||
|
# complete result. Only the model continuation is compacted.
|
||||||
|
tool_result_text = str(tool_result) if tool_result else ''
|
||||||
|
remaining_tool_chars = max(
|
||||||
|
0, max_total_tool_result_chars - tool_result_chars_used
|
||||||
|
)
|
||||||
|
allowed_tool_chars = min(
|
||||||
|
max_single_tool_result_chars, remaining_tool_chars
|
||||||
|
)
|
||||||
|
if len(tool_result_text) > allowed_tool_chars:
|
||||||
|
original_tool_chars = len(tool_result_text)
|
||||||
|
if allowed_tool_chars >= 800:
|
||||||
|
marker = (
|
||||||
|
'\\n\\n[Tool result compacted by Open WebUI: '
|
||||||
|
f'{original_tool_chars} characters total; middle omitted. '
|
||||||
|
'Refine the next tool call instead of requesting the same '
|
||||||
|
'broad inventory again.]\\n\\n'
|
||||||
|
)
|
||||||
|
tail_chars = min(2000, allowed_tool_chars // 4)
|
||||||
|
head_chars = max(
|
||||||
|
0, allowed_tool_chars - len(marker) - tail_chars
|
||||||
|
)
|
||||||
|
tool_result_text = (
|
||||||
|
tool_result_text[:head_chars]
|
||||||
|
+ marker
|
||||||
|
+ tool_result_text[-tail_chars:]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
tool_result_text = (
|
||||||
|
'[Tool-result context budget exhausted. Use a more targeted '
|
||||||
|
'tool call or answer from existing evidence.]'
|
||||||
|
)[:allowed_tool_chars]
|
||||||
|
tool_result_chars_used += len(tool_result_text)
|
||||||
|
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
'tool_call_id': tool_call_id,
|
||||||
|
'content': tool_result_text,
|
||||||
|
**({'files': tool_result_files} if tool_result_files else {}),
|
||||||
|
**({'embeds': tool_result_embeds} if tool_result_embeds else {}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
if source.count(needle) != 1:
|
||||||
|
raise SystemExit(
|
||||||
|
f"expected exactly one Open WebUI tool-result anchor, found {source.count(needle)}"
|
||||||
|
)
|
||||||
|
source = source.replace(needle, replacement)
|
||||||
|
|
||||||
needle = """ res = await generate_chat_completion(
|
needle = """ res = await generate_chat_completion(
|
||||||
request,
|
request,
|
||||||
new_form_data,
|
new_form_data,
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ install -d -m 0700 /data/mike-ai-operator/state /data/mike-ai-operator/repositor
|
|||||||
install -m 0755 "$ROOT/platform/operator/athena_operatord.py" /usr/local/libexec/mike-ai-athena-operatord
|
install -m 0755 "$ROOT/platform/operator/athena_operatord.py" /usr/local/libexec/mike-ai-athena-operatord
|
||||||
install -m 0644 "$ROOT/platform/operator/athena-operator.service" /etc/systemd/system/mike-ai-athena-operator.service
|
install -m 0644 "$ROOT/platform/operator/athena-operator.service" /etc/systemd/system/mike-ai-athena-operator.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now mike-ai-athena-operator.service
|
systemctl enable mike-ai-athena-operator.service
|
||||||
|
# The unit may already be active during an in-place upgrade. `enable --now`
|
||||||
|
# does not reload a running process after its executable was replaced, which
|
||||||
|
# would leave the MCP facade and executor on different protocol versions.
|
||||||
|
systemctl restart mike-ai-athena-operator.service
|
||||||
systemctl is-active --quiet mike-ai-athena-operator.service
|
systemctl is-active --quiet mike-ai-athena-operator.service
|
||||||
echo ATHENA_OPERATOR_EXECUTOR_OK
|
echo ATHENA_OPERATOR_EXECUTOR_OK
|
||||||
|
|||||||
Reference in New Issue
Block a user