This commit is contained in:
team3
2026-07-13 05:18:55 +02:00
parent 7ee425f7fa
commit 8926f87185
19 changed files with 703 additions and 41 deletions

View File

@@ -10,9 +10,10 @@ import re
import subprocess
import tempfile
import belege
import db
import llm
from config import ARTEFAKT_CHUNK_ATOME, BEISPIEL_FORMEN, VERIFY_PANEL
from config import ARTEFAKT_CHUNK_ATOME, AUSSAGEN_JE_ATOM, BEISPIEL_FORMEN, VERIFY_PANEL
log = logging.getLogger("creator2.artefakte")
@@ -39,8 +40,10 @@ def _referenziert_quelle(inhalt: dict) -> bool:
def _zitate(atom_id: int) -> str:
rows = db.query("SELECT zitat FROM anker WHERE atom_id=? AND start>=0", (atom_id,))
return "\n".join(f"> {r['zitat']}" for r in rows) or "(kein Anker)"
# Quelltext-FENSTER um die Anker (belege.py) — nur das Zitat war zu schmal:
# inhaltliche Karten zu Verfahren galten als unbelegt und churnten endlos.
bloecke = belege.fenster_liste(atom_id)
return "\n---\n".join(bloecke) or "(kein Anker)"
def _atom_block(a: dict) -> str:
@@ -267,6 +270,78 @@ def _formen_gate(topic: str, atom_ids: list[int] | None = None) -> None:
db.update("artefakte", "id", b["id"], inhalt=db.j(inh_neu))
async def _aussagen_generieren(ctx: llm.Kontext, chunk: list[dict]) -> None:
"""Prüfungs-PAARE je Atom: wahre Aussage (ankerprüfbare Paraphrase) + falsche
(GENAU EIN Aspekt gekippt — Mutations-Muster auf Satzebene). Der Code baut je
Paar 2 Zeilen; die wahre Aussage ist gratis die Feedback-Erklärung der falschen."""
bloecke = "\n\n".join(_atom_block(a) for a in chunk)
res = await llm.call(ctx, stage="aussagen_gen", template="Aussagen-Generate",
werte={"atome": bloecke, "paare": AUSSAGEN_JE_ATOM},
role="guide", n=len(chunk), item=f"as{chunk[0]['id']}",
erwartet=list)
gueltig = {a["id"] for a in chunk}
zaehler: dict[int, int] = {}
for e in res or []:
if not isinstance(e, dict) or e.get("atom") not in gueltig:
continue
atom_id = e["atom"]
wahr, falsch = str(e.get("wahr", "")).strip(), str(e.get("falsch", "")).strip()
if not wahr or not falsch or wahr == falsch:
continue
if _referenziert_quelle({"text": f"{wahr} {falsch}"}):
continue
i = zaehler.get(atom_id, 0)
zaehler[atom_id] = i + 1
paar = f"{atom_id}-{i}"
db.insert("artefakte", atom_id=atom_id, typ="aussage", status="kandidat",
inhalt=db.j({"text": wahr, "wahr": True, "paar": paar,
"erklaerung": wahr}))
db.insert("artefakte", atom_id=atom_id, typ="aussage", status="kandidat",
inhalt=db.j({"text": falsch, "wahr": False, "paar": paar,
"erklaerung": wahr}))
async def _aussagen_verifizieren(ctx: llm.Kontext, chunk: list[dict]) -> None:
"""Getrennter Maßstab je SOLL (Lehre aus der Fehlersuche): wahre müssen aus den
Belegen folgen, falsche EINDEUTIG widersprechen. Panel einstimmig → verifiziert;
unvollständiges Panel → kandidat lassen (Lektion 20)."""
bloecke, kandidaten = [], []
for a in chunk:
rows = db.query("SELECT * FROM artefakte WHERE atom_id=? AND typ='aussage'"
" AND status='kandidat'", (a["id"],))
if not rows:
continue
zeilen = [f"ATOM {a['id']} Belege:\n{_zitate(a['id'])}"]
for k in rows:
inh = db.uj(k["inhalt"], {})
soll = "wahr" if inh.get("wahr") else "falsch"
zeilen.append(f"AUSSAGE {k['id']} [SOLL: {soll}]: {inh.get('text', '')}")
kandidaten.append(k)
bloecke.append("\n".join(zeilen))
if not kandidaten:
return
stimmen = await llm.panel(ctx, VERIFY_PANEL, stage="aussagen_verify",
template="Aussagen-Verify", role="judge",
werte={"aussagen": "\n\n".join(bloecke)},
n=len(kandidaten), item=f"asv{chunk[0]['id']}",
erwartet=list)
def urteil_fuer(kid: int):
def urteil(stimme):
for e in stimme:
if isinstance(e, dict) and e.get("aussage") == kid:
return bool(e.get("ok"))
return None
return urteil
for k in kandidaten:
votes = [u for s in stimmen if (u := urteil_fuer(k["id"])(s)) is not None]
if len(votes) < VERIFY_PANEL:
continue
db.update("artefakte", "id", k["id"],
status="verifiziert" if all(votes) else "verworfen")
async def _kette(ctx: llm.Kontext, chunk: list[dict]) -> None:
"""gen→gate→verify EINES Chunks verkettet — der Verify braucht nur die eigenen
Kandidaten, nicht die Generierung fremder Chunks (Stage-Barriere kostete
@@ -278,6 +353,12 @@ async def _kette(ctx: llm.Kontext, chunk: list[dict]) -> None:
await _generieren(ctx, ohne)
_formen_gate(ctx.topic, [a["id"] for a in chunk])
await _verifizieren(ctx, chunk)
ohne_aussage = [a for a in chunk if not db.one(
"SELECT id FROM artefakte WHERE atom_id=? AND typ='aussage'"
" AND status!='verworfen' LIMIT 1", (a["id"],))]
if ohne_aussage:
await _aussagen_generieren(ctx, ohne_aussage)
await _aussagen_verifizieren(ctx, chunk)
async def bauen(ctx: llm.Kontext) -> None:
@@ -295,6 +376,10 @@ def messen(ctx: llm.Kontext) -> list[dict]:
if not any(k["status"] == "verifiziert" for k in karten):
befunde.append({"art": "atom_ohne_flashcard", "item": str(a["id"]),
"detail": a["titel"]})
if not db.one("SELECT id FROM artefakte WHERE atom_id=? AND typ='aussage'"
" AND status='verifiziert' LIMIT 1", (a["id"],)):
befunde.append({"art": "atom_ohne_aussage", "item": str(a["id"]),
"detail": a["titel"]})
offen = db.query("SELECT id FROM artefakte WHERE atom_id=? AND status='kandidat'",
(a["id"],))
for k in offen: