feat: Konversationsgedaechtnis - Kurzzeit-Gespraechsverlauf pro Session
- 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>
This commit is contained in:
parent
e0e69fdf15
commit
16a964032e
12 changed files with 225 additions and 40 deletions
65
tests/test_memory.py
Normal file
65
tests/test_memory.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
import app.dependencies as deps
|
||||
from app.main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def _install_stubs(monkeypatch, captured):
|
||||
class MemLLM:
|
||||
async def complete(self, text, history=None, session_id=None):
|
||||
captured["history"] = list(history or [])
|
||||
return f"Antwort auf: {text}"
|
||||
|
||||
class StubTTS:
|
||||
async def synthesize(self, text, voice=None, audio_format="pcm"):
|
||||
return b"AUDIO"
|
||||
|
||||
monkeypatch.setitem(deps.LLM_REGISTRY, "mem", lambda s: MemLLM())
|
||||
monkeypatch.setitem(deps.TTS_REGISTRY, "stub", lambda s: StubTTS())
|
||||
return {"llm_provider": "mem", "tts_provider": "stub", "output_endpoint": "loopback"}
|
||||
|
||||
|
||||
def test_history_accumulates_and_flows_to_llm(monkeypatch):
|
||||
captured = {}
|
||||
base = _install_stubs(monkeypatch, captured)
|
||||
|
||||
# Turn 1: noch kein Verlauf.
|
||||
r1 = client.post("/api/chat?debug=true&session_id=conv1", json={"text": "Hallo", **base})
|
||||
assert r1.status_code == 200
|
||||
assert r1.json()["history_len"] == 0
|
||||
assert captured["history"] == []
|
||||
|
||||
# Turn 2: Verlauf enthaelt User+Assistant aus Turn 1.
|
||||
r2 = client.post("/api/chat?debug=true&session_id=conv1", json={"text": "Und weiter?", **base})
|
||||
assert r2.status_code == 200
|
||||
assert r2.json()["history_len"] == 2
|
||||
assert captured["history"] == [
|
||||
{"role": "user", "content": "Hallo"},
|
||||
{"role": "assistant", "content": "Antwort auf: Hallo"},
|
||||
]
|
||||
|
||||
|
||||
def test_no_session_id_is_stateless(monkeypatch):
|
||||
captured = {}
|
||||
base = _install_stubs(monkeypatch, captured)
|
||||
|
||||
client.post("/api/chat?debug=true", json={"text": "A", **base})
|
||||
client.post("/api/chat?debug=true", json={"text": "B", **base})
|
||||
assert captured["history"] == [] # ohne session_id kein Gedaechtnis
|
||||
|
||||
|
||||
def test_history_limited_by_setting(monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
captured = {}
|
||||
base = _install_stubs(monkeypatch, captured)
|
||||
monkeypatch.setattr(settings, "history_max_messages", 2)
|
||||
|
||||
for text in ("eins", "zwei", "drei"):
|
||||
client.post("/api/chat?debug=true&session_id=limit", json={"text": text, **base})
|
||||
|
||||
# Vor dem 3. Turn liegen 4 Nachrichten vor; geladen werden nur die letzten 2.
|
||||
assert len(captured["history"]) == 2
|
||||
assert captured["history"][-1] == {"role": "assistant", "content": "Antwort auf: zwei"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue