112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
"""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")
|