update
This commit is contained in:
@@ -94,7 +94,7 @@ def test_tabelle_struktur_check():
|
||||
def test_referenziert_quelle_ignoriert_code():
|
||||
# „aufgabe 3" in Code/Kommentar darf NICHT als Quellen-Referenz matchen
|
||||
inhalt = {"form": "code", "text": "", "code": "# aufgabe 3: sum\nprint(sum([1,2]))",
|
||||
"sprache": "python", "tabelle": "", "mermaid": ""}
|
||||
"sprache": "python", "tabelle": ""}
|
||||
assert not artefakte._referenziert_quelle(inhalt)
|
||||
# in der Prosa (text) matcht es weiterhin
|
||||
assert artefakte._referenziert_quelle({"text": "siehe aufgabe 3", "frage": "", "antwort": ""})
|
||||
@@ -151,23 +151,6 @@ def test_formen_gate_verwirft_kaputten_code():
|
||||
assert db.one("SELECT status FROM artefakte WHERE id=?", (schlecht,))["status"] == "verworfen"
|
||||
|
||||
|
||||
def test_formen_gate_diagramm_beispiel():
|
||||
topic = topic_anlegen("diagbsp")
|
||||
a = db.insert("atome", topic=topic, titel="X", typ="begriff", definition="d",
|
||||
status="neu", braucht=db.j([]))
|
||||
spec = {"knoten": [{"id": "q0", "label": "Start"}, {"id": "q1", "label": "Ende"}],
|
||||
"kanten": [{"von": "q0", "zu": "q1", "label": "a"}]}
|
||||
d = db.insert("artefakte", atom_id=a, typ="beispiel", status="kandidat",
|
||||
inhalt=db.j({"form": "diagramm", "spec": spec}))
|
||||
leer = db.insert("artefakte", atom_id=a, typ="beispiel", status="kandidat",
|
||||
inhalt=db.j({"form": "diagramm", "spec": {"knoten": []}}))
|
||||
artefakte._formen_gate(topic)
|
||||
d_row = db.one("SELECT status, inhalt FROM artefakte WHERE id=?", (d,))
|
||||
assert d_row["status"] == "kandidat"
|
||||
assert db.uj(d_row["inhalt"])["mermaid"].startswith("flowchart") # kompiliert
|
||||
assert db.one("SELECT status FROM artefakte WHERE id=?", (leer,))["status"] == "verworfen"
|
||||
|
||||
|
||||
async def test_guide_messen_toter_beispiel_marker():
|
||||
import guide
|
||||
topic = topic_anlegen("bspqa")
|
||||
|
||||
@@ -313,7 +313,7 @@ async def test_reparieren_bewegt_false_bei_identischem_text(monkeypatch):
|
||||
b_id = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu")
|
||||
a = db.insert("atome", topic=topic, titel="A", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||
lang = f"<!-- atom: {a} | A -->\n" + "Wort " * 60
|
||||
lang = f"<!-- atom: {a} | A -->\n" + ("Wort " * 60).strip() # ohne Trailing-Space
|
||||
db.insert("sections", baustein_id=b_id, stage="fix", text_lang=lang,
|
||||
text_kompakt="- p", befunde=db.j(["Behebe (laenge): kürzen"]))
|
||||
monkeypatch.setitem(fake_agents._HANDLER, "Guide-Fix",
|
||||
@@ -366,6 +366,20 @@ def test_split_writer_delimited_und_fallback():
|
||||
assert guide._split_writer("nur text")[1] == "nur text"
|
||||
|
||||
|
||||
def test_writer_saeubern_terminator():
|
||||
import guide
|
||||
# geleakter `=====`-Terminator (Setext-H1 unter Bullet) + </s> raus
|
||||
roh = "- $M$ gilt\n\n$$L \\in NP$$\n- Für jedes $L$ gilt\n=====\n</s>"
|
||||
sauber = guide._writer_saeubern(roh)
|
||||
assert "=====" not in sauber and "</s>" not in sauber
|
||||
assert sauber.endswith("- Für jedes $L$ gilt")
|
||||
# _split_writer säubert den kompakt-Block (letzter Block fängt sonst das =====)
|
||||
k, l = guide._split_writer("===LANG===\nText\n===KOMPAKT===\n- a\n- b\n=====\n</s>")
|
||||
assert k == "- a\n- b" and l == "Text"
|
||||
# Tabellen-Trenner (---) bleibt unangetastet
|
||||
assert "---" in guide._writer_saeubern("| A | B |\n|---|---|\n| 1 | 2 |")
|
||||
|
||||
|
||||
async def test_fix_cap_friert_section_ein(monkeypatch):
|
||||
"""Nach FIX_MAX_VERSUCHE erfolglosen Fixes (Text unverändert) wird die Section
|
||||
für Stil-Befunde eingefroren → reparieren meldet bewegt=False (Stillstand)."""
|
||||
@@ -388,3 +402,25 @@ async def test_fix_cap_friert_section_ein(monkeypatch):
|
||||
bewegt = await guide.reparieren(
|
||||
ctx, [{"art": "det_check", "item": str(b), "detail": "Kürzen"}])
|
||||
assert bewegt is False # eingefroren → nicht geroutet → kein Fortschritt
|
||||
|
||||
|
||||
async def test_marker_platzieren_deterministisch():
|
||||
"""Der Writer setzt keine Beispiel-Marker mehr — _marker_platzieren fügt sie
|
||||
hinter den Atom-Absatz ein (atom-genau)."""
|
||||
import db
|
||||
import guide
|
||||
from conftest import topic_anlegen
|
||||
topic = topic_anlegen("platz")
|
||||
b = db.insert("bausteine", topic=topic, ziel_id=1, titel="B", ord=0, status="neu")
|
||||
a = db.insert("atome", topic=topic, titel="X", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b, ord=0, braucht=db.j([]))
|
||||
art = db.insert("artefakte", atom_id=a, typ="beispiel", status="verifiziert",
|
||||
inhalt=db.j({"form": "tabelle", "tabelle": "| A | B |\n|---|---|\n| 1 | 2 |"}))
|
||||
lang = f"#### X\n<!-- atom: {a} | X -->\nErklärung von X.\n\nWeiterer Absatz zum Thema."
|
||||
out = guide._marker_platzieren(db.one("SELECT * FROM bausteine WHERE id=?", (b,)), lang)
|
||||
assert f"<!-- beispiel: {art} -->" in out
|
||||
# der Marker steht NACH dem Atom-Absatz
|
||||
assert out.index("<!-- beispiel:") < out.index("Weiterer Absatz")
|
||||
# idempotent: zweiter Lauf dupliziert nicht
|
||||
assert guide._marker_platzieren(db.one("SELECT * FROM bausteine WHERE id=?", (b,)),
|
||||
out).count("<!-- beispiel:") == 1
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
"""Ebene 3.5 Diagramme: LLM-Diagramme (Automat/Ablauf/Baum), Spec→Mermaid, Assembly."""
|
||||
|
||||
import db
|
||||
import diagramme
|
||||
import llm
|
||||
from conftest import run_anlegen, topic_anlegen
|
||||
|
||||
|
||||
def _baustein_mit_kette(topic, n=4):
|
||||
"""Baustein mit n Atomen und einer braucht-Kette a0←a1←…←a(n-1) (n-1 Kanten)."""
|
||||
soll = db.insert("soll", topic=topic, punkt="P", status="bestaetigt", belege=db.j([]))
|
||||
ziel = db.insert("lernziele", topic=topic, text="Kann P", soll_id=soll, status="aktiv")
|
||||
b = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu")
|
||||
atome = [db.insert("atome", topic=topic, titel=f"Atom {i}", typ="begriff",
|
||||
definition="d", status="neu", baustein_id=b, ord=i, braucht=db.j([]))
|
||||
for i in range(n)]
|
||||
for i in range(1, n): # Atom i braucht Atom i-1
|
||||
db.execute("INSERT INTO kanten(topic, von_atom, zu_atom, art) VALUES(?,?,?,'braucht')",
|
||||
(topic, atome[i], atome[i - 1]))
|
||||
return b, atome
|
||||
|
||||
|
||||
def test_spec_zu_mermaid_flowchart():
|
||||
spec = {"typ": "flow",
|
||||
"knoten": [{"id": "1", "label": "A"}, {"id": "2", "label": 'B "x" [y]'}],
|
||||
"kanten": [{"von": "1", "zu": "2", "label": ""}]}
|
||||
out = diagramme._spec_zu_mermaid(spec)
|
||||
assert out.startswith("flowchart TD")
|
||||
assert 'n1["A"]' in out and "n1 --> n2" in out
|
||||
assert '"' not in out.split("\n", 1)[1].replace('["', "").replace('"]', "") # Label entschärft
|
||||
|
||||
|
||||
async def test_fake_judge_nein_keine_diagramme():
|
||||
topic = topic_anlegen("diag-leer")
|
||||
b, _ = _baustein_mit_kette(topic, n=2)
|
||||
ctx = llm.Kontext(run_anlegen(topic), topic, "minimax")
|
||||
await diagramme.bauen(ctx) # Fake-Judge sagt noetig=False → nur 'kein'-Merkzeile
|
||||
assert db.query("SELECT id FROM diagramme WHERE topic=? AND status='verifiziert'",
|
||||
(topic,)) == []
|
||||
assert db.one("SELECT status FROM diagramme WHERE baustein_id=?", (b,))["status"] == "kein"
|
||||
|
||||
|
||||
async def test_llm_diagramm_plan_spec_verify(monkeypatch):
|
||||
"""LLM-Zweig: Judge sagt nötig → Spec → Mermaid → Grounding-Panel verifiziert."""
|
||||
import fake_agents
|
||||
topic = topic_anlegen("diag-llm")
|
||||
# Baustein OHNE genug Kanten (kein DAG) → LLM-Zweig entscheidet
|
||||
soll = db.insert("soll", topic=topic, punkt="P", status="bestaetigt", belege=db.j([]))
|
||||
ziel = db.insert("lernziele", topic=topic, text="Kann P", soll_id=soll, status="aktiv")
|
||||
b = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="Automat", ord=0, status="neu")
|
||||
for i in range(3):
|
||||
db.insert("atome", topic=topic, titel=f"Zustand {i}", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b, ord=i, braucht=db.j([]))
|
||||
monkeypatch.setitem(fake_agents._HANDLER, "Diagramm-Plan",
|
||||
lambda p: {"noetig": True, "typ": "state"})
|
||||
ctx = llm.Kontext(run_anlegen(topic), topic, "minimax")
|
||||
await diagramme.bauen(ctx)
|
||||
d = db.one("SELECT * FROM diagramme WHERE baustein_id=?", (b,))
|
||||
assert d["status"] == "verifiziert" and d["quelle"] == "llm" and d["typ"] == "state"
|
||||
assert d["mermaid"].startswith("stateDiagram-v2")
|
||||
|
||||
|
||||
async def test_llm_diagramm_grounding_verwirft(monkeypatch):
|
||||
"""Grounding-Panel einstimmig ok=false → Diagramm verworfen (fail-closed)."""
|
||||
import fake_agents
|
||||
topic = topic_anlegen("diag-verwirf")
|
||||
soll = db.insert("soll", topic=topic, punkt="P", status="bestaetigt", belege=db.j([]))
|
||||
ziel = db.insert("lernziele", topic=topic, text="Kann P", soll_id=soll, status="aktiv")
|
||||
b = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="X", ord=0, status="neu")
|
||||
for i in range(3):
|
||||
db.insert("atome", topic=topic, titel=f"K{i}", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b, ord=i, braucht=db.j([]))
|
||||
monkeypatch.setitem(fake_agents._HANDLER, "Diagramm-Plan",
|
||||
lambda p: {"noetig": True, "typ": "flow"})
|
||||
monkeypatch.setitem(fake_agents._HANDLER, "Diagramm-Verify",
|
||||
lambda p: [{"ok": False, "mangel": "erfundener Knoten"}])
|
||||
ctx = llm.Kontext(run_anlegen(topic), topic, "minimax")
|
||||
await diagramme.bauen(ctx)
|
||||
assert db.one("SELECT status FROM diagramme WHERE baustein_id=?", (b,))["status"] == "verworfen"
|
||||
|
||||
|
||||
async def test_marker_wird_zu_fence():
|
||||
"""Assembly: platzierter <!-- diagramm: id --> → ```mermaid-Fence."""
|
||||
import guide
|
||||
topic = topic_anlegen("diag-place")
|
||||
b, _ = _baustein_mit_kette(topic, n=2)
|
||||
mermaid = "flowchart TD\n n1[\"A\"]\n n2[\"B\"]\n n1 --> n2"
|
||||
d = db.insert("diagramme", topic=topic, baustein_id=b, typ="flow", quelle="llm",
|
||||
spec=db.j({}), mermaid=mermaid, status="verifiziert")
|
||||
db.insert("sections", baustein_id=b, stage="done", text_kompakt="- k",
|
||||
text_lang=f"Text.\n\n<!-- diagramm: {d} -->\n\nMehr Text.")
|
||||
lang = guide.kapitel_struktur(topic)[0]["sections"][0]["lang"]
|
||||
assert "```mermaid" in lang and "flowchart TD" in lang
|
||||
assert "<!-- diagramm:" not in lang # Marker ersetzt
|
||||
|
||||
|
||||
async def test_toter_diagramm_marker_wird_entfernt():
|
||||
import guide
|
||||
topic = topic_anlegen("diag-tot")
|
||||
b, _ = _baustein_mit_kette(topic, n=2) # kein Diagramm gebaut
|
||||
db.insert("sections", baustein_id=b, stage="done", text_kompakt="- k",
|
||||
text_lang="Text.\n\n<!-- diagramm: 999 -->\n\nMehr.")
|
||||
lang = guide.kapitel_struktur(topic)[0]["sections"][0]["lang"]
|
||||
assert "<!-- diagramm:" not in lang and "```mermaid" not in lang
|
||||
|
||||
|
||||
async def test_guide_messen_meldet_toten_marker():
|
||||
"""Ein Marker ohne verifiziertes Diagramm → diagramm_marker_tot in guide.messen."""
|
||||
import guide
|
||||
topic = topic_anlegen("diag-qa")
|
||||
b, _ = _baustein_mit_kette(topic, n=4)
|
||||
db.insert("sections", baustein_id=b, stage="done", text_kompakt="- k",
|
||||
text_lang="Text.\n\n<!-- diagramm: 777 -->\n\n" + "Wort " * 60)
|
||||
ctx = llm.Kontext(run_anlegen(topic), topic, "minimax")
|
||||
ctx.ebene = "guide"
|
||||
assert "diagramm_marker_tot" in {x["art"] for x in guide.messen(ctx)}
|
||||
|
||||
|
||||
async def test_marker_platzieren_deterministisch():
|
||||
"""Der Writer setzt keine Beispiel-Marker mehr — _marker_platzieren fügt sie
|
||||
hinter den Atom-Absatz ein (atom-genau)."""
|
||||
import guide
|
||||
topic = topic_anlegen("platz")
|
||||
b = db.insert("bausteine", topic=topic, ziel_id=1, titel="B", ord=0, status="neu")
|
||||
a = db.insert("atome", topic=topic, titel="X", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b, ord=0, braucht=db.j([]))
|
||||
art = db.insert("artefakte", atom_id=a, typ="beispiel", status="verifiziert",
|
||||
inhalt=db.j({"form": "tabelle", "tabelle": "| A | B |\n|---|---|\n| 1 | 2 |"}))
|
||||
lang = f"#### X\n<!-- atom: {a} | X -->\nErklärung von X.\n\nWeiterer Absatz zum Thema."
|
||||
out = guide._marker_platzieren(db.one("SELECT * FROM bausteine WHERE id=?", (b,)), lang)
|
||||
assert f"<!-- beispiel: {art} -->" in out
|
||||
# der Marker steht NACH dem Atom-Absatz
|
||||
assert out.index("<!-- beispiel:") < out.index("Weiterer Absatz")
|
||||
# idempotent: zweiter Lauf dupliziert nicht
|
||||
assert guide._marker_platzieren(db.one("SELECT * FROM bausteine WHERE id=?", (b,)),
|
||||
out).count("<!-- beispiel:") == 1
|
||||
@@ -146,7 +146,7 @@ async def test_ebenen_entfernen_kaskade():
|
||||
assert db.one("SELECT status FROM topics WHERE name=?", (topic,))["status"] == "artefakte_fertig"
|
||||
# Anzeige-Reset: Token-Zählung der entfernten Ebenen beginnt neu (Events bleiben)
|
||||
resets = db.uj(db.one("SELECT resets FROM topics WHERE name=?", (topic,))["resets"], {})
|
||||
assert set(resets) == {"struktur", "diagramme", "guide"} # diagramme hängt an struktur
|
||||
assert set(resets) == {"struktur", "guide"} # guide hängt an struktur
|
||||
assert resets["struktur"] == db.one("SELECT MAX(id) AS m FROM events")["m"]
|
||||
run2 = await _lauf_komplett(topic)
|
||||
_pruefe_endzustand(topic, run2)
|
||||
|
||||
Reference in New Issue
Block a user