update
This commit is contained in:
119
backend/guide.py
119
backend/guide.py
@@ -38,7 +38,7 @@ from textkit import (
|
||||
|
||||
log = logging.getLogger("creator.guide")
|
||||
|
||||
GUIDE_STEPS = ("Auswahl", "Gliederung", "Schreiben", "Lese-Prüfung")
|
||||
GUIDE_STEPS = ("Auswahl", "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
|
||||
@@ -455,22 +455,128 @@ async def _generate_sections(
|
||||
await _fail(guide_id, "Gliederung fehlgeschlagen (Judge ohne gültiges Ergebnis)")
|
||||
return None
|
||||
|
||||
# Schritt 2: Schreiben — vorhandene Chunk-Dateien werden übernommen (Resume)
|
||||
# Chunks festlegen — Inhalte, Inhalts-Prüfung, Writer und Lese-Check nutzen
|
||||
# dieselbe Aufteilung: 1 Agent je ~30 Bausteine (gedeckelt).
|
||||
total_sections = sum(len(c["nums"]) for c in plan)
|
||||
chunks = _split_chunks(plan, min(WRITER_MAX, max(1, math.ceil(total_sections / WRITER_SECTIONS))))
|
||||
zuteilungen = [_zuteilung_text(chunk, entries) for chunk in chunks]
|
||||
chunk_sizes = [sum(len(c["nums"]) for c in chunk) for chunk in chunks]
|
||||
writer_count = len(zuteilungen)
|
||||
writer_count = len(chunks)
|
||||
idx = _titel_index(entries)
|
||||
|
||||
# Schritt 2: Inhalte je Baustein identifizieren — pro Chunk ein Agent (Marker-Output, Resume).
|
||||
inhalt_paths = [content_path.parent / f"{content_path.stem}.inhalt-chunk-{i}.md" for i in range(1, writer_count + 1)]
|
||||
offen = [i for i, p in enumerate(inhalt_paths) if not p.exists()]
|
||||
if offen:
|
||||
await _set_step(guide_id, 2, f"Sammle Inhalte ({writer_count} Agenten)…" if writer_count > 1 else "Sammle Inhalte…")
|
||||
results = await asyncio.gather(*[
|
||||
run_agent(
|
||||
f"{guide_id}-inhalt-{i + 1}",
|
||||
_prompt(
|
||||
"Guide-Inhalt",
|
||||
topic=topic, zuteilung=zuteilungen[i], facts=facts,
|
||||
out_path=inhalt_paths[i], extra=_extra(instructions),
|
||||
),
|
||||
_timeout("inhalt", chunk_sizes[i]), provider=provider, role="guide", capabilities="full",
|
||||
)
|
||||
for i in offen
|
||||
], return_exceptions=True)
|
||||
if is_cancelled():
|
||||
return None
|
||||
if not any(p.exists() for p in inhalt_paths):
|
||||
await _fail(guide_id, _gather_error("Inhalts-Fehler", list(results)))
|
||||
return None
|
||||
|
||||
inhalt_by_num: dict[int, str] = {}
|
||||
for p in inhalt_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 not inhalt_by_num:
|
||||
await _fail(guide_id, "Keine Inhalte identifiziert")
|
||||
return None
|
||||
|
||||
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.
|
||||
check_paths = [content_path.parent / f"{content_path.stem}.inhalt-check-{i}.json" for i in range(1, writer_count + 1)]
|
||||
offen_checks = [i for i, p in enumerate(check_paths) if inhalt_chunk_nums[i] and _lese_probleme_schema(_json_datei(p)) is None]
|
||||
if offen_checks:
|
||||
await _set_step(guide_id, 3, "Prüfe Inhalte…")
|
||||
slots = [{
|
||||
"key": f"{guide_id}-inhalt-check-{i + 1}",
|
||||
"prompt": _prompt(
|
||||
"Guide-Inhalt-Check",
|
||||
topic=topic, format_name=format_name,
|
||||
sections="\n\n".join(f"SECTION: {_titel(entries[num])}\n{inhalt_by_num[num]}" for num in inhalt_chunk_nums[i]),
|
||||
out_path=check_paths[i], extra=_extra(instructions),
|
||||
),
|
||||
"role": "judge", "capabilities": "files",
|
||||
"payload": (lambda result, p=check_paths[i]: _lese_probleme_schema(_json_datei(p))),
|
||||
} for i in offen_checks]
|
||||
await _race(topic, "Inhalts-Prüfung", slots, len(slots), _timeout("inhalt_check", max(chunk_sizes)), provider, cancelled=is_cancelled)
|
||||
if is_cancelled():
|
||||
return None
|
||||
|
||||
probleme_by_num: dict[int, str] = {}
|
||||
for i, p in enumerate(check_paths):
|
||||
geltung = set(inhalt_chunk_nums[i])
|
||||
for item in (_lese_probleme_schema(_json_datei(p)) or []):
|
||||
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:
|
||||
_log(topic, f"Inhalts-Prüfung: {len(probleme_by_num)} Baustein(e) beanstandet")
|
||||
await _set_step(guide_id, 3, f"Überarbeite {len(probleme_by_num)} Inhalt(e)…")
|
||||
fix_chunks = [[num for num in nums if num in probleme_by_num] for nums in inhalt_chunk_nums]
|
||||
fix_paths = [content_path.parent / f"{content_path.stem}.inhalt-fix-{i + 1}.md" for i in range(writer_count)]
|
||||
fix_offen = [i for i, nums in enumerate(fix_chunks) if nums and not fix_paths[i].exists()]
|
||||
results = await asyncio.gather(*[
|
||||
run_agent(
|
||||
f"{guide_id}-inhalt-fix-{i + 1}",
|
||||
_prompt(
|
||||
"Guide-Inhalt-Fix",
|
||||
topic=topic, facts=facts,
|
||||
auftraege="\n\n".join(
|
||||
f"SECTION: {_titel(entries[num])}\nPROBLEM: {probleme_by_num[num]}\nAKTUELL:\n{inhalt_by_num[num]}"
|
||||
for num in fix_chunks[i]
|
||||
),
|
||||
out_path=fix_paths[i], extra=_extra(instructions),
|
||||
),
|
||||
_timeout("inhalt", len(fix_chunks[i])), provider=provider, role="guide", capabilities="full",
|
||||
)
|
||||
for i in fix_offen
|
||||
], return_exceptions=True)
|
||||
if is_cancelled():
|
||||
return None
|
||||
for p in fix_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 in probleme_by_num and sec["md"].strip():
|
||||
inhalt_by_num[num] = sec["md"]
|
||||
|
||||
# Schritt 4: Schreiben — Writer formuliert die geprüften Inhalte aus (Resume).
|
||||
def inhalte_text(chunk) -> str:
|
||||
nums = [num for ch in chunk for num in ch["nums"] if num in inhalt_by_num]
|
||||
return "\n\n".join(f"<!-- section: {_titel(entries[num])} -->\n{inhalt_by_num[num]}" for num in nums)
|
||||
|
||||
paths = [content_path.parent / f"{content_path.stem}.chunk-{i}.md" for i in range(1, writer_count + 1)]
|
||||
offen = [i for i, p in enumerate(paths) if not p.exists()]
|
||||
if offen:
|
||||
await _set_step(guide_id, 2, f"Schreibe Sections ({writer_count} Writer)…" if writer_count > 1 else "Schreibe Sections…")
|
||||
await _set_step(guide_id, 4, f"Schreibe Sections ({writer_count} Writer)…" if writer_count > 1 else "Schreibe Sections…")
|
||||
results = await asyncio.gather(*[
|
||||
run_agent(
|
||||
f"{guide_id}-w{i + 1}",
|
||||
_prompt(
|
||||
"Guide-Writer",
|
||||
topic=topic, format_name=format_name, zuteilung=zuteilungen[i],
|
||||
inhalte=inhalte_text(chunks[i]),
|
||||
facts=facts, spec=spec, out_path=paths[i], extra=_extra(instructions),
|
||||
),
|
||||
_timeout("writer", chunk_sizes[i]), provider=provider, role="guide", capabilities="full",
|
||||
@@ -490,7 +596,6 @@ async def _generate_sections(
|
||||
await _fail(guide_id, _gather_error("Writer-Fehler", list(results)))
|
||||
return None
|
||||
|
||||
idx = _titel_index(entries)
|
||||
by_num: dict[int, dict] = {}
|
||||
for p in paths:
|
||||
if not p.exists():
|
||||
@@ -524,7 +629,7 @@ async def _generate_sections(
|
||||
check_paths = [content_path.parent / f"{content_path.stem}.lese-check-r{runde}-{i}.json" for i in range(1, writer_count + 1)]
|
||||
offen_checks = [i for i, p in enumerate(check_paths) if scope[i] and _lese_probleme_schema(_json_datei(p)) is None]
|
||||
if offen_checks:
|
||||
await _set_step(guide_id, 3, f"Prüfe Lesbarkeit (Runde {runde}/{KONSENS_MAX_RUNDEN})…")
|
||||
await _set_step(guide_id, 5, f"Prüfe Lesbarkeit (Runde {runde}/{KONSENS_MAX_RUNDEN})…")
|
||||
slots = [{
|
||||
"key": f"{guide_id}-lese-check-r{runde}-{i + 1}",
|
||||
"prompt": _prompt(
|
||||
@@ -557,7 +662,7 @@ async def _generate_sections(
|
||||
break
|
||||
|
||||
_log(topic, f"Lese-Prüfung Runde {runde}: {len(probleme_by_num)} Section(s) beanstandet")
|
||||
await _set_step(guide_id, 3, f"Überarbeite {len(probleme_by_num)} Section(s) (Runde {runde})…")
|
||||
await _set_step(guide_id, 5, f"Überarbeite {len(probleme_by_num)} Section(s) (Runde {runde})…")
|
||||
fix_chunks = [[num for num in nums if num in probleme_by_num] for nums in chunk_nums]
|
||||
fix_paths = [content_path.parent / f"{content_path.stem}.fix-r{runde}-{i + 1}.md" for i in range(writer_count)]
|
||||
fix_offen = [i for i, nums in enumerate(fix_chunks) if nums and not fix_paths[i].exists()]
|
||||
|
||||
Reference in New Issue
Block a user