In strafrechtlichen Großverfahren – von komplexen Wirtschaftsstrafsachen über Steuerstrafverfahren bis hin zu EncroChat- und SkyECC-Komplexen – explodieren die Datenmengen. Kanzleien sehen sich regelmäßig mit hunderttausenden bis mehreren Millionen Seiten digitaler Ermittlungsakten (§ 32f StPO) konfrontiert.

Viele Entwickler und Kanzleien experimentieren heute mit Large Language Models (LLMs) und Retrieval-Augmented Generation (RAG), um diese Dokumentenberge zu erschließen. In der anwaltlichen Praxis scheitern naive RAG-Implementierungen jedoch regelmäßig krachend:

  1. Behördliche Scan-Qualität: Akten enthalten oft schiefe Scans, Durchschläge, Stempel, handschriftliche Vermerke und fehlerhafte Behörden-OCR.
  2. Das Halluzinations-Risiko: Erfindet ein Chatbot im Zivilrecht ein Zitat, ist das peinlich. Im Strafrecht, wo es um Freiheitsstrafen und existenzielle Vorwürfe geht, führt ein halluziniertes Beweisergebnis zu fatalen Fehlentscheidungen.
  3. Mangelnde Quellenbindung: Gerichte und Staatsanwaltschaften akzeptieren keine abstrakten Zusammenfassungen. Jede Tatsachenbehauptung muss zwingend mit der konkreten Fundstelle belegt werden: „Band III, Bl. 142 d. A.“.

Wer professionelle juristische Kanzleisysteme konzipiert, findet auf dem Fachportal LegalTech.top – Fachportal für Legal Tech, Kanzleiautomatisierung und KI im Recht fundierte Referenzarchitekturen. Im Folgenden betrachten wir die konkrete technische Pipeline zur deterministischen Geometrie-Extraktion und Validierung.

1. Die Pipeline-Architektur: Vom Rohscan zum beweisbaren Wissensgraphen

Eine industrielle juristische Parsing-Pipeline gliedert sich in vier strikt getrennte Phasen:

[PDF-Ermittlungsakte] │ ▼ [Phase 1: Textlayer-Audit (PyMuPDF)] ──(Kein Text / Schlechte Qualität)──► [OCRmyPDF / Tesseract Pipeline] │ │ ├───────────────────────────────◄──────────────────────────────────────────┘ ▼ [Phase 2: Geometrie- & Paginierungs-Extraktion (Bounding Boxes & RegEx)] │ ▼ [Phase 3: Chunking mit Blattzahlen-Metadaten & Vektorisierung] │ ▼ [Phase 4: LLM-Inferenz mit zwingendem RegEx-Assertion-Layer] 

2. Phase 1: Textlayer-Validierung mit PyMuPDF (fitz)

Häufig enthalten übermittelte Ermittlungsakten zwar bereits einen Textlayer, dieser ist jedoch oft korrupt, verschoben oder unvollständig. Bevor ein Dokument in die Vektorisierung wandert, muss jede einzelne Seite auditiert werden:

import fitz # PyMuPDF from typing import Dict, Any def audit_pdf_page_textlayer(page: fitz.Page) -> Dict[str, Any]: """ Prüft, ob eine PDF-Seite bereits über einen validen, lesbaren Textlayer verfügt oder ob eine Neu-OCR erforderlich ist. """ text = page.get_text("text").strip() images = page.get_images() # 1. Fall: Reine Bildseite ohne Text  if not text and len(images) > 0: return {"status": "NEEDS_OCR", "confidence": 0.0, "reason": "No text layer found on image"} # 2. Fall: Leere Trennseite  if not text and len(images) == 0: return {"status": "EMPTY_PAGE", "confidence": 1.0, "reason": "Blank separator page"} # 3. Fall: Textqualität anhand Zeichen-Dichte & Sonderzeichen-Anteil bewerten  total_chars = len(text) non_printable = sum(1 for c in text if not c.isprintable() and c not in '\n\r\t') garbage_ratio = non_printable / max(total_chars, 1) # Erkennung von verrauschtem OCR-Müll (typisch für fehlerhafte Vorlagen)  if garbage_ratio > 0.15 or total_chars < 30 and len(images) > 0: return { "status": "NEEDS_REOCR", "confidence": 1.0 - garbage_ratio, "char_count": total_chars, "reason": "Text layer corrupted or insufficient density" } return { "status": "VALID", "confidence": 0.95, "char_count": total_chars, "sample": text[:120].replace('\n', ' ') } 

Ist status == "NEEDS_OCR", wird die Seite durch eine spezialisierte Tesseract/OCRmyPDF-Instanz mit aktivierter PDF/A-Injektion (--output-type pdfa-2) geleitet.

3. Phase 2: Blattzahlen-Referenzierung und Koordinaten-Extraktion

