fix: bound broad tool workflows
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
title: MikeAI Auto Tool Selector
|
||||
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.
|
||||
"""
|
||||
|
||||
@@ -31,6 +31,7 @@ class Filter:
|
||||
"navidrome": "server:mcp:navidrome-local",
|
||||
"platform": "server:mcp:athena-platform",
|
||||
"operator": "server:mcp:athena-operator-local",
|
||||
"web": "server:mcp:web-general-local",
|
||||
}
|
||||
|
||||
LABELS = {
|
||||
@@ -42,6 +43,7 @@ class Filter:
|
||||
"navidrome": "Navidrome",
|
||||
"platform": "Athena-Plattformwissen",
|
||||
"operator": "Athena Operator",
|
||||
"web": "allgemeine Websuche (TinySearch)",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
@@ -83,6 +85,7 @@ class Filter:
|
||||
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(?: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",
|
||||
),
|
||||
)
|
||||
@@ -123,6 +126,20 @@ class Filter:
|
||||
"contents. Use the read-only shell only when that purpose-built tool "
|
||||
"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", [])
|
||||
for message in messages:
|
||||
if message.get("role") == "system" and isinstance(message.get("content"), str):
|
||||
@@ -161,7 +178,8 @@ class Filter:
|
||||
homeassistant = self._matches(
|
||||
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"\bautomations?\.ya?ml\b", r"\bconfiguration\.ya?ml\b",
|
||||
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"\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(?:[aä]nder|erstell|installier|l[oö]sch|entfern|download|umbenenn)\w*\b.{0,25}\b(?:nichts?|keine[nrms]?|keinerlei)\b",
|
||||
),
|
||||
):
|
||||
unraid_write = False
|
||||
@@ -282,8 +301,14 @@ class Filter:
|
||||
return body
|
||||
|
||||
latest_text = self._latest_user_text(body)
|
||||
selected = self._classify(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.
|
||||
# Keep it available on every normal request so an unfamiliar website
|
||||
# (MakerWorld, eBay, a vendor page tomorrow) never requires another
|
||||
@@ -298,8 +323,6 @@ class Filter:
|
||||
metadata_features["web_search"] = True
|
||||
|
||||
if not selected:
|
||||
if needs_native_web:
|
||||
await self._notify(__event_emitter__, ["native Websuche"])
|
||||
return body
|
||||
|
||||
existing = body.get("tool_ids")
|
||||
@@ -335,10 +358,12 @@ class Filter:
|
||||
for message in body.get("messages", []):
|
||||
if message.get("role") == "system" and isinstance(message.get("content"), str):
|
||||
message["content"] += (
|
||||
" Native Open WebUI web search is also enabled for this request. "
|
||||
"After collecting local or specialist-tool evidence, continue with "
|
||||
"search_web/fetch_url for current public evidence; do not stop merely "
|
||||
"because no separate web MCP appears in the MCP tool list."
|
||||
" Two broad web engines are available: native Open WebUI "
|
||||
"search_web/fetch_url and TinySearch search/scrape_urls/research. "
|
||||
"Start with one focused query. If it returns no results, an access "
|
||||
"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
|
||||
await self._notify(__event_emitter__, labels)
|
||||
|
||||
@@ -33,6 +33,12 @@ replacement = """ tool_call_iterations = 0
|
||||
tool_call_executions = 0
|
||||
max_tool_call_executions = 40
|
||||
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_signature_counts = {}
|
||||
max_tool_call_iterations = getattr(
|
||||
@@ -96,6 +102,64 @@ if source.count(needle) != 1:
|
||||
)
|
||||
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(
|
||||
request,
|
||||
new_form_data,
|
||||
|
||||
Reference in New Issue
Block a user