update
This commit is contained in:
@@ -98,6 +98,7 @@ TIMEOUTS = {
|
|||||||
"verify": (200, 10),
|
"verify": (200, 10),
|
||||||
"fix": (300, 15),
|
"fix": (300, 15),
|
||||||
"ziele": (300, 6),
|
"ziele": (300, 6),
|
||||||
|
"level": (200, 4),
|
||||||
"kapitel": (200, 2),
|
"kapitel": (200, 2),
|
||||||
"zyklus": (150, 0),
|
"zyklus": (150, 0),
|
||||||
"writer": (450, 60),
|
"writer": (450, 60),
|
||||||
|
|||||||
@@ -175,6 +175,12 @@ def _kapitel_intro(prompt: str):
|
|||||||
" in aufeinander aufbauenden Abschnitten."}
|
" in aufeinander aufbauenden Abschnitten."}
|
||||||
|
|
||||||
|
|
||||||
|
def _level_kalibrierung(prompt: str):
|
||||||
|
ids = _ids(_text_nach(prompt, "ATOME:"))
|
||||||
|
# deterministisch: erstes Atom wird Grundgerüst, Rest Standardstoff
|
||||||
|
return [{"atom": i, "level": "E" if n == 0 else "M"} for n, i in enumerate(ids)]
|
||||||
|
|
||||||
|
|
||||||
def _kapitel_schnitt(prompt: str):
|
def _kapitel_schnitt(prompt: str):
|
||||||
ids = [int(i) for i in re.findall(r"^(\d+): ", _text_nach(prompt, "BAUSTEINE:"),
|
ids = [int(i) for i in re.findall(r"^(\d+): ", _text_nach(prompt, "BAUSTEINE:"),
|
||||||
re.MULTILINE)]
|
re.MULTILINE)]
|
||||||
@@ -238,7 +244,7 @@ _HANDLER = {
|
|||||||
"Atom-Luecke": _luecke, "Soll-Abgedeckt": lambda p: {"abgedeckt": True},
|
"Atom-Luecke": _luecke, "Soll-Abgedeckt": lambda p: {"abgedeckt": True},
|
||||||
"Artefakt-Generate": _artefakt_generate, "Artefakt-Verify": _artefakt_verify,
|
"Artefakt-Generate": _artefakt_generate, "Artefakt-Verify": _artefakt_verify,
|
||||||
"Artefakt-Fix": _artefakt_fix, "Lernziele": _lernziele, "Kapitel-Intro": _kapitel_intro,
|
"Artefakt-Fix": _artefakt_fix, "Lernziele": _lernziele, "Kapitel-Intro": _kapitel_intro,
|
||||||
"Kapitel-Schnitt": _kapitel_schnitt,
|
"Kapitel-Schnitt": _kapitel_schnitt, "Level-Kalibrierung": _level_kalibrierung,
|
||||||
"Kanten-Zyklus": _zyklus,
|
"Kanten-Zyklus": _zyklus,
|
||||||
"Guide-Writer": _writer, "Guide-Pruefer": _pruefer, "Guide-Fix": _fix,
|
"Guide-Writer": _writer, "Guide-Pruefer": _pruefer, "Guide-Fix": _fix,
|
||||||
"QA-Guide-Falsch": _qa_falsch, "QA-Guide-Falsch-Check": _qa_falsch_check,
|
"QA-Guide-Falsch": _qa_falsch, "QA-Guide-Falsch-Check": _qa_falsch_check,
|
||||||
|
|||||||
@@ -297,6 +297,38 @@ def _bausteine_ordnen(topic: str, kanten: list[tuple[int, int]], rang: dict) ->
|
|||||||
db.update("bausteine", "id", b_id, ord=pos)
|
db.update("bausteine", "id", b_id, ord=pos)
|
||||||
|
|
||||||
|
|
||||||
|
async def _level_kalibrieren(ctx: llm.Kontext) -> None:
|
||||||
|
"""Stufen als Komplexitätsschichten (4C/ID): Die Extraktion sieht nur ihren
|
||||||
|
Chunk und stuft absolut (aak: 13 % E → Einstiegs-Durchgang war ein
|
||||||
|
Lücken-Gerippe). Hier stuft ein Judge je Baustein RELATIV nach — er sieht
|
||||||
|
alle Atome nebeneinander. E-Atome müssen allein einen kohärenten
|
||||||
|
Kurzdurchgang tragen; rein vertiefende Bausteine bleiben ohne E."""
|
||||||
|
bausteine = db.query("SELECT * FROM bausteine WHERE topic=?", (ctx.topic,))
|
||||||
|
atome = _atome(ctx.topic)
|
||||||
|
|
||||||
|
async def einer(b: dict) -> None:
|
||||||
|
eigene = [a for a in atome if a["baustein_id"] == b["id"]]
|
||||||
|
if not eigene:
|
||||||
|
return
|
||||||
|
# bewusst OHNE bisheriges Level: der Extraktions-Prior ist verzerrt
|
||||||
|
# (Chunk-blind, M-Anchoring) und würde die Neubewertung ankern
|
||||||
|
liste = "\n".join(f"{a['id']}: {a['titel']} — {a['definition']}" for a in eigene)
|
||||||
|
res = await llm.call(ctx, stage="level", template="Level-Kalibrierung",
|
||||||
|
werte={"titel": b["titel"], "atome": liste},
|
||||||
|
role="judge", n=len(eigene), item=f"lv{b['id']}",
|
||||||
|
erwartet=list)
|
||||||
|
gueltig = {a["id"]: a["level"] for a in eigene}
|
||||||
|
for e in res or []:
|
||||||
|
if not isinstance(e, dict):
|
||||||
|
continue
|
||||||
|
atom_id = e.get("atom")
|
||||||
|
level = str(e.get("level", "")).strip().upper()
|
||||||
|
if atom_id in gueltig and level in ("E", "M", "S") and level != gueltig[atom_id]:
|
||||||
|
db.update("atome", "id", atom_id, level=level)
|
||||||
|
|
||||||
|
await llm.alle(einer(b) for b in bausteine)
|
||||||
|
|
||||||
|
|
||||||
async def _kapitel_bilden(ctx: llm.Kontext) -> None:
|
async def _kapitel_bilden(ctx: llm.Kontext) -> None:
|
||||||
"""Kapitel = kontiguierliche Segmente der geordneten Baustein-Folge. Das Soll
|
"""Kapitel = kontiguierliche Segmente der geordneten Baustein-Folge. Das Soll
|
||||||
ist seit der Entkopplung reine Checkliste — Kapitel sind Struktur. Der Judge
|
ist seit der Entkopplung reine Checkliste — Kapitel sind Struktur. Der Judge
|
||||||
@@ -360,6 +392,7 @@ async def bauen(ctx: llm.Kontext) -> None:
|
|||||||
await _ziele_bilden(ctx)
|
await _ziele_bilden(ctx)
|
||||||
await _zyklen_brechen(ctx)
|
await _zyklen_brechen(ctx)
|
||||||
_bausteine_schneiden(ctx)
|
_bausteine_schneiden(ctx)
|
||||||
|
await _level_kalibrieren(ctx)
|
||||||
await _kapitel_bilden(ctx)
|
await _kapitel_bilden(ctx)
|
||||||
|
|
||||||
|
|
||||||
@@ -420,5 +453,6 @@ async def reparieren(ctx: llm.Kontext, befunde: list[dict]) -> bool:
|
|||||||
await _ziele_bilden(ctx) # fängt Partition-Lücken
|
await _ziele_bilden(ctx) # fängt Partition-Lücken
|
||||||
await _zyklen_brechen(ctx) # fängt Zyklen
|
await _zyklen_brechen(ctx) # fängt Zyklen
|
||||||
_bausteine_schneiden(ctx) # Neu-Schnitt fängt Band + Vorwärts-Kanten
|
_bausteine_schneiden(ctx) # Neu-Schnitt fängt Band + Vorwärts-Kanten
|
||||||
|
await _level_kalibrieren(ctx)
|
||||||
await _kapitel_bilden(ctx) # fängt Kapitel-Befunde
|
await _kapitel_bilden(ctx) # fängt Kapitel-Befunde
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const daten = ref(null)
|
|||||||
const level = ref('E') // Default: Einstieg — Umfang wächst per Toggle (E→M→S)
|
const level = ref('E') // Default: Einstieg — Umfang wächst per Toggle (E→M→S)
|
||||||
const ansicht = ref('erklaerend') // erklaerend = Fließtext | kompakt = Stichpunkte
|
const ansicht = ref('erklaerend') // erklaerend = Fließtext | kompakt = Stichpunkte
|
||||||
const LEVEL_ICON = { E: '●○○', M: '●●○', S: '●●●' }
|
const LEVEL_ICON = { E: '●○○', M: '●●○', S: '●●●' }
|
||||||
const LEVEL_NAME = { E: 'Umfang: nur Einstieg', M: 'Umfang: bis Mittel', S: 'Umfang: alles' }
|
const LEVEL_NAME = { E: 'Umfang: Grundgerüst', M: 'Umfang: + Standardstoff', S: 'Umfang: alles' }
|
||||||
const fokus = ref(false) // Fokus: Vollbild — App-Chrome + Navigation weg, Karten aufgelöst
|
const fokus = ref(false) // Fokus: Vollbild — App-Chrome + Navigation weg, Karten aufgelöst
|
||||||
watch(fokus, (f) => document.body.classList.toggle('vollbild', f))
|
watch(fokus, (f) => document.body.classList.toggle('vollbild', f))
|
||||||
onUnmounted(() => document.body.classList.remove('vollbild'))
|
onUnmounted(() => document.body.classList.remove('vollbild'))
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ Regeln:
|
|||||||
ergibt das Atom „NP-Vollständigkeit des Hamiltonkreis-Problems" — nie „X mit deg ≥ 42".
|
ergibt das Atom „NP-Vollständigkeit des Hamiltonkreis-Problems" — nie „X mit deg ≥ 42".
|
||||||
- Aufgabeninstanzen, Aufgabennummern, Punktzahlen sind KEINE Atome und gehören in
|
- Aufgabeninstanzen, Aufgabennummern, Punktzahlen sind KEINE Atome und gehören in
|
||||||
kein Feld außer dem Zitat.
|
kein Feld außer dem Zitat.
|
||||||
- „typ": begriff | aussage | verfahren. „level": E | M | S.
|
- „typ": begriff | aussage | verfahren.
|
||||||
|
- „level": E = Grundgerüst (braucht man beim ERSTEN Durchgang) | M = Standardstoff
|
||||||
|
| S = Feinheiten/Spezialfälle. Relativ zum restlichen Stoff einstufen.
|
||||||
- „zitat": die Aufgabenstelle WÖRTLICH kopiert (5–40 Wörter) — sie ist der Beleg,
|
- „zitat": die Aufgabenstelle WÖRTLICH kopiert (5–40 Wörter) — sie ist der Beleg,
|
||||||
dass dieses Konzept geübt wird.
|
dass dieses Konzept geübt wird.
|
||||||
- Prüft eine Aufgabe mehrere Konzepte, wird jedes ein eigenes Atom.
|
- Prüft eine Aufgabe mehrere Konzepte, wird jedes ein eigenes Atom.
|
||||||
@@ -22,4 +24,5 @@ QUELLTEXT:
|
|||||||
{text}
|
{text}
|
||||||
|
|
||||||
Antworte NUR mit JSON (keine Code-Fences, keine Prosa, KEINE Datei):
|
Antworte NUR mit JSON (keine Code-Fences, keine Prosa, KEINE Datei):
|
||||||
[{{"titel": "…", "typ": "aussage", "definition": "…", "zitat": "…", "level": "M", "braucht": []}}]
|
[{{"titel": "…", "typ": "aussage", "definition": "…", "zitat": "…", "level": "E", "braucht": []}},
|
||||||
|
{{"titel": "…", "typ": "verfahren", "definition": "…", "zitat": "…", "level": "M", "braucht": []}}]
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ KEINE Atome. Regeln:
|
|||||||
- „definition": 1–2 Sätze, eigenständig verständlich (dient später dem Dubletten-Vergleich).
|
- „definition": 1–2 Sätze, eigenständig verständlich (dient später dem Dubletten-Vergleich).
|
||||||
- „zitat": die Kernpassage WÖRTLICH aus dem Quelltext kopiert (exakt, Zeichen für Zeichen,
|
- „zitat": die Kernpassage WÖRTLICH aus dem Quelltext kopiert (exakt, Zeichen für Zeichen,
|
||||||
5–40 Wörter). Ohne wörtliches Zitat kein Atom.
|
5–40 Wörter). Ohne wörtliches Zitat kein Atom.
|
||||||
- „level": E (Einstieg) | M (Mittel) | S (Schwer).
|
- „level": E = Grundgerüst (Kernbegriffe/Kernaussagen, die man beim ERSTEN Durchgang
|
||||||
|
durchs Thema braucht) | M = Standardstoff (Sätze, Verfahren, Standardbeweise)
|
||||||
|
| S = Feinheiten (Spezialfälle, Beweisdetails, Optimierungen). Relativ zum
|
||||||
|
restlichen Stoff des Auszugs einstufen — Stufen sind Komplexitätsschichten,
|
||||||
|
keine Bauchgefühl-Schwierigkeit.
|
||||||
- „braucht": Titel anderer Atome DIESES Auszugs, die man vorher verstanden haben muss
|
- „braucht": Titel anderer Atome DIESES Auszugs, die man vorher verstanden haben muss
|
||||||
(nur echte Voraussetzungen, keine bloße Verwandtschaft). Leer, wenn keine.
|
(nur echte Voraussetzungen, keine bloße Verwandtschaft). Leer, wenn keine.
|
||||||
- Vollständigkeit zählt: JEDES lernbare Konzept des Auszugs wird ein Atom.
|
- Vollständigkeit zählt: JEDES lernbare Konzept des Auszugs wird ein Atom.
|
||||||
@@ -24,4 +28,5 @@ QUELLTEXT:
|
|||||||
{text}
|
{text}
|
||||||
|
|
||||||
Antworte NUR mit JSON (keine Code-Fences, keine Prosa, KEINE Datei):
|
Antworte NUR mit JSON (keine Code-Fences, keine Prosa, KEINE Datei):
|
||||||
[{{"titel": "…", "typ": "begriff", "definition": "…", "zitat": "…", "level": "M", "braucht": []}}]
|
[{{"titel": "…", "typ": "begriff", "definition": "…", "zitat": "…", "level": "E", "braucht": []}},
|
||||||
|
{{"titel": "…", "typ": "aussage", "definition": "…", "zitat": "…", "level": "M", "braucht": ["…"]}}]
|
||||||
|
|||||||
21
templates/Level-Kalibrierung.md
Normal file
21
templates/Level-Kalibrierung.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!-- template:Level-Kalibrierung -->
|
||||||
|
Unten die Atome EINES Guide-Bausteins „{titel}" (Format „id: titel — definition").
|
||||||
|
|
||||||
|
Kalibriere die Lernstufen RELATIV zueinander. Die Stufen sind Komplexitätsschichten
|
||||||
|
(nicht „leicht/schwer"):
|
||||||
|
- E = Grundgerüst: das Minimum, das man beim ERSTEN Durchgang braucht, um den
|
||||||
|
Baustein zu verstehen — Kernbegriffe, die Kernaussage. Die E-Atome allein müssen
|
||||||
|
einen kohärenten Kurzdurchgang ergeben.
|
||||||
|
- M = Standardstoff: Sätze, Verfahren, Standardbeweise — der zweite Durchgang.
|
||||||
|
- S = Feinheiten: Spezialfälle, Beweisdetails, Optimierungen, Randnotizen.
|
||||||
|
|
||||||
|
Regeln:
|
||||||
|
- Behandelt der Baustein grundlegenden Stoff, gehört sein tragender Kern zu E.
|
||||||
|
- Rein vertiefende Bausteine (reine Beweis-/Spezialbausteine) dürfen ohne E bleiben.
|
||||||
|
- Jede id genau einmal, keine erfinden.
|
||||||
|
|
||||||
|
ATOME:
|
||||||
|
{atome}
|
||||||
|
|
||||||
|
Antworte NUR mit JSON (keine Code-Fences):
|
||||||
|
[{{"atom": 12, "level": "E"}}, {{"atom": 13, "level": "M"}}]
|
||||||
@@ -125,3 +125,25 @@ async def test_kapitel_retry_und_fallback(monkeypatch):
|
|||||||
kaps = db.query("SELECT * FROM kapitel WHERE topic=?", (topic,))
|
kaps = db.query("SELECT * FROM kapitel WHERE topic=?", (topic,))
|
||||||
assert kaps and all(k["art"] == "fallback" for k in kaps)
|
assert kaps and all(k["art"] == "fallback" for k in kaps)
|
||||||
assert "kapitel_fallback" in {bf["art"] for bf in struktur.messen(ctx)}
|
assert "kapitel_fallback" in {bf["art"] for bf in struktur.messen(ctx)}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_level_kalibrierung(monkeypatch):
|
||||||
|
import fake_agents
|
||||||
|
topic = topic_anlegen("level")
|
||||||
|
run = run_anlegen(topic)
|
||||||
|
ziel = db.insert("lernziele", topic=topic, text="Kann X", status="aktiv")
|
||||||
|
b_id = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu")
|
||||||
|
a1 = db.insert("atome", topic=topic, titel="Kern", typ="begriff", definition="d",
|
||||||
|
level="M", status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||||
|
a2 = db.insert("atome", topic=topic, titel="Detail", typ="aussage", definition="d",
|
||||||
|
level="M", status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||||
|
|
||||||
|
def judge(prompt):
|
||||||
|
return [{"atom": a1, "level": "E"}, {"atom": a2, "level": "S"},
|
||||||
|
{"atom": 99999, "level": "E"}, {"atom": a2, "level": "X"}] # Müll ignorieren
|
||||||
|
monkeypatch.setitem(fake_agents._HANDLER, "Level-Kalibrierung", judge)
|
||||||
|
ctx = llm.Kontext(run, topic, "minimax")
|
||||||
|
ctx.ebene = "struktur"
|
||||||
|
await struktur._level_kalibrieren(ctx)
|
||||||
|
assert db.one("SELECT level FROM atome WHERE id=?", (a1,))["level"] == "E"
|
||||||
|
assert db.one("SELECT level FROM atome WHERE id=?", (a2,))["level"] == "S"
|
||||||
|
|||||||
Reference in New Issue
Block a user