update
This commit is contained in:
193
backend/guide.py
193
backend/guide.py
@@ -41,13 +41,15 @@ log = logging.getLogger("creator.guide")
|
|||||||
|
|
||||||
GUIDE_STEPS = ("Gliederung", "Inhalte", "Inhalts-Check", "Schreiben", "Lese-Prüfung")
|
GUIDE_STEPS = ("Gliederung", "Inhalte", "Inhalts-Check", "Schreiben", "Lese-Prüfung")
|
||||||
|
|
||||||
# Writer skalieren mit der Section-Zahl: 1 Writer je ~30 Sections (gedeckelt).
|
|
||||||
# Kleine Pakete vermeiden Lazy-Output bei langen Listen und begrenzen den Schaden
|
|
||||||
# eines fehlgeschlagenen Writers.
|
|
||||||
# Inhalte/Inhalts-Check/Lese-Prüfung laufen in Paketen von ~GUIDE_CHUNK Bausteinen je Agent.
|
# Inhalte/Inhalts-Check/Lese-Prüfung laufen in Paketen von ~GUIDE_CHUNK Bausteinen je Agent.
|
||||||
# Nur der Writer (Schreiben) bleibt 1 Agent je Baustein (variable Längen, kein Kürzen).
|
# Nur der Writer (Schreiben) bleibt 1 Agent je Baustein (variable Längen, kein Kürzen, keine
|
||||||
|
# Längen-Angleichung zwischen Bausteinen).
|
||||||
GUIDE_CHUNK = 10
|
GUIDE_CHUNK = 10
|
||||||
|
|
||||||
|
# Prüf-Schritte als Panel: CHECK_PANEL Judges je Chunk, Section beanstandet bei Mehrheit.
|
||||||
|
# Ein einzelner Judge ist bias-/sampling-anfällig; ein kleines Panel ist stabiler.
|
||||||
|
CHECK_PANEL = 3
|
||||||
|
|
||||||
# Lese-Prüfung: nur EINE Runde (Check + Fix). Folgerunden brachten kaum Mehrwert
|
# Lese-Prüfung: nur EINE Runde (Check + Fix). Folgerunden brachten kaum Mehrwert
|
||||||
# (1 Agent je Baustein prüft ohnehin fein), kosten aber extra Agenten.
|
# (1 Agent je Baustein prüft ohnehin fein), kosten aber extra Agenten.
|
||||||
LESE_RUNDEN = 1
|
LESE_RUNDEN = 1
|
||||||
@@ -134,10 +136,10 @@ def _reset_fertig(content_path: Path, step: int) -> None:
|
|||||||
|
|
||||||
# Slot-Datei-Globs je Schritt (Index = GUIDE_STEPS). Stem-verankert, kollisionsfrei.
|
# Slot-Datei-Globs je Schritt (Index = GUIDE_STEPS). Stem-verankert, kollisionsfrei.
|
||||||
_STEP_GLOBS = (
|
_STEP_GLOBS = (
|
||||||
("gliederung*",), # 0 Gliederung (inkl. Auswahl-Filter)
|
("gliederung*",), # 0 Gliederung (inkl. Auswahl-Filter)
|
||||||
("inhalt-chunk-*",), # 1 Inhalte
|
("inhalt-chunk-*", "inhalt-nach-*"), # 1 Inhalte (inkl. Nachrunde)
|
||||||
("inhalt-check-*", "inhalt-fix-*"), # 2 Inhalts-Check
|
("inhalt-check-*", "inhalt-fix-*"), # 2 Inhalts-Check
|
||||||
("chunk-*",), # 3 Schreiben
|
("chunk-*",), # 3 Schreiben (chunk-* matcht auch chunk-nach-*)
|
||||||
("lese-check-*", "fix-r*"), # 4 Lese-Prüfung
|
("lese-check-*", "fix-r*"), # 4 Lese-Prüfung
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -171,6 +173,31 @@ def _lese_probleme_schema(data):
|
|||||||
return out or None
|
return out or None
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_probleme(judge_paths: list[Path], geltung: set[int], idx: dict[str, int]) -> dict[int, str]:
|
||||||
|
"""Panel-Aggregation: mehrere Judge-Outputs eines Chunks → beanstandete {num: problem}.
|
||||||
|
|
||||||
|
Eine Stimme je Judge, der eine Section nennt. Beanstandet, wenn > Hälfte der
|
||||||
|
GELIEFERTEN (valid geparsten) Judges sie nennt (3→≥2, 2→≥2, 1→≥1). Robust gegen
|
||||||
|
Einzel-Ausfall: fehlende Dateien zählen nicht mit. Problem-Text vom erstnennenden Judge.
|
||||||
|
"""
|
||||||
|
outputs = [p for p in (_lese_probleme_schema(_json_datei(j)) for j in judge_paths) if p is not None]
|
||||||
|
if not outputs:
|
||||||
|
return {}
|
||||||
|
votes: dict[int, int] = {}
|
||||||
|
problem: dict[int, str] = {}
|
||||||
|
for out in outputs:
|
||||||
|
gesehen: set[int] = set()
|
||||||
|
for item in out:
|
||||||
|
num = _titel_aufloesen(idx, item["section"])
|
||||||
|
if num is None or num not in geltung or num in gesehen:
|
||||||
|
continue
|
||||||
|
gesehen.add(num)
|
||||||
|
votes[num] = votes.get(num, 0) + 1
|
||||||
|
problem.setdefault(num, item["problem"])
|
||||||
|
schwelle = len(outputs) / 2
|
||||||
|
return {num: problem[num] for num, v in votes.items() if v > schwelle}
|
||||||
|
|
||||||
|
|
||||||
def _resolve_gliederung(data, entries: dict[int, str], soll_min: int, soll_max: int) -> list[dict] | None:
|
def _resolve_gliederung(data, entries: dict[int, str], soll_min: int, soll_max: int) -> list[dict] | None:
|
||||||
"""{"kapitel": [{"titel", "nummern": [1, 3, 7]}]} → [{"title", "nums"}].
|
"""{"kapitel": [{"titel", "nummern": [1, 3, 7]}]} → [{"title", "nums"}].
|
||||||
|
|
||||||
@@ -385,40 +412,80 @@ async def _generate_sections(
|
|||||||
if not inhalt_by_num:
|
if not inhalt_by_num:
|
||||||
await _fail(guide_id, "Keine Inhalte identifiziert")
|
await _fail(guide_id, "Keine Inhalte identifiziert")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Nachrunde: fehlende Bausteine (Chunk-Ausfall oder Lazy-Output) gezielt nachziehen — eine Runde.
|
||||||
|
geplant_nums = [num for ch in plan for num in ch["nums"]]
|
||||||
|
fehlend = [num for num in geplant_nums if num not in inhalt_by_num]
|
||||||
|
if fehlend:
|
||||||
|
_log(topic, f"Inhalte: {len(fehlend)} Baustein(e) fehlen — Nachrunde…")
|
||||||
|
nach_chunks = [[{"title": "Weitere", "nums": fehlend[k:k + GUIDE_CHUNK]}] for k in range(0, len(fehlend), GUIDE_CHUNK)]
|
||||||
|
nach_paths = [content_path.parent / f"{content_path.stem}.inhalt-nach-{k}.md" for k in range(1, len(nach_chunks) + 1)]
|
||||||
|
nach_offen = [k for k, p in enumerate(nach_paths) if not p.exists()]
|
||||||
|
if nach_offen:
|
||||||
|
async def melde_n(d, t): await _set_step(guide_id, 1, f"Sammle fehlende Inhalte {d}/{t}…")
|
||||||
|
await _gather_fortschritt([
|
||||||
|
run_agent(
|
||||||
|
f"{guide_id}-inhalt-nach-{k + 1}",
|
||||||
|
_prompt(
|
||||||
|
"Guide-Inhalt",
|
||||||
|
topic=topic, zuteilung=_zuteilung_subs(nach_chunks[k], entries, subs_by_titel),
|
||||||
|
facts=facts, out_path=nach_paths[k], extra=_extra(instructions),
|
||||||
|
),
|
||||||
|
_timeout("inhalt", len(nach_chunks[k][0]["nums"])), provider=provider, role="guide", capabilities="full",
|
||||||
|
)
|
||||||
|
for k in nach_offen
|
||||||
|
], len(nach_chunks), melde_n, start=len(nach_chunks) - len(nach_offen))
|
||||||
|
if is_cancelled():
|
||||||
|
return None
|
||||||
|
for p in nach_paths:
|
||||||
|
if not p.exists():
|
||||||
|
continue
|
||||||
|
for sec in _parse_fragment(p.read_text(encoding="utf-8")):
|
||||||
|
num = _titel_aufloesen(idx, sec["titel"])
|
||||||
|
if num is not None and num not in inhalt_by_num and sec["md"].strip():
|
||||||
|
inhalt_by_num[num] = sec["md"]
|
||||||
|
|
||||||
if all(p.exists() for p in inhalt_paths):
|
if all(p.exists() for p in inhalt_paths):
|
||||||
_set_fertig(content_path, 1) # Inhalte vollständig
|
_set_fertig(content_path, 1) # Inhalte vollständig
|
||||||
|
|
||||||
inhalt_chunk_nums = [[num for ch in chunk for num in ch["nums"] if num in inhalt_by_num] for chunk in chunks]
|
inhalt_chunk_nums = [[num for ch in chunk for num in ch["nums"] if num in inhalt_by_num] for chunk in chunks]
|
||||||
|
|
||||||
# Schritt 3: Inhalte prüfen (pro Chunk ein Check) + beanstandete einmal überarbeiten.
|
# Schritt 3: Inhalte prüfen — CHECK_PANEL Judges je Chunk, Mehrheit beanstandet.
|
||||||
check_paths = [content_path.parent / f"{content_path.stem}.inhalt-check-{i}.json" for i in range(1, writer_count + 1)]
|
# + beanstandete einmal überarbeiten. Resume: nur fehlende Judge-Dateien neu starten.
|
||||||
offen_checks = [i for i, p in enumerate(check_paths) if inhalt_chunk_nums[i] and _lese_probleme_schema(_json_datei(p)) is None]
|
check_judge_paths = [
|
||||||
if offen_checks:
|
[content_path.parent / f"{content_path.stem}.inhalt-check-{i}-j{j}.json" for j in range(1, CHECK_PANEL + 1)]
|
||||||
|
for i in range(1, writer_count + 1)
|
||||||
|
]
|
||||||
|
offen_slots = [
|
||||||
|
(i, j) for i in range(writer_count) if inhalt_chunk_nums[i]
|
||||||
|
for j in range(CHECK_PANEL) if _lese_probleme_schema(_json_datei(check_judge_paths[i][j])) is None
|
||||||
|
]
|
||||||
|
if offen_slots:
|
||||||
await _set_step(guide_id, 2, "Prüfe Inhalte…")
|
await _set_step(guide_id, 2, "Prüfe Inhalte…")
|
||||||
|
sections_je_chunk = {
|
||||||
|
i: "\n\n".join(f"SECTION: {_titel(entries[num])}\n{inhalt_by_num[num]}" for num in inhalt_chunk_nums[i])
|
||||||
|
for i, _ in offen_slots
|
||||||
|
}
|
||||||
slots = [{
|
slots = [{
|
||||||
"key": f"{guide_id}-inhalt-check-{i + 1}",
|
"key": f"{guide_id}-inhalt-check-{i + 1}-j{j + 1}",
|
||||||
"prompt": _prompt(
|
"prompt": _prompt(
|
||||||
"Guide-Inhalt-Check",
|
"Guide-Inhalt-Check",
|
||||||
topic=topic, format_name=format_name,
|
topic=topic, format_name=format_name, sections=sections_je_chunk[i],
|
||||||
sections="\n\n".join(f"SECTION: {_titel(entries[num])}\n{inhalt_by_num[num]}" for num in inhalt_chunk_nums[i]),
|
out_path=check_judge_paths[i][j], extra=_extra(instructions),
|
||||||
out_path=check_paths[i], extra=_extra(instructions),
|
|
||||||
),
|
),
|
||||||
"role": "judge", "capabilities": "files",
|
"role": "judge", "capabilities": "files",
|
||||||
"payload": (lambda result, p=check_paths[i]: _lese_probleme_schema(_json_datei(p))),
|
"payload": (lambda result, p=check_judge_paths[i][j]: _lese_probleme_schema(_json_datei(p))),
|
||||||
} for i in offen_checks]
|
} for i, j in offen_slots]
|
||||||
n_checks = len(slots)
|
n_checks = len(slots)
|
||||||
upd = lambda n: asyncio.create_task(_set_step(guide_id, 2, f"Prüfe Inhalte {n}/{n_checks}…"))
|
upd = lambda n: asyncio.create_task(_set_step(guide_id, 2, f"Prüfe Inhalte {n}/{n_checks}…"))
|
||||||
await _race(topic, "Inhalts-Prüfung", slots, len(slots), _timeout("inhalt_check", max(chunk_sizes)), provider, on_update=upd, cancelled=is_cancelled)
|
await _race(topic, "Inhalts-Prüfung", slots, len(slots), _timeout("inhalt_check", max(chunk_sizes)), provider, on_update=upd, cancelled=is_cancelled, grace=KONSENS_GRACE)
|
||||||
if is_cancelled():
|
if is_cancelled():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
probleme_by_num: dict[int, str] = {}
|
probleme_by_num: dict[int, str] = {}
|
||||||
for i, p in enumerate(check_paths):
|
for i in range(writer_count):
|
||||||
geltung = set(inhalt_chunk_nums[i])
|
if inhalt_chunk_nums[i]:
|
||||||
for item in (_lese_probleme_schema(_json_datei(p)) or []):
|
probleme_by_num.update(_panel_probleme(check_judge_paths[i], set(inhalt_chunk_nums[i]), idx))
|
||||||
num = _titel_aufloesen(idx, item["section"])
|
|
||||||
if num in geltung and num in inhalt_by_num and num not in probleme_by_num:
|
|
||||||
probleme_by_num[num] = item["problem"]
|
|
||||||
|
|
||||||
if probleme_by_num:
|
if probleme_by_num:
|
||||||
_log(topic, f"Inhalts-Prüfung: {len(probleme_by_num)} Baustein(e) beanstandet")
|
_log(topic, f"Inhalts-Prüfung: {len(probleme_by_num)} Baustein(e) beanstandet")
|
||||||
@@ -473,9 +540,9 @@ async def _generate_sections(
|
|||||||
"Guide-Writer",
|
"Guide-Writer",
|
||||||
topic=topic, format_name=format_name, zuteilung=w_zuteil[i],
|
topic=topic, format_name=format_name, zuteilung=w_zuteil[i],
|
||||||
inhalte=inhalte_text(w_chunks[i]),
|
inhalte=inhalte_text(w_chunks[i]),
|
||||||
facts=facts, spec=spec, out_path=paths[i], extra=_extra(instructions),
|
spec=spec, out_path=paths[i], extra=_extra(instructions),
|
||||||
),
|
),
|
||||||
_timeout("writer", 1), provider=provider, role="guide", capabilities="full",
|
_timeout("writer", 1), provider=provider, role="guide", capabilities="files",
|
||||||
)
|
)
|
||||||
for i in offen
|
for i in offen
|
||||||
], len(w_chunks), melde, start=len(w_chunks) - len(offen))
|
], len(w_chunks), melde, start=len(w_chunks) - len(offen))
|
||||||
@@ -505,6 +572,38 @@ async def _generate_sections(
|
|||||||
if not by_num:
|
if not by_num:
|
||||||
await _fail(guide_id, "Keine Sections in der Writer-Ausgabe gefunden")
|
await _fail(guide_id, "Keine Sections in der Writer-Ausgabe gefunden")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Nachrunde: fehlende Sections (Writer-Ausfall) gezielt nachschreiben — eine Runde.
|
||||||
|
nach_fehlend = [num for num in geplant_nums if num not in by_num]
|
||||||
|
if nach_fehlend:
|
||||||
|
_log(topic, f"Schreiben: {len(nach_fehlend)} Section(s) fehlen — Nachrunde…")
|
||||||
|
nw_chunks = [[{"title": "Weitere", "nums": [num]}] for num in nach_fehlend]
|
||||||
|
nw_paths = [content_path.parent / f"{content_path.stem}.chunk-nach-{k}.md" for k in range(1, len(nw_chunks) + 1)]
|
||||||
|
nw_offen = [k for k, p in enumerate(nw_paths) if not p.exists()]
|
||||||
|
if nw_offen:
|
||||||
|
async def melde_nw(d, t): await _set_step(guide_id, 3, f"Schreibe fehlende Sections {d}/{t}…")
|
||||||
|
await _gather_fortschritt([
|
||||||
|
run_agent(
|
||||||
|
f"{guide_id}-w-nach-{k + 1}",
|
||||||
|
_prompt(
|
||||||
|
"Guide-Writer",
|
||||||
|
topic=topic, format_name=format_name, zuteilung=_zuteilung_subs(nw_chunks[k], entries, subs_by_titel),
|
||||||
|
inhalte=inhalte_text(nw_chunks[k]), spec=spec, out_path=nw_paths[k], extra=_extra(instructions),
|
||||||
|
),
|
||||||
|
_timeout("writer", 1), provider=provider, role="guide", capabilities="files",
|
||||||
|
)
|
||||||
|
for k in nw_offen
|
||||||
|
], len(nw_chunks), melde_nw, start=len(nw_chunks) - len(nw_offen))
|
||||||
|
if is_cancelled():
|
||||||
|
return None
|
||||||
|
for p in nw_paths:
|
||||||
|
if not p.exists():
|
||||||
|
continue
|
||||||
|
for sec in _parse_fragment(p.read_text(encoding="utf-8")):
|
||||||
|
num = _titel_aufloesen(idx, sec["titel"])
|
||||||
|
if num is not None and num not in by_num and sec["md"].strip():
|
||||||
|
by_num[num] = sec
|
||||||
|
|
||||||
if all(p.exists() for p in paths):
|
if all(p.exists() for p in paths):
|
||||||
_set_fertig(content_path, 3) # Schreiben vollständig
|
_set_fertig(content_path, 3) # Schreiben vollständig
|
||||||
|
|
||||||
@@ -524,39 +623,41 @@ async def _generate_sections(
|
|||||||
|
|
||||||
scope = chunk_nums
|
scope = chunk_nums
|
||||||
for runde in range(1, LESE_RUNDEN + 1):
|
for runde in range(1, LESE_RUNDEN + 1):
|
||||||
check_paths = [content_path.parent / f"{content_path.stem}.lese-check-r{runde}-{i}.json" for i in range(1, writer_count + 1)]
|
# CHECK_PANEL Judges je Paket; Mehrheit beanstandet. Aggregation robust gegen Einzel-Ausfall.
|
||||||
offen_checks = [i for i, p in enumerate(check_paths) if scope[i] and _lese_probleme_schema(_json_datei(p)) is None]
|
check_judge_paths = [
|
||||||
if offen_checks:
|
[content_path.parent / f"{content_path.stem}.lese-check-r{runde}-{i}-j{j}.json" for j in range(1, CHECK_PANEL + 1)]
|
||||||
|
for i in range(1, writer_count + 1)
|
||||||
|
]
|
||||||
|
offen_slots = [
|
||||||
|
(i, j) for i in range(writer_count) if scope[i]
|
||||||
|
for j in range(CHECK_PANEL) if _lese_probleme_schema(_json_datei(check_judge_paths[i][j])) is None
|
||||||
|
]
|
||||||
|
if offen_slots:
|
||||||
await _set_step(guide_id, 4, "Prüfe Lesbarkeit…")
|
await _set_step(guide_id, 4, "Prüfe Lesbarkeit…")
|
||||||
|
sections_je_chunk = {i: sections_text(scope[i]) for i, _ in offen_slots}
|
||||||
slots = [{
|
slots = [{
|
||||||
"key": f"{guide_id}-lese-check-r{runde}-{i + 1}",
|
"key": f"{guide_id}-lese-check-r{runde}-{i + 1}-j{j + 1}",
|
||||||
"prompt": _prompt(
|
"prompt": _prompt(
|
||||||
"Guide-Lese-Check",
|
"Guide-Lese-Check",
|
||||||
topic=topic, format_name=format_name, spec=spec,
|
topic=topic, format_name=format_name, spec=spec,
|
||||||
sections=sections_text(scope[i]),
|
sections=sections_je_chunk[i],
|
||||||
out_path=check_paths[i], extra=_extra(instructions),
|
out_path=check_judge_paths[i][j], extra=_extra(instructions),
|
||||||
),
|
),
|
||||||
"role": "judge", "capabilities": "files",
|
"role": "judge", "capabilities": "files",
|
||||||
"payload": (lambda result, p=check_paths[i]: _lese_probleme_schema(_json_datei(p))),
|
"payload": (lambda result, p=check_judge_paths[i][j]: _lese_probleme_schema(_json_datei(p))),
|
||||||
} for i in offen_checks]
|
} for i, j in offen_slots]
|
||||||
n_checks = len(slots)
|
n_checks = len(slots)
|
||||||
upd = lambda n: asyncio.create_task(_set_step(guide_id, 4, f"Prüfe Lesbarkeit {n}/{n_checks}…"))
|
upd = lambda n: asyncio.create_task(_set_step(guide_id, 4, f"Prüfe Lesbarkeit {n}/{n_checks}…"))
|
||||||
res = await _race(topic, f"Lese-Prüfung r{runde}", slots, len(slots), _timeout("lese_check", max(chunk_sizes)), provider, on_update=upd, cancelled=is_cancelled)
|
res = await _race(topic, f"Lese-Prüfung r{runde}", slots, len(slots), _timeout("lese_check", max(chunk_sizes)), provider, on_update=upd, cancelled=is_cancelled, grace=KONSENS_GRACE)
|
||||||
if is_cancelled():
|
if is_cancelled():
|
||||||
return None
|
return None
|
||||||
if res is None:
|
if res is None:
|
||||||
# Nicht hart failen: bei 1 Agent je Baustein darf ein einzelner Check-Ausfall
|
_log(topic, f"Lese-Prüfung Runde {runde}: kein volles Quorum — vorhandene Judges aggregiert")
|
||||||
# nicht den ganzen Guide kippen. Sections sind geschrieben; Lese-Prüfung ist Politur.
|
|
||||||
_log(topic, f"Lese-Prüfung Runde {runde} ohne vollständiges Ergebnis — Stand bleibt")
|
|
||||||
break
|
|
||||||
|
|
||||||
probleme_by_num: dict[int, str] = {}
|
probleme_by_num: dict[int, str] = {}
|
||||||
for i, p in enumerate(check_paths):
|
for i in range(writer_count):
|
||||||
geltung = set(scope[i])
|
if scope[i]:
|
||||||
for item in (_lese_probleme_schema(_json_datei(p)) or []):
|
probleme_by_num.update(_panel_probleme(check_judge_paths[i], set(scope[i]), idx))
|
||||||
num = _titel_aufloesen(idx, item["section"])
|
|
||||||
if num in geltung and num in by_num and num not in probleme_by_num:
|
|
||||||
probleme_by_num[num] = item["problem"]
|
|
||||||
|
|
||||||
# Deterministisches Lesbarkeits-Gate: zu schwere Sections in dieselbe
|
# Deterministisches Lesbarkeits-Gate: zu schwere Sections in dieselbe
|
||||||
# Überarbeitung einreihen (LLM-Beanstandung hat Vorrang). Gate aus → no-op.
|
# Überarbeitung einreihen (LLM-Beanstandung hat Vorrang). Gate aus → no-op.
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ Schreibe Sections für einen Lern-Guide zum Thema "{topic}" (Format: {format_nam
|
|||||||
Dir zugeteilt sind folgende Kapitel und Bausteine — verbindlich: jede zugeteilte Section muss vorkommen, keine zusätzlichen erfinden. Unter jedem Baustein stehen seine SUBBAUSTEINE mit Stufe (`[einfach]`/`[mittel]`/`[schwer]`):
|
Dir zugeteilt sind folgende Kapitel und Bausteine — verbindlich: jede zugeteilte Section muss vorkommen, keine zusätzlichen erfinden. Unter jedem Baustein stehen seine SUBBAUSTEINE mit Stufe (`[einfach]`/`[mittel]`/`[schwer]`):
|
||||||
{zuteilung}
|
{zuteilung}
|
||||||
|
|
||||||
{facts}
|
|
||||||
|
|
||||||
Das Format ({format_name}) beeinflusst NICHT, wie ein Baustein geschrieben wird — nur, wie viele Bausteine das Thema abdecken. Derselbe Baustein bekommt in MiniGuide, Guide und FullGuide denselben Text.
|
Das Format ({format_name}) beeinflusst NICHT, wie ein Baustein geschrieben wird — nur, wie viele Bausteine das Thema abdecken. Derselbe Baustein bekommt in MiniGuide, Guide und FullGuide denselben Text.
|
||||||
|
|
||||||
Jeder Baustein bekommt ZWEI Fassungen mit DENSELBEN Inhalten (dieselben Subbausteine):
|
Jeder Baustein bekommt ZWEI Fassungen mit DENSELBEN Inhalten (dieselben Subbausteine):
|
||||||
@@ -24,7 +22,7 @@ SO SCHREIBST DU die ausführliche Fassung — EIN zusammenhängender Text für e
|
|||||||
GEPRÜFTE INHALTE je Baustein — das ist verbindlich, was gelehrt werden muss:
|
GEPRÜFTE INHALTE je Baustein — das ist verbindlich, was gelehrt werden muss:
|
||||||
{inhalte}
|
{inhalte}
|
||||||
|
|
||||||
Nutze diese Inhalte. Erfinde nichts dazu, lass nichts Wesentliches weg. Lehre simpel: kurze Sätze, Listen für Aufzählungen, ein Anfänger versteht es sofort.
|
Recherchiere NICHT und suche NICHT im Web. Alle nötigen Fakten stehen in diesen geprüften Inhalten — nutze ausschließlich sie. Erfinde nichts dazu, lass nichts Wesentliches weg. Lehre simpel: kurze Sätze, Listen für Aufzählungen, ein Anfänger versteht es sofort.
|
||||||
|
|
||||||
SECTION-SPEZIFIKATION (gilt je Baustein):
|
SECTION-SPEZIFIKATION (gilt je Baustein):
|
||||||
{spec}
|
{spec}
|
||||||
|
|||||||
Reference in New Issue
Block a user