diff --git a/app/core/orchestrator.py b/app/core/orchestrator.py index 8feba50..0900eea 100644 --- a/app/core/orchestrator.py +++ b/app/core/orchestrator.py @@ -10,6 +10,21 @@ from app.metrics import timer, metrics # zwischen Geduldssätzen, bei noch laufender Recherche. FILLER_PATIENCE_INTERVAL = 10.0 +# Freundliche Mitteilung statt Stille, wenn keine Antwort zustande kommt +# (z. B. Web-Suche ohne Treffer). Senior wartet sonst vergeblich. Fallback Deutsch. +_NO_RESULT = { + "de": "Dazu konnte ich leider keine Informationen finden.", + "en": "I'm sorry, I couldn't find any information about that.", + "nl": "Daarover kon ik helaas geen informatie vinden.", + "fr": "Je suis désolé, je n'ai trouvé aucune information à ce sujet.", + "es": "Lo siento, no he encontrado información al respecto.", + "it": "Mi dispiace, non ho trovato informazioni in merito.", +} + + +def _no_result_message(language: str | None) -> str: + return _NO_RESULT.get((language or "de").lower(), _NO_RESULT["de"]) + def _stage(name: str): return timer("stage_duration_seconds", {"stage": name}) @@ -135,8 +150,8 @@ class Orchestrator: history=history, language=effective_language, ) - if not trace.semantic_response: - raise RuntimeError("LLM returned an empty response") + if not (trace.semantic_response or "").strip(): + trace.semantic_response = _no_result_message(effective_language) trace.spoken_response = await self.spoken_adapter.run( trace.semantic_response, @@ -330,9 +345,16 @@ class Orchestrator: for sentence in chunker.feed(result): await _dispatch(sentence) - trace.semantic_response = "".join(parts) + trace.semantic_response = "".join(parts).strip() if not trace.semantic_response: - raise RuntimeError("LLM returned an empty response") + # Keine Antwort (z. B. Suche ohne Treffer) -> freundliche Mitteilung + # statt Stille, damit der Nutzer nicht vergeblich wartet. + trace.semantic_response = _no_result_message(effective_language) + if on_token: + await on_token(trace.semantic_response) + if chunker: + for sentence in chunker.feed(trace.semantic_response): + await _dispatch(sentence) trace.citations = collected_citations trace.web_searches = search_count[0] diff --git a/app/pipeline/fillers.py b/app/pipeline/fillers.py index b633254..9ccbe0b 100644 --- a/app/pipeline/fillers.py +++ b/app/pipeline/fillers.py @@ -35,6 +35,21 @@ OPENING_PHRASES = [ "Give me a second, I'll find that out for you.", "I'm checking that for you now, one moment please.", "Let me look that up so I can tell you exactly.", + "Let me check the current details for you — it won't take long.", + "I'll find the up-to-date answer for you, one moment.", + "Allow me a brief moment to look into that.", + "Let me gather the latest information for you.", + "I'm on it — just give me a short moment.", + "Let me make sure I have the current facts for you.", + "One moment please, I'm fetching the latest for you.", + "Let me quickly check what's current on that.", + "I'll look into that for you right away, just a moment.", + "Bear with me a second while I find that out.", + "Let me get you the most recent information on that.", + "Just a brief moment — I'm checking the latest for you.", + "I'll dig that up for you now, one moment please.", + "Let me find the current answer so I can tell you properly.", + "Let me take a quick look and find that out for you.", ] PATIENCE_PHRASES = [ diff --git a/app/runtime_config.py b/app/runtime_config.py index 4e76e74..837cf0a 100644 --- a/app/runtime_config.py +++ b/app/runtime_config.py @@ -35,6 +35,9 @@ RUNTIME_SETTABLE: dict[str, tuple[str, str, str]] = { "memory_extraction_every_n_turns": ("Extraktion alle N Turns", "int", "z.B. 3"), "daily_request_limit": ("Tageskontingent (global)", "int", "0 = unbegrenzt"), "cost_daily_limit_usd": ("Kostenbudget/Tag (global, $)", "float", "0 = unbegrenzt — pro Nutzer überschreibbar"), + "electricity_price_eur_per_kwh": ("Strompreis (€/kWh)", "float", "Kosten lokaler LLM-Anfragen (z.B. 0.33)"), + "emergency_alert_eur": ("Notruf-Kosten (€/Alarm)", "float", "SMS+Anruf pro Alarm (z.B. 0.20)"), + "cartesia_usd_per_char": ("Cartesia-TTS ($/Zeichen)", "float", "z.B. 0.00005 (100k Credits ≈ 100.000 Zeichen)"), "emergency_cooldown_minutes": ("Notruf-Sperre (Minuten)", "int", "Erneuter Alarm für N Minuten blockiert (Standard 5, 0 = aus)"), }