Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions generative/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@
MAX_CHUNKS_SHORT_DOC = 10
MAX_PAGES_SHORT_DOC = 50

# Outline-first Kapitel-Split (#345). Kalibriert an 5 Bestands-Büchern (Plan v4,
# empirisch 24/24) — NICHT gegen ein Gold-Set validiert, ENV nur als Escape-Hatch.
# Sanity-Obergrenze Outline-Hauptkapitel: darüber ist es wahrscheinlich ein
# flach-gegliederter Sammelband (>60 L1-Einträge im Stichproben-Bestand) → Normalpfad.
MAX_SANE_OUTLINE_CHAPTERS = int(os.getenv("ATOMIC_AGENT_MAX_SANE_OUTLINE_CHAPTERS", "60"))
# Median-Segmentgröße-Guard: liegt der Median der Kapiteltexte darunter, ist der
# Outline-Split zu Slivern degeneriert (Titel-Marker-Fehlmap) → Normalpfad.
MIN_CHAPTER_SEGMENT_WORDS = int(os.getenv("ATOMIC_AGENT_MIN_CHAPTER_SEGMENT_WORDS", "400"))

# Backlog: nicht verdrahtet — Einlösung = Kosten-Cap-Feature, Maintainer-Entscheid
MAX_TOKENS_PER_RUN = 500_000

Expand Down
21 changes: 19 additions & 2 deletions generative/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2228,7 +2228,9 @@ def _run_extraction_stages(
words_per_page=text_quality.words_per_page if text_quality.is_thin else None,
)
)
chunks = pdf_chunker.split_by_chapters(text)
# #345: Outline-first Kapitel-Split — Kapitelgrenze = validierte PDF-Lesezeichen-
# Zielseite. Ohne nutzbare Outline exakt das bisherige (heuristische) Verhalten.
chunks = pdf_chunker.split_by_chapters(text, pdf_path=source_path)
pdf_meta_early = pdf_chunker.pdf_metadata(source_path) or {}
try:
source_pages = int(pdf_meta_early.get("Pages") or 0)
Expand All @@ -2245,6 +2247,19 @@ def _run_extraction_stages(
)
chunks = pdf_chunker._split_by_words(text)
print(f" {len(chunks)} Chunks")
# #345: --by-chapter auf einem Dokument OHNE nutzbare Outline degeneriert zu
# Hunderten Mikro-„Kapiteln" (jedes ein Planner-Call) — das war der 15,5-h-/
# 32-Mio-Token-/0-Notes-Lauf. Statt zu warnen und loszulaufen: hart abbrechen,
# sobald der Split KEIN Outline-Split ist und die Chunk-Zahl über die Schwelle
# geht. Ein echter Outline-Split (source="outline") passiert diesen Guard.
_split_source = getattr(chunks[0], "source", None) if chunks else None
if getattr(args, "by_chapter", False) and len(chunks) > LARGE_DOC_THRESHOLD and _split_source != "outline":
sys.exit(
f"[FEHLER] --by-chapter: {len(chunks)} Segmente ohne nutzbare PDF-Outline "
f"(Split-Quelle: {_split_source}) — das ist ein degenerierter Split, kein "
f"Kapitel-Split. Abbruch statt {len(chunks)} Planner-Aufrufe (vgl. #345). "
f"Ohne eingebettete Lesezeichen ist --by-chapter für dieses PDF ungeeignet."
)
if len(chunks) > LARGE_DOC_THRESHOLD and not getattr(args, "by_chapter", False):
print(f" [WARN] {len(chunks)} Chunks - großes Dokument. Erwäge --by-chapter für Bücher.")
acronym_dict = acronym_fix.extract_acronym_pairs(text)
Expand All @@ -2254,7 +2269,9 @@ def _run_extraction_stages(
f"{', '.join(list(acronym_dict.keys())[:8])}"
f"{'...' if len(acronym_dict) > 8 else ''}"
)
overview = pdf_chunker.extract_overview(text)
# #345/M1: ein Split pro Lauf — die bereits berechneten Chunks in die Overview
# injizieren (outline-basiert, kein zweiter split_by_chapters-Aufruf).
overview = pdf_chunker.extract_overview(text, chapters=chunks)
pdf_meta = pdf_meta_early
# #234: Selbst-Vergiftungs-Schutz — eingebetteten /Title verwerfen, wenn der
# Info-Dict-Autor dem Dateinamen widerspricht. Vor dem Enrichment, damit der
Expand Down
Loading
Loading