update
This commit is contained in:
111
backend/tests/test_chat.py
Normal file
111
backend/tests/test_chat.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""Was das Planungsgespräch darf — und was es nicht sieht.
|
||||
|
||||
Geprüft wird nicht die Antwort des Modells, sondern womit es losgeschickt wird:
|
||||
Werkzeugsatz, Arbeitsverzeichnis und der Absatz im Systemprompt gehören zusammen.
|
||||
Fällt eines auseinander, behauptet das Modell nachgesehen zu haben, ohne zu können.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import agents
|
||||
import chat
|
||||
import projekte
|
||||
import verzeichnis
|
||||
|
||||
KARTE = ("<!-- titel -->\nUpload\n<!-- art -->\nkern\n"
|
||||
"<!-- ziel -->\nDateien landen im Speicher.\n"
|
||||
"<!-- funktion -->\nDatei wählen\n<!-- testen -->\n- Dialog öffnen\n")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def projekt(datenordner):
|
||||
projekte.anlegen("P")
|
||||
(datenordner / "P" / "upload.md").write_text(KARTE)
|
||||
return "P"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mitschrift(monkeypatch):
|
||||
"""Fängt den Aufruf ab und gibt eine gültige Antwort zurück."""
|
||||
gesehen = {}
|
||||
|
||||
async def antworte(system, inhalt, kommando=None, ordner=None):
|
||||
gesehen.update(system=system, inhalt=inhalt, kommando=kommando, ordner=ordner)
|
||||
return json.dumps({"antwort": "Verstanden.", "karten": []})
|
||||
|
||||
monkeypatch.setattr(agents, "frage_lokal", antworte)
|
||||
return gesehen
|
||||
|
||||
|
||||
async def test_ohne_verzeichnis_kein_lesen(projekt, mitschrift):
|
||||
await chat.frage(projekt, "Was kann das Projekt?")
|
||||
assert mitschrift["kommando"] == agents.PLANUNG_OHNE_CODE
|
||||
assert mitschrift["ordner"] is None
|
||||
assert chat.CODE_OHNE in mitschrift["system"]
|
||||
|
||||
|
||||
async def test_mit_verzeichnis_darf_lesen(projekt, mitschrift, tmp_path):
|
||||
code = tmp_path / "quelltext"
|
||||
code.mkdir()
|
||||
verzeichnis.setze(projekt, str(code))
|
||||
await chat.frage(projekt, "Gibt es den Upload schon?")
|
||||
assert mitschrift["kommando"] == agents.PLANUNG_KOMMANDO
|
||||
assert mitschrift["ordner"] == str(code.resolve())
|
||||
assert chat.CODE_MIT in mitschrift["system"]
|
||||
|
||||
|
||||
async def test_toter_pfad_zaehlt_wie_keiner(projekt, mitschrift, tmp_path):
|
||||
"""Sonst startet der Prozess nicht, und die Meldung zeigt auf die CLI."""
|
||||
code = tmp_path / "weg"
|
||||
code.mkdir()
|
||||
verzeichnis.setze(projekt, str(code))
|
||||
code.rmdir()
|
||||
await chat.frage(projekt, "Und jetzt?")
|
||||
assert mitschrift["kommando"] == agents.PLANUNG_OHNE_CODE
|
||||
assert mitschrift["ordner"] is None
|
||||
|
||||
|
||||
async def test_planung_sucht_nicht_im_web(projekt, mitschrift, tmp_path):
|
||||
"""Fremdes Wissen kommt aus der Recherche, nicht nebenbei in einen Entwurf."""
|
||||
code = tmp_path / "quelltext"
|
||||
code.mkdir()
|
||||
verzeichnis.setze(projekt, str(code))
|
||||
await chat.frage(projekt, "Wie macht man das üblicherweise?")
|
||||
for satz in (agents.PLANUNG_KOMMANDO, agents.PLANUNG_OHNE_CODE):
|
||||
assert "WebSearch" not in _erlaubt(satz)
|
||||
assert "WebFetch" not in _erlaubt(satz)
|
||||
|
||||
|
||||
def test_recherche_sieht_keinen_quelltext():
|
||||
"""Umgekehrt: aus dem Projekt darf nichts in eine Suchanfrage geraten."""
|
||||
erlaubt = _erlaubt(agents.RECHERCHE_KOMMANDO)
|
||||
assert "WebSearch" in erlaubt and "WebFetch" in erlaubt
|
||||
for werkzeug in ("Read", "Glob", "Grep"):
|
||||
assert werkzeug not in erlaubt
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kommando", [
|
||||
agents.PLANUNG_KOMMANDO, agents.PLANUNG_OHNE_CODE, agents.RECHERCHE_KOMMANDO])
|
||||
def test_kein_satz_kann_etwas_anstossen(kommando):
|
||||
"""Fremder Text im Kontext darf nichts auslösen, egal woher er kommt."""
|
||||
gesperrt = _gesperrt(kommando)
|
||||
for werkzeug in ("Write", "Edit", "Bash", "CronCreate", "PushNotification",
|
||||
"SendMessage", "Workflow", "Task"):
|
||||
assert werkzeug in gesperrt
|
||||
assert "--safe-mode" in kommando
|
||||
|
||||
|
||||
def _abschnitt(kommando: tuple, flagge: str) -> list:
|
||||
rest = list(kommando[kommando.index(flagge) + 1:])
|
||||
ende = next((i for i, w in enumerate(rest) if w.startswith("--")), len(rest))
|
||||
return rest[:ende]
|
||||
|
||||
|
||||
def _erlaubt(kommando: tuple) -> list:
|
||||
return _abschnitt(kommando, "--allowed-tools")
|
||||
|
||||
|
||||
def _gesperrt(kommando: tuple) -> list:
|
||||
return _abschnitt(kommando, "--disallowed-tools")
|
||||
Reference in New Issue
Block a user