update
This commit is contained in:
@@ -21,7 +21,8 @@ import re
|
||||
import database as db
|
||||
import readability
|
||||
from blocks import _sink_json
|
||||
from config import FORMAT_PURPOSE, READABILITY_ACTIVE, TEMPLATES_DIR, MAX_CONCURRENT_AGENTS_PER_TOPIC
|
||||
from config import (FORMAT_PURPOSE, GUIDE_LAENGE_MAX, GUIDE_LAENGE_MIN, READABILITY_ACTIVE,
|
||||
TEMPLATES_DIR, MAX_CONCURRENT_AGENTS_PER_TOPIC)
|
||||
from fsutil import atomic_write_json
|
||||
from jsonio import read_json_file as _json_file
|
||||
from pipeline import (CANCELLED, FAILED, OK, GenContext, _extra, _log, _prompt,
|
||||
@@ -34,9 +35,7 @@ GUIDE_STAGES = ("lernziele", "zuweisung", "writer", "fakten_gate", "coverage", "
|
||||
STAGE_LABELS = {"lernziele": "Lernziele", "zuweisung": "Zuweisung", "writer": "Writer",
|
||||
"fakten_gate": "Fakten-Gate", "coverage": "Coverage",
|
||||
"lesbarkeit": "Lesbarkeit", "done": "Fertig"}
|
||||
MAX_WRITER_ROUNDS = 2 # coverage → writer feedback loop cap (gains die after round 1–2)
|
||||
GATE_FIX_MIN = 3 # fact-gate claims below this: log only — a full fix rewrite for
|
||||
# 1–2 residual claims fired on 19/20 blocks (40 agent-minutes)
|
||||
from config import GATE_FIX_MIN, MAX_WRITER_ROUNDS, WRITER_SPLIT_SUBS # zentral tunebar
|
||||
# Simultaneous cards = the per-topic agent cap: every card busies exactly ONE agent at a
|
||||
# time (its stages run serially), so a lower number just idles slots (was hardcoded 10
|
||||
# from the old 10-slot era while the .env already allowed 24).
|
||||
@@ -65,7 +64,10 @@ def _ziele_schema(data):
|
||||
|
||||
|
||||
def _gate_schema(data):
|
||||
"""{"ok":true} → [] · {"claims":[{text,grund}]} → list · None invalid."""
|
||||
"""{"ok":true} → [] · {"claims":[{text,grund,urteil}]} → list · None invalid.
|
||||
urteil "falsch" (contradicts the facts/itself) vs "unbelegt" (true but underivable) —
|
||||
default unbelegt. Entries whose grund starts with "belegt" are dropped: one judge
|
||||
returned a 65-entry full inventory including SUPPORTED claims."""
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
if data.get("ok") is True:
|
||||
@@ -76,7 +78,12 @@ def _gate_schema(data):
|
||||
out = []
|
||||
for c in claims:
|
||||
if isinstance(c, dict) and str(c.get("text", "")).strip():
|
||||
out.append({"text": str(c["text"]).strip(), "grund": str(c.get("grund", "")).strip()})
|
||||
grund = str(c.get("grund", "")).strip()
|
||||
if grund.casefold().startswith("belegt"):
|
||||
continue
|
||||
urteil = str(c.get("urteil", "")).strip().casefold()
|
||||
out.append({"text": str(c["text"]).strip(), "grund": grund,
|
||||
"urteil": urteil if urteil == "falsch" else "unbelegt"})
|
||||
return out
|
||||
|
||||
|
||||
@@ -241,7 +248,7 @@ async def _stage_zuweisung(env: _Env, card: dict) -> bool:
|
||||
# A single section over ~45 subs measurably breaks the writer/coverage (Front Matter:
|
||||
# 4/6 objectives open after 2 rounds). First drafts of oversized cards are written in two
|
||||
# halves and merged back into ONE canonical section (all gates/assembly read one section).
|
||||
WRITER_SPLIT_SUBS = 30
|
||||
# WRITER_SPLIT_SUBS: siehe config.py
|
||||
|
||||
|
||||
def _merge_split_sections(sec_a: dict, sec_b: dict) -> str:
|
||||
@@ -397,13 +404,15 @@ async def _stage_fakten_gate(env: _Env, card: dict) -> bool:
|
||||
if status == FAILED:
|
||||
claims = [] # gate failure must not block the card — logged, text stands
|
||||
_log(env.topic, f"Fakten-Gate {card['block']}: kein Ergebnis — Text bleibt ungeprüft")
|
||||
if claims and len(claims) < GATE_FIX_MIN:
|
||||
# 1–2 Rest-Claims rechtfertigen keinen Voll-Rewrite: der Fix-Pass lief für 19/20
|
||||
# Blöcke und kostete 40 Agent-Minuten; die Guide-QA-Fachlichkeits-Stichprobe misst nach
|
||||
falsch = [c for c in claims if c.get("urteil") == "falsch"] if claims else []
|
||||
if claims and not falsch and len(claims) < GATE_FIX_MIN:
|
||||
# 1–2 merely UNSUPPORTED claims don't justify a fix pass (it ran for 19/20 blocks,
|
||||
# 40 agent-minutes) — but a WRONG claim always does: one slipped through this
|
||||
# threshold and cost the guide 1.5 QA points
|
||||
_log(env.topic, f"Fakten-Gate {card['block']}: {len(claims)} Claim(s) unter Schwelle — kein Fix")
|
||||
claims = []
|
||||
if claims:
|
||||
_log(env.topic, f"Fakten-Gate {card['block']}: {len(claims)} unbelegte Claims → Fix")
|
||||
_log(env.topic, f"Fakten-Gate {card['block']}: {len(claims)} Claims ({len(falsch)} falsch) → Fix")
|
||||
fixp = env.slot(f"gatefix-{_safe(norm)}-r{card['writer_rounds']}.md")
|
||||
fixp.unlink(missing_ok=True)
|
||||
claims_text = "\n".join(f"- {c['text']}" + (f" ({c['grund']})" if c["grund"] else "")
|
||||
@@ -501,6 +510,18 @@ async def _stage_lesbarkeit(env: _Env, card: dict) -> bool:
|
||||
hints = await asyncio.to_thread(readability.rate_sections, {1: sec["md"]})
|
||||
if hints.get(1):
|
||||
problems.append(hints[1])
|
||||
# deterministic length trigger, same formula as the QA detector: prompt guidelines
|
||||
# alone left writers 2.7–4.1× over target — a measured overshoot forces the fix pass
|
||||
subs_all = env.subs_by_title.get(card["block"], [])
|
||||
n_rel = max(sum(1 for s in subs_all if s.get("relevance") != "peripheral"), 1)
|
||||
aus = re.split(r"<!--\s*ausführlich\s*-->", sec["md"], maxsplit=1)
|
||||
pro_sub = len(aus[1] if len(aus) == 2 else sec["md"]) / n_rel
|
||||
if not (GUIDE_LAENGE_MIN <= pro_sub <= GUIDE_LAENGE_MAX * 0.9):
|
||||
ziel = _writer_budget(len(subs_all))
|
||||
problems.append(
|
||||
f"Länge {round(pro_sub)} Zeichen/Sub (Rahmen {GUIDE_LAENGE_MIN}–{round(GUIDE_LAENGE_MAX * 0.9)}): "
|
||||
f"schreibe den ausführlich-Teil auf etwa {ziel} Zeichen GESAMT um — Sockel-Prosa und "
|
||||
f"Wiederholungen streichen, alle Sub-Marker und Beispiele behalten")
|
||||
if problems:
|
||||
from guide import _level_label
|
||||
subs = env.subs_by_title.get(card["block"], [])
|
||||
|
||||
Reference in New Issue
Block a user