diff --git a/backend/config.py b/backend/config.py index 8f2c357..5a0f644 100644 --- a/backend/config.py +++ b/backend/config.py @@ -22,11 +22,11 @@ TIMEOUTS = { "onepager_verify": (300, 0), } -# Auswahl-Auftrag je Format: (Mindest-Anteil, Mindestanzahl, Zweck). +# Auswahl-Auftrag je Format: (Mindest-Anteil, Maximal-Anteil, Mindestanzahl, Zweck). FORMAT_ANTEIL = { - "MiniGuide": (0.05, 8, "einen kompakten Anfänger-Guide — der schnelle Einstieg ins Thema"), - "Guide": (0.33, 20, "einen ausführlichen Anfänger-Guide — ein solides Fundament im Thema"), - "FullGuide": (0.90, 0, "einen Komplett-Guide — das ganze Thema"), + "MiniGuide": (0.05, 0.10, 8, "einen kompakten Anfänger-Guide — der schnelle Einstieg ins Thema"), + "Guide": (0.25, 0.35, 20, "einen ausführlichen Anfänger-Guide — ein solides Fundament im Thema"), + "FullGuide": (0.90, 1.00, 0, "einen Komplett-Guide — das ganze Thema"), } # Provider-Stacks: komplett unabhängig, einer kann jederzeit entfernt werden. diff --git a/backend/generator.py b/backend/generator.py index 0bb02ee..4c07d51 100644 --- a/backend/generator.py +++ b/backend/generator.py @@ -508,13 +508,13 @@ async def generate_bausteine(topic: str, instructions: str = "", provider: str = # Kleine Pakete vermeiden Lazy-Output bei langen Listen und begrenzen den Schaden # eines fehlgeschlagenen Writers. WRITER_SECTIONS = 30 -WRITER_MAX = 10 +WRITER_MAX = 20 -def _resolve_gliederung(data, entries: dict[int, str], soll: int) -> list[dict] | None: +def _resolve_gliederung(data, entries: dict[int, str], soll_min: int, soll_max: int) -> list[dict] | None: """{"kapitel": [{"titel", "bausteine": [Titel]}]} → [{"title", "nums"}]. - `soll` = Mindest-Anzahl gewählter Bausteine (mit kleiner Toleranz). + `soll_min`/`soll_max` = erlaubte Spanne gewählter Bausteine (mit kleiner Toleranz). """ if not isinstance(data, dict) or not isinstance(data.get("kapitel"), list): return None @@ -540,7 +540,7 @@ def _resolve_gliederung(data, entries: dict[int, str], soll: int) -> list[dict] return None if (total - unknown) / total < 0.85: return None - if len(seen) < 0.9 * soll: + if len(seen) < 0.9 * soll_min or len(seen) > 1.1 * soll_max: return None return chapters @@ -719,10 +719,11 @@ async def _generate_sections( spec = (TEMPLATES_DIR / "Format" / "Section.md").read_text(encoding="utf-8") bausteine_liste = "\n".join(f"- {t}" for t in entries.values()) n = len(entries) - anteil, minimum, zweck = FORMAT_ANTEIL[format_name] - k = min(n, max(minimum, math.ceil(anteil * n))) + anteil_min, anteil_max, minimum, zweck = FORMAT_ANTEIL[format_name] + k_min = min(n, max(minimum, math.ceil(anteil_min * n))) + k_max = min(n, max(k_min, math.floor(anteil_max * n))) auswahl_auftrag = ( - f"Wähle MINDESTENS {k} der Bausteine und baue daraus {zweck}. " + f"Wähle MINDESTENS {k_min} und HÖCHSTENS {k_max} der Bausteine und baue daraus {zweck}. " "Wähle, was diesem Zweck dient — lass weg, was dafür nicht nötig ist." ) @@ -738,7 +739,7 @@ async def _generate_sections( auswahl_auftrag=auswahl_auftrag, out_path=plan_path, extra=_extra(instructions), ), "role": "guide", "capabilities": "files", - "payload": (lambda result: _resolve_gliederung(_json_datei(plan_path), entries, k)), + "payload": (lambda result: _resolve_gliederung(_json_datei(plan_path), entries, k_min, k_max)), }] res = await _race(topic, "Gliederung", slots, 1, _timeout("plan", n), provider, cancelled=is_cancelled) if is_cancelled():