From 1ea7ab5ab140151688a1889b2e4bcd7b4cba2514 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 30 May 2026 01:17:48 +0200 Subject: [PATCH] =?UTF-8?q?fix(brain):=20run=5F=20Tool-Namen=20safe?= =?UTF-8?q?=20escapen=20=E2=80=94=20Bindestriche=20kippten=20Tools-Liste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beobachtung beim zweiten Live-Test (01:13:41): ARIA versuchte echten Tool-Call `run_spotify` — bekam aber Error: 'No such tool available'. Ursache: _skill_to_tool baute Tool-Namen via `run_{s['name']}`. Bei Skills wie 'yt-dlp-download' wurde daraus 'run_yt-dlp-download' mit Bindestrich. Anthropic-Tool-Name-Schema ist eigentlich [a-zA-Z0-9_-], ABER der claude-max-api-proxy konvertiert intern auf OpenAI-Format und faellt bei Bindestrichen um — wenn EIN Tool ungueltig ist, kippt die GANZE Tool-Liste, ARIA sieht nichts von 'run_*' inklusive 'run_spotify' obwohl der ja Bindestrich-frei war. Fix: - _skill_to_tool: name = "run_" + re.sub(r"[^a-zA-Z0-9_]", "_", s["name"]) → run_yt_dlp_download statt run_yt-dlp-download. - Dispatcher: bei tool_name='run_X' wird zuerst X als skill_name probiert, bei Miss wird ueber die Liste der existierenden Skills gemappt — der Skill mit safe_name(name)==X wird dann genommen. - Bypass-Lesson + Bypass-Section: gleiche safe-Logik fuer den empfohlenen run_-String im Memory/Prompt. Co-Authored-By: Claude Opus 4.7 (1M context) --- aria-brain/agent.py | 25 ++++++++++++++++++++++--- aria-brain/api_heuristic.py | 3 ++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/aria-brain/agent.py b/aria-brain/agent.py index 0038e90..2474131 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -787,10 +787,18 @@ def _skill_to_tool(s: dict) -> dict: } if a.get("required"): required.append(name) + # Tool-Namen duerfen in der Anthropic/Claude tool_use-API nur + # [a-zA-Z0-9_-]{1,64} sein, aber der claude-max-api-proxy (OpenAI- + # Format-Adapter) ist restriktiver und faellt bei Bindestrichen auf + # die Nase — die GANZE Tool-Liste wird dann verworfen und ARIA + # bekommt "No such tool available". Skill-Namen wie 'yt-dlp-download' + # oder 'pdf-umfrage-generator' muessen daher zu run_yt_dlp_download + # bzw. run_pdf_umfrage_generator gemappt werden. + safe_name = "run_" + re.sub(r"[^a-zA-Z0-9_]", "_", s["name"]) return { "type": "function", "function": { - "name": f"run_{s['name']}", + "name": safe_name, "description": s.get("description", "(ohne Beschreibung)"), "parameters": { "type": "object", @@ -844,7 +852,7 @@ class Agent: count = ev["count"] migration_key = f"auto/skill-bypass/{skill_name}" title = f"Skill '{skill_name}' nutzen, nicht curl" - run_tool = f"run_{skill_name.replace('-', '_')}" + run_tool = "run_" + re.sub(r"[^a-zA-Z0-9_]", "_", skill_name) content = ( f"WICHTIG fuer Performance + Stefans Wartezeit: " f"Skill '{skill_name}' existiert und deckt {host} ab. " @@ -1262,7 +1270,18 @@ class Agent: f"Sicherheits-Snapshot des vorherigen Stands: {res.get('safety_snapshot')}" ) if name.startswith("run_"): - skill_name = name[len("run_"):] + # Tool-Namen sind 'safe' (nur _), Skill-Namen koennen aber + # Bindestriche enthalten (z.B. yt-dlp-download). Wir suchen + # zuerst exakt, dann ueber Underscore-zu-Bindestrich-Mapping. + tool_suffix = name[len("run_"):] + skill_name = tool_suffix + if skills_mod.read_manifest(skill_name) is None: + # ggf. Bindestriche zurueckmappen + for cand in skills_mod.list_skills(active_only=False): + cand_name = cand.get("name") or "" + if re.sub(r"[^a-zA-Z0-9_]", "_", cand_name) == tool_suffix: + skill_name = cand_name + break res = skills_mod.run_skill(skill_name, args=arguments) snippet = (res.get("stdout") or "")[:2000] or "(kein stdout)" err = (res.get("stderr") or "")[:500] diff --git a/aria-brain/api_heuristic.py b/aria-brain/api_heuristic.py index 3fa6492..dd23a95 100644 --- a/aria-brain/api_heuristic.py +++ b/aria-brain/api_heuristic.py @@ -164,8 +164,9 @@ def build_bypass_section(bypass_events: list[dict]) -> str: sname = ev["skill_name"] host = ev["host"] count = ev["count"] + safe = re.sub(r"[^a-zA-Z0-9_]", "_", sname) lines.append(f"- gegen **{host}** ({count}x kuerzlich) → nutze " - f"`run_{sname.replace('-', '_')}(...)` statt curl. " + f"`run_{safe}(...)` statt curl. " f"Der Skill ist da. Nutze ihn.") lines.append("") return "\n".join(lines)