update
This commit is contained in:
@@ -248,12 +248,17 @@ def _beispiel_pruefen(inh: dict) -> tuple[bool, dict]:
|
||||
return True, inh
|
||||
|
||||
|
||||
def _formen_gate(topic: str) -> None:
|
||||
def _formen_gate(topic: str, atom_ids: list[int] | None = None) -> None:
|
||||
"""Kandidat-Beispiele durch ihr Form-Gate: Syntax/Struktur ungültig → verworfen
|
||||
(fail-closed für den Inhalt; ein fehlendes Beispiel ist erlaubt, optional)."""
|
||||
for b in db.query(
|
||||
"SELECT ar.* FROM artefakte ar JOIN atome a ON a.id=ar.atom_id"
|
||||
" WHERE a.topic=? AND ar.typ='beispiel' AND ar.status='kandidat'", (topic,)):
|
||||
(fail-closed für den Inhalt; ein fehlendes Beispiel ist erlaubt, optional).
|
||||
atom_ids: optional auf einen Chunk beschränken (Pipeline pro Kette)."""
|
||||
sql = ("SELECT ar.* FROM artefakte ar JOIN atome a ON a.id=ar.atom_id"
|
||||
" WHERE a.topic=? AND ar.typ='beispiel' AND ar.status='kandidat'")
|
||||
params: tuple = (topic,)
|
||||
if atom_ids:
|
||||
sql += f" AND ar.atom_id IN ({','.join('?' * len(atom_ids))})"
|
||||
params += tuple(atom_ids)
|
||||
for b in db.query(sql, params):
|
||||
inh = db.uj(b["inhalt"], {})
|
||||
ok, inh_neu = _beispiel_pruefen(inh)
|
||||
if not ok:
|
||||
@@ -262,13 +267,22 @@ def _formen_gate(topic: str) -> None:
|
||||
db.update("artefakte", "id", b["id"], inhalt=db.j(inh_neu))
|
||||
|
||||
|
||||
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
|
||||
Wall-Clock: 1 Nachzügler blockierte alle Verifies)."""
|
||||
ohne = [a for a in chunk if not db.one(
|
||||
"SELECT id FROM artefakte WHERE atom_id=? AND typ='flashcard'"
|
||||
" AND status!='verworfen' LIMIT 1", (a["id"],))]
|
||||
if ohne:
|
||||
await _generieren(ctx, ohne)
|
||||
_formen_gate(ctx.topic, [a["id"] for a in chunk])
|
||||
await _verifizieren(ctx, chunk)
|
||||
|
||||
|
||||
async def bauen(ctx: llm.Kontext) -> None:
|
||||
ctx.ebene = EBENE
|
||||
neu = _chunks(ctx.topic, nur_ohne=True)
|
||||
await llm.alle(_generieren(ctx, c) for c in neu)
|
||||
_formen_gate(ctx.topic) # form-spezifische Syntax/Struktur-Gates vor dem Panel
|
||||
alle = _chunks(ctx.topic, nur_ohne=False)
|
||||
await llm.alle(_verifizieren(ctx, c) for c in alle)
|
||||
await llm.alle(_kette(ctx, c) for c in _chunks(ctx.topic, nur_ohne=False))
|
||||
|
||||
|
||||
def messen(ctx: llm.Kontext) -> list[dict]:
|
||||
|
||||
@@ -137,6 +137,10 @@ def _init_schema(con: sqlite3.Connection) -> None:
|
||||
con.execute(zusatz)
|
||||
except sqlite3.OperationalError:
|
||||
pass # Spalte existiert schon
|
||||
# Status-Marker entfernter Ebenen (diagramme/fehlersuche) zurückrollen — sonst
|
||||
# kennt _ORDNUNG den Status nicht und der Resume fiele fälschlich auf korpus zurück.
|
||||
con.execute("UPDATE topics SET status='struktur_fertig'"
|
||||
" WHERE status IN ('diagramme_fertig', 'fehlersuche_fertig')")
|
||||
con.commit()
|
||||
|
||||
|
||||
|
||||
@@ -560,8 +560,8 @@ async def bauen(ctx: llm.Kontext) -> None:
|
||||
offen = [b for b in _bausteine(ctx.topic) if b["status"] != "fertig"
|
||||
or (db.one("SELECT stage FROM sections WHERE baustein_id=?", (b["id"],)) or
|
||||
{"stage": "writer"})["stage"] != "done"]
|
||||
await llm.alle(_karte(ctx, b) for b in offen)
|
||||
await _kapitel_intros(ctx)
|
||||
# Intros brauchen nur Kapitel+Ziele (keine Sections) → parallel zu den Karten
|
||||
await llm.alle([*(_karte(ctx, b) for b in offen), _kapitel_intros(ctx)])
|
||||
|
||||
|
||||
def kapitel_struktur(topic: str) -> list[dict]:
|
||||
|
||||
@@ -6,6 +6,24 @@ import re
|
||||
|
||||
_FENCE = re.compile(r"```(?:json)?\s*(.*?)```", re.DOTALL)
|
||||
|
||||
# LaTeX in JSON-Strings ("$\in$", "\Sigma") ist ein INVALIDES Escape → json.loads
|
||||
# wirft (kostete bis zu 29 % Parse-Fehler bei Mathe-Stages). Nur ungültige Escapes
|
||||
# verdoppeln: gültige (\" \\ \/ \b \f \n \r \t \uXXXX) bleiben; \u ohne 4
|
||||
# Hex-Ziffern (\underline) zählt als ungültig.
|
||||
_UNGUELTIGES_ESCAPE = re.compile(r'\\(?![\"\\/bfnrt]|u[0-9a-fA-F]{4})')
|
||||
|
||||
|
||||
def _loads(s: str):
|
||||
"""json.loads mit EINER Reparaturstufe für rohe LaTeX-Backslashes. Valides
|
||||
JSON durchläuft unverändert (Reparatur nur im Fehlerfall)."""
|
||||
try:
|
||||
return json.loads(s)
|
||||
except ValueError:
|
||||
try:
|
||||
return json.loads(_UNGUELTIGES_ESCAPE.sub(r"\\\\", s))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def parse(text: str):
|
||||
"""→ Objekt oder None. Nie werfen — der Aufrufer entscheidet über Retry.
|
||||
@@ -54,11 +72,8 @@ def _ein_kandidat(text: str):
|
||||
elif c == end_ch:
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
try:
|
||||
return json.loads(text[start:i + 1])
|
||||
except ValueError:
|
||||
break
|
||||
try:
|
||||
return json.loads(text)
|
||||
except ValueError:
|
||||
return None
|
||||
daten = _loads(text[start:i + 1])
|
||||
if daten is not None:
|
||||
return daten
|
||||
break
|
||||
return _loads(text)
|
||||
|
||||
@@ -134,6 +134,11 @@ async def call(ctx: Kontext, *, stage: str, template: str, werte: dict,
|
||||
status = "ok"
|
||||
return res.text
|
||||
daten = jsonx.parse(res.text)
|
||||
# Modelle lassen bei EINEM Element die Array-Klammern gern weg —
|
||||
# ein valides Einzel-Dict zählt als 1-elementige Liste (sonst
|
||||
# brannte jeder 1-Item-Chunk 3 Neuversuche durch).
|
||||
if erwartet is list and isinstance(daten, dict) and daten:
|
||||
daten = [daten] # leeres {} bleibt „falscher Typ" (= Ausfall)
|
||||
if daten is not None and isinstance(daten, erwartet):
|
||||
status = "ok"
|
||||
return daten
|
||||
|
||||
Reference in New Issue
Block a user