feat(llm): Härtung gegen tool-unfähiges Modell (no-tool-endpoints-404)

Waehlt jemand (Admin/Preset) ein Modell ohne Tool-faehigen Endpoint, lieferte
OpenRouter ein 404 "No endpoints found that support tool use" -> bisher 4x Retry
mit Backoff und danach stille, konfident-veraltete Fallback-Antwort.

- ToolCallingLLM erkennt das strukturelle 404 (_looks_tool_unsupported), merkt
  es fuer den Turn (_tools_unsupported), deaktiviert Tools und antwortet SOFORT
  plain (kein Haenger). Log-Warnung + Metrik tool_unsupported_total.
- Backstop-Inject jetzt als SYSTEM-Kontext statt synthetischem tool_call: das
  funktioniert mit tool-faehigen UND tool-unfaehigen Modellen (letztere lehnen
  tool-Messages mit 405 ab). Folge: heikle Kategorien bleiben auch auf einem
  tool-unfaehigen Modell korrekt; nur nicht-heikle Volatil-Fragen fallen plain.

Live verifiziert gegen mistral-2501 (tool-unfaehig): 3.7s statt Haenger,
Backstop liefert weiter "Merz"; gegen mistral-3.2 kein Regress (1 Suche).
Tests: 312 gruen. Doc §5.7/§5.8/§8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-30 00:21:38 +02:00
commit 48a79da12f
2 changed files with 70 additions and 17 deletions

View file

@ -140,9 +140,21 @@ getestet). Bei Treffer erzwingt `ToolCallingLLM` die Suche **deterministisch**.
**Wichtige Designentscheidung:** *Nicht* per `tool_choice`-Forcen — das honoriert
das Modell nicht zuverlässig (beobachtet: forced choice ignoriert → veraltete
Antwort). Stattdessen führt der Wrapper die Suche **selbst direkt** aus und speist
das Ergebnis als synthetischen Tool-Turn ein (`_inject_forced_search`); das Modell
formuliert daraus. Metrik: `search_forced_total`. Recall-optimiert (lieber eine
überflüssige Suche als eine konfident falsche Antwort).
das Ergebnis als **System-Kontext** ein (`_inject_forced_search`); das Modell
formuliert daraus. System-Kontext (statt synthetischem tool_call) funktioniert mit
tool-fähigen **und** tool-unfähigen Modellen (letztere lehnen tool-Messages mit 405
ab). Metrik: `search_forced_total`. Recall-optimiert (lieber eine überflüssige
Suche als eine konfident falsche Antwort).
### 5.8 Härtung: tool-unfähiges Modell
Wählt jemand (Admin/Preset) ein Modell ohne Tool-fähigen Endpoint, liefert
OpenRouter ein 404 „No endpoints found that support tool use". `ToolCallingLLM`
erkennt das (`_looks_tool_unsupported`), **merkt es für den Turn** (`_tools_unsupported`),
deaktiviert Tools und antwortet **sofort plain** (kein Retry-Hänger, keine stille
Stale-Antwort durch die Fallback-Kette). Log-Warnung + Metrik `tool_unsupported_total`.
Wichtig: Der **Backstop bleibt wirksam** (er speist Fakten als System-Kontext ein,
nicht als tool_call) → heikle Kategorien bleiben auch auf einem tool-unfähigen Modell
korrekt; nur nicht-heikle Volatil-Fragen (Wetter/Preise) fallen auf plain zurück.
## 6. Die zwei harten Stellen (bewusst benannt)
@ -174,8 +186,8 @@ wird zur Tool-Rangordnung).
- **Erledigt (nach v1):** Backstop für heikle Kategorien (§5.7), Filler für
**Gerät-TTS** (§5.4), **Citations in die UI-Bubble** (`PipelineTrace.citations`
`on_citations` → WS `semantic`-Event → Quellen-Links unter der Antwort).
- **Fast-follow (offen):** „no-tool-endpoints"-404-Härtung (tool-unfähiges Modell
bewusst plain statt still stale), ggf. deterministischer Logistik-Nudge.
- **Erledigt:** „no-tool-endpoints"-404-Härtung (§5.8).
- **Fast-follow (offen):** ggf. deterministischer Logistik-Nudge (`schedule_hours`).
- **Später (Multitool):** weitere Tools in dieselbe `ToolCallingLLM`-Schleife
(Kalender, Erinnerungen, Medizin-Safety) mit Tool-Rangordnung.