fix(llm): LLM antwortet jetzt in der konfigurierten Systemsprache
Bisher wurde `language` zwar an STT/TTS übergeben, aber nie an den LLM.
Lokales Modell antwortete deshalb immer auf Englisch/Deutsch, egal ob
Französisch, Spanisch o.a. eingestellt war.
Lösung: `language`-Parameter durch die gesamte LLM-Schicht gezogen:
- base.py: `lang_instruction()` helper + Signatur erweitert
- local_openai_compatible.py: Sprachanweisung ("Respond in Français.")
wird als letzter System-Part in den Message-Stack eingefügt
- openrouter.py: Explizite Sprachanweisung ergänzt den bestehenden
"Answer in the same language as the user"-Prompt
- fallback.py: FallbackLLMProvider leitet `language` durch
- orchestrator.py: alle 3 LLM-Aufrufstellen übergeben `language`
- Tests: alle Stub-LLMs um `language=None` ergänzt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c3f6206d0c
commit
81d4cf6fd8
14 changed files with 89 additions and 44 deletions
|
|
@ -12,7 +12,7 @@ class StubLLM:
|
|||
self.raw = raw
|
||||
self.calls = 0
|
||||
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
self.calls += 1
|
||||
return self.raw
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def test_session_route_applies(monkeypatch):
|
|||
|
||||
def test_chat_per_request_override_and_loopback(monkeypatch):
|
||||
class StubLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "Mir geht es gut, danke."
|
||||
|
||||
class StubTTS:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ client = TestClient(app)
|
|||
|
||||
def _install_stubs(monkeypatch, captured):
|
||||
class MemLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
captured["history"] = list(history or [])
|
||||
return f"Antwort auf: {text}"
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class StubLLM:
|
|||
self.raw = raw
|
||||
self.calls = 0
|
||||
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
self.calls += 1
|
||||
return self.raw
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ client = TestClient(app)
|
|||
|
||||
def _stub_chat(monkeypatch, answer="ok"):
|
||||
class StubLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return answer
|
||||
|
||||
class StubTTS:
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ def test_energy_vad_ignores_silence_without_speech():
|
|||
|
||||
def _install_slow_stream(monkeypatch):
|
||||
class SlowLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "fertig"
|
||||
|
||||
async def stream(self, text, history=None, session_id=None):
|
||||
async def stream(self, text, history=None, session_id=None, language=None):
|
||||
for i in range(200):
|
||||
await asyncio.sleep(0.005)
|
||||
yield f"t{i} "
|
||||
|
|
@ -89,7 +89,7 @@ def _install_voice_stubs(monkeypatch):
|
|||
return "ok"
|
||||
|
||||
class LLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "antwort"
|
||||
|
||||
class TTS:
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ def _run(coro):
|
|||
|
||||
def test_llm_fallback_uses_second_on_error():
|
||||
class BadLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
raise RuntimeError("down")
|
||||
|
||||
class GoodLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "ok"
|
||||
|
||||
chain = FallbackLLMProvider("llm", [("bad", BadLLM()), ("good", GoodLLM())])
|
||||
|
|
@ -37,7 +37,7 @@ def test_llm_fallback_uses_second_on_error():
|
|||
|
||||
def test_llm_fallback_all_fail_raises():
|
||||
class BadLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
raise RuntimeError("x")
|
||||
|
||||
chain = FallbackLLMProvider("llm", [("a", BadLLM()), ("b", BadLLM())])
|
||||
|
|
@ -47,18 +47,18 @@ def test_llm_fallback_all_fail_raises():
|
|||
|
||||
def test_llm_stream_fallback_before_first_token():
|
||||
class BadStream:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "x"
|
||||
|
||||
async def stream(self, text, history=None, session_id=None):
|
||||
async def stream(self, text, history=None, session_id=None, language=None):
|
||||
raise RuntimeError("boom")
|
||||
yield # macht die Funktion zum Generator
|
||||
|
||||
class GoodStream:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "ok"
|
||||
|
||||
async def stream(self, text, history=None, session_id=None):
|
||||
async def stream(self, text, history=None, session_id=None, language=None):
|
||||
yield "he"
|
||||
yield "llo"
|
||||
|
||||
|
|
@ -74,11 +74,11 @@ def test_llm_stream_fallback_before_first_token():
|
|||
|
||||
def test_config_llm_fallback_applied(monkeypatch):
|
||||
class BadLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
raise RuntimeError("primary down")
|
||||
|
||||
class GoodLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "rescued"
|
||||
|
||||
class StubTTS:
|
||||
|
|
@ -102,7 +102,7 @@ def test_config_llm_fallback_applied(monkeypatch):
|
|||
|
||||
def test_metrics_endpoint_records_requests_and_stages(monkeypatch):
|
||||
class StubLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "hi"
|
||||
|
||||
class StubTTS:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ def test_sse_delta_parsing():
|
|||
|
||||
def test_base_stream_default_yields_full_completion():
|
||||
class P(LLMProvider):
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "ganze Antwort"
|
||||
|
||||
async def run():
|
||||
|
|
@ -51,10 +51,10 @@ def test_base_stream_default_yields_full_completion():
|
|||
|
||||
def _install_streaming(monkeypatch, tokens):
|
||||
class StreamLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "".join(tokens)
|
||||
|
||||
async def stream(self, text, history=None, session_id=None):
|
||||
async def stream(self, text, history=None, session_id=None, language=None):
|
||||
for tok in tokens:
|
||||
yield tok
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ def test_ws_without_stream_flag_has_no_tokens(monkeypatch):
|
|||
|
||||
def test_ws_stream_fallback_for_nonstreaming_llm(monkeypatch):
|
||||
class OnlyComplete:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "komplett"
|
||||
|
||||
class StubTTS:
|
||||
|
|
@ -118,10 +118,10 @@ def test_ws_audio_stream_sends_chunks_per_sentence(monkeypatch):
|
|||
tts_calls = []
|
||||
|
||||
class StreamLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return "Satz eins. Satz zwei."
|
||||
|
||||
async def stream(self, text, history=None, session_id=None):
|
||||
async def stream(self, text, history=None, session_id=None, language=None):
|
||||
for tok in ["Satz ", "eins. ", "Satz ", "zwei."]:
|
||||
yield tok
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ client = TestClient(app)
|
|||
|
||||
def _install_stubs(monkeypatch, captured):
|
||||
class MemLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
captured["history"] = list(history or [])
|
||||
return f"Echo: {text}"
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ def _install_voice_stubs(monkeypatch):
|
|||
return f"erkannt({len(audio_bytes)})"
|
||||
|
||||
class StubLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
async def complete(self, text, history=None, session_id=None, language=None):
|
||||
return f"Antwort zu {text}"
|
||||
|
||||
class StubTTS:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue