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>
Sonar liefert Citation-URLs; bisher verworfen, jetzt bis in die UI gereicht.
- ToolCallingLLM: _run_tool/_inject_forced_search geben Citations zurueck;
stream() sammelt sie ueber einen on_citations-Callback.
- Orchestrator: chat_stream reicht on_citations durch (via _stream_supports)
und legt die gesammelten URLs in PipelineTrace.citations.
- schemas: PipelineTrace.citations.
- ws.py: Citations im semantic-Event.
- Frontend (app.js): renderCitations() zeigt bis zu 4 Quellen-Links
(Hostname, neuer Tab) unter der Antwort-Bubble.
Nur ueber den Streaming-Pfad (= Web-UI). Live verifiziert: "Wer ist
Bundeskanzler?" -> 15 Quellen gesammelt (Bundeskanzler.de, Wikipedia, ...).
Tests: 312 gruen; JS-Syntax geprueft. Doc §8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher wurde der Filler im "Im Gerät"-Modus (Web Speech API) nur angezeigt,
aber nicht gesprochen (Server-Audio ist dort aus). Jetzt eigener Kanal:
- Orchestrator: chat_stream bekommt on_filler; _emit_filler bevorzugt den
dedizierten Filler-Kanal (sonst Fallback on_token). Ephemer wie bisher.
- ws.py: on_filler -> {type:"filler", text}.
- Frontend (app.js): {type:"filler"} -> transient in der STATUS-Zeile (nicht
in der Antwort-Bubble) und im Geräte-Modus sofort speechSynthesis.speak()
OHNE cancel (mehrere Filler -> Queue; die Antwort cancelt spaeter und
uebernimmt). Abgesichert ueber deviceVoicesReady()/deviceVoiceReady(lang) —
keine Verschaerfung der bekannten deviceVoiceReady-Regression.
Server-TTS-Modus unveraendert (Filler zusaetzlich als "Satz null" gesprochen),
zeigt den Filler nun ebenfalls in der Status-Zeile statt in der Bubble.
Tests: 312 gruen; JS-Syntax geprueft. Doc §5.4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Schliesst die ~5-15 % Erst-Miss bei Fragen, deren veraltete Antwort
konfident-falsch waere (Amtstraeger, Wohnort lebender Personen, "lebt X noch").
- app/pipeline/search_backstop.py: should_force_search() erkennt solche
Phrasierungen per mehrsprachiger Regex (de/en/nl + etwas fr/es) mit
Gegenwarts-Schutz ("wer ist", nicht "wer war"). 22 Offline-Tests.
- ToolCallingLLM: bei Treffer wird die Suche DETERMINISTISCH erzwungen.
Wichtig: NICHT per tool_choice-Forcen (das honoriert das Modell nicht
zuverlaessig -> beobachtet: ignoriert, veraltete Antwort). Stattdessen
fuehrt der Wrapper die Suche selbst aus und speist das Ergebnis als
synthetischen Tool-Turn ein (_inject_forced_search). Metrik
search_forced_total. Filler/Geduld feuern auch hier.
- Haertung: _chat faengt non-JSON-200 ab (war ein Crash-/Hänger-Ausloeser,
betrifft auch Produktion).
Live verifiziert: "Wer ist Bundeskanzler?" sucht jetzt 3/3 -> Merz.
Tests: 312 gruen. Doc: §5.7.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ersetzt die statischen 3-Satz-Filler durch einen kreativen, weniger
roboterhaften Mechanismus für schnelles Feedback waehrend der Web-Suche.
- app/pipeline/fillers.py: EINZIGE Pflege-Stelle. Englische Master-Listen
(12 OPENING + 16 PATIENCE). Zur Laufzeit einmal je Sprache uebersetzt und
gecacht (durchgaengig hoefliche Anrede "Sie"/"vous"/"usted"...), Fallback
Englisch. Zufaellige Auswahl statt fester Reihenfolge.
- Geduld-Schleife: bei laengerer Recherche schiebt ein Hintergrund-Task alle
FILLER_PATIENCE_INTERVAL Sekunden einen zufaelligen PATIENCE-Satz nach;
Abbruch beim ersten Antwort-Delta und im finally (keine verwaisten Tasks).
- Ephemer wie bisher: laeuft ueber on_token/_dispatch, nie in
semantic_response/History.
- Standardsprache wird beim Start vorgewaermt (warmup.py) -> erster Such-Turn
ohne Uebersetzungs-Latenz. dependencies: get_fillers()-Singleton.
Doc: Docs/weg2-tool-calling.md §5.4. Tests: 290 gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frische-/Web-Such-Funktion: Das zentrale Modell entscheidet selbst via
web_search-Tool, ob es tagesaktuelle Fakten braucht, holt sie über
perplexity/sonar und formuliert die Antwort in Persona (Augment).
- SonarTool (app/tools/web_search.py): Fakten via perplexity/sonar,
Citations als Metadaten, honest-punt-Sentinel bei Fehler/Timeout.
- ToolCallingLLM (app/providers/llm/tool_calling.py): agentischer Loop als
LLMProvider; complete() + gestreamtes stream() mit SSE-Tool-Assembler;
Persona- + Trigger- + Vorrang-Prompt (Tool-Ergebnis schlaegt Gedaechtnis).
- Verdrahtung: Registry-Eintrag openrouter-tools; web_search_enabled
(global an, pro Nutzer/Profil abschaltbar) via Route-Layering;
build_orchestrator waehlt tool-faehig vs. plain, Fallback-Kette erhalten.
- Filler: ephemerer Beruhigungssatz beim Tool-Start (sofort angezeigt UND
gesprochen als Satz null), nie in semantic_response/History; on_tool_start
defensiv durch die stream()-Kette gefaedelt (kein Bruch bestehender Provider).
- Modellwechsel: Standard auf mistralai/mistral-small-3.2-24b-instruct
(tool-faehig; im Eval einziger Recall-Gate-Passer). 2501 ist tool-unfaehig.
- Eval-Harness (eval/tool_calling/): Datensatz + Runner zur Modellauswahl.
Doc: Docs/weg2-tool-calling.md. Tests: 290 gruen.
Offen (Schritt 5): Koreferenz-Vorstufe (nl-Pronomen) + Metrik-Zaehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eigentliche Ursache für 'immer weibliche Stimme, egal welcher Provider':
UserPrefs kannte kein Feld voice_gender, daher verwarf pydantic in
PUT /api/me/prefs die Geschlechtswahl still. resolve_route sah immer 'any'
-> Piper/Cartesia lieferten die weibliche Stimme (kerstin bzw. Alina).
- voice_gender zum Schema hinzugefügt; Frontend sendet es bereits, resolve_route
liest es bereits (ROUTE_KEYS), voice_for_route mappt m/f bereits.
- Regressions-Test: voice_gender persistiert über /api/me/prefs.
- Docs/stimmen-inventar.md: zentrale Übersicht aller Stimmen je Provider/
Sprache/Geschlecht (Name, Datei, UUID) inkl. Fundstellen im Code.
Live verifiziert (Profil jamulix, de): m -> de_DE-thorsten-high,
f -> de_DE-kerstin-low.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
app.js: default_stt/llm/tts_provider und default_language von 'select'
auf 'combo' umgestellt, damit neben den vordefinierten Optionen jederzeit
ein beliebiger eigener Wert eingegeben werden kann (Zukunftssicherheit).
combo-Renderer unterstützt jetzt auch meta.labels (Sprachkürzel → Klarname).
tests/test_admin_settings.py: 28 Tests für GET/PUT/DELETE /api/admin/config —
Auth, bekannte Werte, Custom-Values, Persistenz, Runtime-Wirkung.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README + BEDIENUNGSANLEITUNG § 4.7: vollständige Kommandos für den Wechsel
zwischen llama.cpp und Ollama (inkl. Stopp-Befehle für den jeweils anderen
Backend-Prozess). TOC und Sachregister aktualisiert.
Docs/Neues_Konzept_mit_TTS_auf_Mobil_Geraet.md: neue Recherche-/Konzept-Datei
zu On-Device-TTS auf iOS/Android hinzugefügt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Loest #7. Der chatterbox-Stub wird durch eine echte Anbindung an den lokalen
Chatterbox-HTTP-Dienst ersetzt (POST /speak -> /status pollen -> GET /audio, WAV).
no_playback=true -> der Dienst spielt nicht lokal ab, liefert nur Bytes. WAV->PCM
(24 kHz, Resampling bei Bedarf). Waehlbar via tts_provider=chatterbox; piper bleibt
der schnelle Default (chatterbox ist ~echtzeit-langsam, dafuer klonbare Stimme).
- config: CHATTERBOX_BASE_URL/_VOICE/_LANG/_SPEED/_TIMEOUT; Registry verdrahtet
- Tests: gemockter httpx (Synthese, WAV/Resample, Job-Fehler, Stimmenwahl)
- Doku: README, Architektur (#7), deploy/README (GPU-Pinning per UUID, no_playback)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
README/BEDIENUNGSANLEITUNG/Architektur: piper laeuft in-process (gecachtes
Stimmmodell, kein Subprozess pro Satz), In-Process-Resampling, lokale Modelle
werden beim Start vorgeladen. .[local] installiert jetzt faster-whisper UND
piper-tts. Latenz-Hinweis erster Ton 5,8 s -> ~1,6 s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- app/core/memory_extractor.py: LLM destilliert nach je N Turns dauerhafte
Fakten/Vorlieben aus dem Verlauf, dedupliziert gegen vorhandene Erinnerungen
und legt sie ab - best-effort, nicht-blockierend (Hintergrund-Task), eigener
Extraktions-Prompt (JSON, Reasoning aus), Cap-Begrenzung
- Trigger in /api/chat und /ws/voice nach dem Persistieren des Turns
- Konfig: MEMORY_EXTRACTION_ENABLED/_EVERY_N_TURNS/_MAX/_PROVIDER
- Tests: Extraktion, Dedup, kaputtes JSON, Cap, leeres Gespraech, Scheduling
- Doku: README + Architektur-Roadmap (Punkt 3 erledigt)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PiperTTSProvider ruft das piper-Binary (--output-raw) async auf, liest die native
Sample-Rate aus der .onnx.json und resampelt per ffmpeg auf 24000 Hz (Gateway-Norm).
Nicht passende Stimmen (z. B. Cloud-Stimme 'Zephyr' aus der Route) fallen auf die
konfigurierte Default-Stimme zurueck. Damit ist eine voll-lokale Konstellation
(faster-whisper + Ollama + piper) moeglich -> keine API-Kosten, max. Datenschutz.
- config: PIPER_BIN/PIPER_VOICES_DIR/PIPER_VOICE/TTS_SAMPLE_RATE (+ .env.example)
- dependencies: piper-Factory mit Settings verdrahtet
- tests: tests/test_piper_tts.py (offline, Fake-Binary; Resample-Test skippt ohne ffmpeg);
e2e/auth-Tests nutzen jetzt einen Stub-TTS statt 'piper' als Pseudo-Stub
- docs: README, BEDIENUNGSANLEITUNG (voll-lokal-Beispiel), Architektur-Roadmap
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live-Test gegen OpenRouter ergab: /audio/transcriptions erwartet JSON mit
input_audio:{data(base64),format} - NICHT multipart/form-data. Die fruehere
Umstellung auf multipart (OpenAI-Standard) war fuer OpenRouter falsch.
- STT-Provider zurueck auf JSON/base64 (mit robuster Fehlerbehandlung)
- verifiziert per TTS->WAV->STT Round-Trip: Text korrekt zurueck transkribiert
- LLM, TTS und STT funktionieren jetzt alle live gegen OpenRouter
- Doku-Referenz aktualisiert (multipart -> JSON/base64)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Store: messages-Tabelle + append_message/get_recent_messages (mit Mandanten-Schutz)
- LLMProvider.complete um history erweitert (openrouter + local bauen system+history+user)
- Orchestrator.chat_text reicht den Verlauf ans LLM weiter
- chat.py: bei session_id letzte HISTORY_MAX_MESSAGES laden, danach User-/Assistant-Turn speichern
- ohne session_id weiterhin zustandslos; ?debug zeigt history_len
- Config HISTORY_MAX_MESSAGES (Default 10)
- Tests: 32 gruen (3 neue Gedaechtnis-Tests)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>