Im Strafverfahren sind Dokumente fast nie fortlaufend nummeriert wie ein Buch. Ermittlungsbehörden versehen jede Seite mit handschriftlichen, maschinellen oder gestempelten Blattzahlen – oft oben rechts: Bl. 142, Bl. 142 R (Rückseite) oder AS 1.204 (Aktenseite).

Um KI-Halluzinationen auszuschließen, extrahieren wir die Bounding Boxes jedes Wortes und mappen die behördliche Blattzahl direkt in das Metadaten-Schema jedes Chunks:

import fitz import re from typing import List, Dict BLATT_PATTERN = re.compile( r'(?:Bl(?:att|\.)?|AS|Bl\.?\s*d\.?\s*A\.?)\s*([0-9]{1,5}\s*(?:[a-zA-Z]|Rü|R)?)', re.IGNORECASE ) def extract_chunks_with_geometry(doc_path: str, volume_name: str) -> List[Dict[str, Any]]: doc = fitz.open(doc_path) extracted_chunks = [] current_sheet = "Unbekannt" for page_idx, page in enumerate(doc): # 1. Wörter mit Bounding Boxes extrahieren: (x0, y0, x1, y1, word, block_no, line_no, word_no)  words = page.get_text("words") page_text = page.get_text("text") # 2. Blattzahl in Kopf-/Fußzeile detektieren  match = BLATT_PATTERN.search(page_text[:400]) # Meist in den ersten Zeilen  if match: current_sheet = match.group(1).strip() # 3. Strukturierte Textblöcke aufbauen  blocks = page.get_text("blocks") for b in blocks: text_content = b[4].strip() if len(text_content) < 25: continue bbox = (round(b[0], 1), round(b[1], 1), round(b[2], 1), round(b[3], 1)) chunk_data = { "content": text_content, "metadata": { "source_doc": doc_path, "volume": volume_name, "page_index": page_idx + 1, "blatt_nummer": current_sheet, "bbox": bbox } } extracted_chunks.append(chunk_data) doc.close() return extracted_chunks 

4. Phase 3 & 4: Halluzinationsvermeidung via RegEx-Assertion

Erzeugt das Sprachmodell (z. B. bei der chronologischen Rekonstruktion eines Tatvorwurfs) eine Zusammenfassung, darf keine Aussage ohne Zitation ausgegeben werden. Wir lassen das Modell Antworten in einem strikten Format generieren:

[Tatsachenbehauptung] (Fundstelle: Band <X>, Bl. <Y>)

Ein nachgelagerter Python-Assertion-Layer prüft jede Fundstelle gegen die Vektordatenbank:

def verify_claim_grounding(claim_text: str, cited_sheet: str, indexed_chunks: List[Dict]) -> bool: """ Deterministische Validierung: Existiert die Behauptung tatsächlich im referenzierten Aktenblatt? """ relevant_chunks = [c for c in indexed_chunks if c["metadata"]["blatt_nummer"] == cited_sheet] if not relevant_chunks: return False # Halluzinierte Blattzahl!  # Prüfe Vorkommen von Schlüsselwörtern des Claims im Originaltext  keywords = [w.lower() for w in claim_text.split() if len(w) > 5] all_sheet_text = " ".join([c["content"].lower() for c in relevant_chunks]) matches = sum(1 for kw in keywords if kw in all_sheet_text) return (matches / max(len(keywords), 1)) >= 0.60 

Wird der Schwellenwert unterschritten, weist das System die Antwort zurück und kennzeichnet den Passus als ungesichert. Wie moderne Legal-Data-Pipelines diese Validierung in Produktionsumgebungen integrieren, erläutert LegalTech.top anhand praktischer Fallbeispiele.

5. Berufsrechtliche Leitplanken: § 203 StGB & § 43e BRAO

Für Software-Ingenieure im LegalTech-Umfeld gilt: Technische Machbarkeit entbindet nicht von berufsrechtlichen Vorgaben:

  • § 203 StGB (Verletzung von Privatgeheimnissen): Die Übermittlung ungeschwärzter Ermittlungsakten an Standard-Public-APIs ohne Auftragsverarbeitungs- und Verschwiegenheitsvereinbarungen ist strafbewehrt.
  • Lokale Inferenz (On-Premises): Für Kanzleien empfiehlt sich die Bereitstellung lokaler Open-Weight-Modelle (z. B. via vLLM, Ollama oder Triton Inference Server auf eigener GPU-Hardware).
  • Zero Data Retention (ZDR): Werden Cloud-APIs eingesetzt, müssen vertraglich garantierte ZDR-Umgebungen vereinbart werden, die jegliche Protokollierung oder Modellweiterschulung ausschließen.

Über den Autor

Rechtsanwalt Thomas Koll

Fachanwalt für Strafrecht

Kanzlei Koll & Tamrzadeh Rechtsanwälte

Oppenhoffallee 3–5, 52066 Aachen

Telefon: 0241 / 9437950

E-Mail: redaktion@computerstrafrecht.info / koll@koll-tamrzadeh.de

Zuständige Kammer: Rechtsanwaltskammer Köln