Zum Hauptinhalt springen
Tech-Showcase Code öffentlich

Drei LLMs, ein Mahnlauf

Ein Python-Skript liest 40 offene Posten, beurteilt jeden Fall, erzwingt sauberes JSON und entwirft die Mahn-E-Mail — mit dem jeweils passenden Modell pro Schritt.

  • 40 Posten verarbeitet
  • 0 ungültige Antworten
  • 2 Sprachen erkannt

Kein Kundenprojekt: ein lauffähiges Lehrstück mit synthetischen Daten, echtem Code und echten Modell-Ausgaben. Skript und Datensatz stehen unten zum Download.

Ausgangslage

Die Frage, die fast jeder stellt

«Chatten können wir jetzt. Aber wie nutzen wir LLMs im Tagesgeschäft — ohne dass jemand den ganzen Tag Prompts tippt?»

Die Antwort ist unspektakulär: Man nimmt das Chat-Fenster weg. Ein LLM wird zum Arbeitswerkzeug, wenn es in einer Schleife steckt — ein Skript füttert ihm eine Datenzeile nach der anderen, prüft jede Antwort maschinell und schreibt das Ergebnis dorthin, wo die Arbeit weitergeht.

Genau das zeigt dieses Showcase an einem Prozess, den jedes KMU kennt: dem Mahnlauf.

Szenario

Das Szenario

Die fiktive Muster Handwerk AG hat 40 offene Posten. Die Debitorenliste sieht aus, wie solche Listen wirklich aussehen: Notizen der Buchhaltung in Deutsch, Französisch und Englisch, Halbsätze, Grossbuchstaben, Widersprüche. Vier Beispiele:

RechnungKundeBetragTage überfälligNotiz der Buchhaltung
2026-1002 Café Bellevue GmbH CHF 1'350.00 34 hat angerufen, zahlt Ende Monat. tel. bestätigt 08.07.
2026-1003 Menuiserie Perrin Sàrl CHF 8'920.00 41 attend le décompte final avant de payer – à clarifier avec chef de projet
2026-1009 Elektro Wyss AG CHF 6'540.00 88 Gerücht vom Lieferanten: Zahlungsschwierigkeiten?? nichts konkretes. VORSICHT mit Ton
2026-1031 Tech Solutions Ltd CHF 21'430.00 42 invoice stuck in their approval workflow, says 'processed next run' – 2nd time they say this

Alle 40 Zeilen sind synthetisch — Firmen, Beträge und Notizen sind erfunden. Der komplette Datensatz liegt im Download.

Architektur

Die Pipeline: drei Schritte, drei Modelle

Jede Zeile durchläuft drei Schritte. Für jeden Schritt ist ein anderes Modell zuständig — jeweils das, das für die Aufgabe reicht:

offene_posten.csv 40 Zeilen Pseudonymisierung «Jurablick AG» → KUNDE_04 1 · Beurteilen Claude Opus 4.8 ANTHROPIC 2 · Strukturieren GPT-5 mini OPENAI E-Mail nötig? Ja Nein 3 · Schreiben Gemini 2.5 Flash GOOGLE Mensch entscheidet Zurückstellen · Manuelle Prüfung Entwürfe + Ergebnis-CSV nichts wird versendet

→ Diagramm ist seitlich wischbar

Notation: Flussdiagramm nach ISO 5807 — Parallelogramm = Daten, Rechteck = Prozess, Doppelbalken = vordefinierter Prozess (API-Aufruf), Raute = Entscheidung, Trapez = manuelle Tätigkeit, Wellenform = Dokument.

1 · Beurteilen

Claude Opus 4.8

Anthropic · CHF 4.05 / 20.25 pro 1M Tokens

Liest die chaotische Notiz und wägt ab: Zahlungszusage? Streitfall? Teilzahlung? Das stärkste Modell sitzt dort, wo Urteilsvermögen gefragt ist.

2 · Strukturieren

GPT-5 mini

OpenAI · CHF 0.20 / 1.62 pro 1M Tokens

Presst die Beurteilung in ein striktes JSON, das Software weiterverarbeiten kann. Formatarbeit — dafür reicht ein kleines, günstiges Modell.

3 · Schreiben

Gemini 2.5 Flash

Google · CHF 0.24 / 2.03 pro 1M Tokens

Formuliert den E-Mail-Entwurf in der Sprache des Kunden — Deutsch, Französisch oder Englisch, im Ton, den Schritt 1 vorgegeben hat.

Warum nicht einfach ein Modell für alles?

Ginge auch — die Aufteilung ist bewusst didaktisch. Sie zeigt zwei Dinge: Anbieter lassen sich pro Schritt austauschen (kein Lock-in), und das teure Modell arbeitet nur dort, wo es gebraucht wird. Bei 40 Posten ist das egal, bei 40'000 nicht mehr.

Code

Der Code

Das komplette Skript hat gut 200 Zeilen, läuft mit drei API-Schlüsseln als Umgebungsvariablen und versendet nichts — E-Mails landen als Entwürfe auf der Festplatte. Die drei entscheidenden Stellen:

Der Prompt für Schritt 1 — Geschäftsregeln in Klartext

Die «Regeln des Betriebs» stehen direkt im Prompt. Das ist der Teil, den Sie ohne Programmierkenntnisse selbst anpassen können:

PROMPT_BEURTEILEN = """Du bist erfahrene:r Debitoren-Sachbearbeiter:in der Muster Handwerk AG,
einem Schweizer KMU. Beurteile diesen offenen Posten (Stichtag {stichtag}):

Rechnung {rechnung_nr} an {kunde_pseudo} über CHF {betrag_chf},
fällig seit {tage_ueberfaellig} Tagen, bisherige Mahnungen: {mahnstufe}.
Notiz aus der Buchhaltung (Originalton, evtl. DE/FR/EN): «{notizen}»

Empfiehl in 3–5 Sätzen das weitere Vorgehen. Regeln des Betriebs:
- Zahlungszusagen, Ratenvereinbarungen, Streitfälle und Teilzahlungen NICHT stur weitermahnen.
- Unklare Fälle (Insolvenzgerüchte, unzustellbare Post, Rechts-/Verrechnungsfragen)
  erfordern eine manuelle Prüfung durch die Geschäftsleitung — nicht mahnen.
- Bei wichtigen Kunden bestimmt, aber beziehungsschonend formulieren.
- Erkenne die Sprache des Kunden (de/fr/en) aus Notiz und Firmenname."""

Drei Anbieter, drei offizielle SDKs

Jeder Schritt ist ein kurzer Funktionsaufruf nach der offiziellen SDK-Dokumentation des jeweiligen Anbieters — inklusive Token-Zählung für den Kostenzähler:

def beurteilen(client, prompt: str):
    """Schritt 1 — Claude: liest den Fall und empfiehlt das Vorgehen."""
    msg = client.messages.create(
        model=MODELL_BEURTEILEN,
        max_tokens=600,
        thinking={"type": "adaptive"},
        messages=[{"role": "user", "content": prompt}],
    )
    text = "".join(b.text for b in msg.content if b.type == "text")
    return text, msg.usage.input_tokens, msg.usage.output_tokens


def strukturieren(client, prompt: str):
    """Schritt 2 — GPT-5 mini: erzwingt sauberes JSON."""
    r = client.chat.completions.create(
        model=MODELL_STRUKTUR,
        response_format={"type": "json_object"},
        messages=[{"role": "user", "content": prompt}],
    )
    return r.choices[0].message.content, r.usage.prompt_tokens, r.usage.completion_tokens


def schreiben(client, prompt: str):
    """Schritt 3 — Gemini: formuliert den E-Mail-Entwurf."""
    r = client.models.generate_content(model=MODELL_TEXT, contents=prompt)
    u = r.usage_metadata
    return r.text, u.prompt_token_count, u.candidates_token_count

Die Schleife — prüfen, wiederholen, protokollieren

Das JSON aus Schritt 2 wird maschinell validiert (erlaubte Aktionen, Fristen, Sprachen). Ist es ungültig, bekommt das Modell genau einen zweiten Versuch mit der Fehlermeldung — danach bricht der Lauf ab, statt Müll zu produzieren:

    for p in posten:
        # Schritt 1: beurteilen
        beurteilung, t_in, t_out = beurteilen(c_claude, PROMPT_BEURTEILEN.format(**p))
        kosten_chf += (t_in * PREISE[MODELL_BEURTEILEN][0] + t_out * PREISE[MODELL_BEURTEILEN][1]) / 1e6 * KURS_USD_CHF

        # Schritt 2: strukturieren (1 Wiederholung bei ungültigem JSON)
        prompt2 = PROMPT_STRUKTUR.format(beurteilung=beurteilung, **p)
        for versuch in (1, 2):
            roh, t_in, t_out = strukturieren(c_openai, prompt2)
            kosten_chf += (t_in * PREISE[MODELL_STRUKTUR][0] + t_out * PREISE[MODELL_STRUKTUR][1]) / 1e6 * KURS_USD_CHF
            try:
                entscheid = json_pruefen(roh)
                break
            except (ValueError, json.JSONDecodeError) as e:
                if versuch == 2:
                    raise SystemExit(f"{p['rechnung_nr']}: JSON nach 2 Versuchen ungültig — {e}")
                prompt2 += f"\n\nDeine letzte Antwort war ungültig ({e}). Korrigiere sie."

        # Schritt 3: schreiben — nur wenn wirklich eine E-Mail raus soll
        if entscheid["email_senden"] and entscheid["aktion"] not in ("zurueckstellen", "manuelle_pruefung"):
            entwurf, t_in, t_out = schreiben(c_gemini, PROMPT_TEXT.format(**entscheid, **p))
            kosten_chf += (t_in * PREISE[MODELL_TEXT][0] + t_out * PREISE[MODELL_TEXT][1]) / 1e6 * KURS_USD_CHF
            # Pseudonym erst jetzt, lokal, wieder durch den Klarnamen ersetzen
            entwurf = entwurf.replace(p["kunde_pseudo"], p["kunde"])
            with open(f"entwuerfe/{p['rechnung_nr']}.txt", "w", encoding="utf-8") as f:
                f.write(entwurf)

        ergebnisse.append({"rechnung_nr": p["rechnung_nr"], "kunde": p["kunde"],
                           "betrag_chf": p["betrag_chf"], "tage": p["tage_ueberfaellig"],
                           **{k: entscheid[k] for k in ("aktion", "sprache", "ton",
                                                        "frist_tage", "risiko", "grund")}})
        print(f"{p['rechnung_nr']}  {entscheid['aktion']:<16} {entscheid['risiko']:<7} {entscheid['grund']}")
Resultate

Der Testlauf: echte Ausgaben

Wir haben die Pipeline über alle 40 Posten laufen lassen. Ergebnis: 18× freundliche Erinnerung, 2× Mahnung, 1× letzte Mahnung — und 19 Fälle, die das System bewusst NICHT gemahnt hat, sondern zurückgestellt oder der Geschäftsleitung zur manuellen Prüfung vorgelegt. Genau diese Fälle sind der Punkt: Ein stures Mahnprogramm hätte dem Kunden mit telefonisch bestätigter Zahlungszusage trotzdem eine Mahnung geschickt.

FallNotiz (Original)EntscheidBegründung des Modells
2026-1002
CHF 1'350.00 · 34 Tage überfällig
hat angerufen, zahlt Ende Monat. tel. bestätigt 08.07. Zurückstellen
Risiko: mittel
Zahlungszusage per Telefon am 08.07. bis Ende Monat; Mahnstopp bis Anfang August gesetzt.
2026-1004
CHF 23'760.00 · 67 Tage überfällig
2. Mahnung per Post 12.06. KEINE Reaktion. grosser Folgeauftrag offen!! Manuelle Prüfung
Risiko: hoch
Wichtiger Kunde mit grossem Folgeauftrag; standardisierter Mahnlauf kontraproduktiv. Manuelle Prüfung durch Geschäftsleitung empfohlen.
2026-1007
CHF 4'870.00 · 51 Tage überfällig
Teilzahlung CHF 2'000 am 30.06. eingegangen, Rest offen. war ok für ihn per mail Freundliche Erinnerung
Risiko: mittel
Teilzahlung geleistet, Restbetrag offen. Keine neue Zahlungszusage. Beziehungsschonende Erinnerung per E-Mail empfohlen.
2026-1009
CHF 6'540.00 · 88 Tage überfällig
Gerücht vom Lieferanten: Zahlungsschwierigkeiten?? nichts konkretes. VORSICHT mit Ton Manuelle Prüfung
Risiko: hoch
88 Tage überfällig, 2. Mahnung, Gerücht über Zahlungsschwierigkeiten – Fall zur manuellen Prüfung durch Geschäftsleitung.
2026-1006
CHF 15'300.00 · 28 Tage überfällig
says PO number missing on invoice – resend with PO 4711, then payment 'immediately' Zurückstellen
Risiko: tief
Kunde beanstandet fehlende Bestellnummer, sagt Zahlung nach Korrektur zu. Mahnstopp, korrigierte Rechnung mit PO 4711 ausstellen.
2026-1023
CHF 430.00 · 63 Tage überfällig
Kleinbetrag. 2 Mahnungen raus. lohnt sich eine Betreibung überhaupt?? KLÄREN Letzte Mahnung
Risiko: mittel
Kleinbetrag CHF 430, 63 Tage überfällig, 2. Mahnung. Letzte Zahlungsaufforderung mit Betreibungsandrohung, danach Prüfung mit GL.

21 E-Mail-Entwürfe, drei Sprachen

Zwei Beispiele aus dem Lauf — unverändert, wie das Modell sie abgelegt hat:

So wurden diese Ausgaben erzeugt

In unserer Entwicklungsumgebung liegen keine Schlüssel von Anthropic, OpenAI oder Google. Der Testlauf lief deshalb mit exakt denselben Prompts und derselben Validierung über einen einzigen OpenAI-kompatiblen Endpunkt (DeepSeek — dasselbe Modell, das den Chat auf dieser Website antreibt). Der veröffentlichte Code ruft die drei Anbieter gemäss deren offiziellen SDKs auf; die gezeigten Entscheide und Entwürfe sind echte, unbearbeitete Modell-Ausgaben aus diesem Testlauf.

Kosten

Was kostet so ein Lauf?

Hochrechnung für die 40 Posten: gemessene Textmengen aus dem Testlauf, umgerechnet in Tokens (~3.6 Zeichen/Token) und multipliziert mit den Listenpreisen der drei Anbieter (in USD publiziert, hier umgerechnet zum Kurs 1 USD = 0.81 CHF, Stand Juli 2026; Angaben in CHF pro 1 Mio. Tokens):

SchrittPreis in / out (CHF)Tokens in / out (40 Posten)Kosten
Beurteilen
Claude Opus 4.8
4.05 / 20.25 ~8'998 / ~8'715 ~CHF 0.21
Strukturieren
GPT-5 mini
0.20 / 1.62 ~15'242 / ~2'731 ~CHF 0.01
Schreiben
Gemini 2.5 Flash
0.24 / 2.03 ~3'907 / ~3'010 ~CHF 0.01

Total pro Lauf: ~CHF 0.23 — bei einem wöchentlichen Mahnlauf rund CHF 0.98 pro Monat.

Das ist eine Projektion, keine Abrechnung: Tokenzahlen sind aus Zeichenmengen geschätzt, Wechselkurse bewegen sich, echte Provider-Rechnungen können abweichen. Die Grössenordnung ist der Punkt — der teuerste Posten im Mahnwesen ist nicht die KI, sondern die Stunde Handarbeit.

Sicherheit

Sicherheit, Datenschutz, Grenzen

Klarnamen bleiben im Haus

Vor jedem API-Aufruf werden Kundennamen durch Pseudonyme ersetzt (KUNDE_07) und erst im fertigen Entwurf lokal wieder eingesetzt. Die Anbieter sehen Beträge und Notizen, aber keine Namen.

Nichts geht automatisch raus

Das Skript versendet keine einzige E-Mail — es schreibt Entwürfe. Und 19 von 40 Fällen hat es selbst zur manuellen Prüfung markiert oder zurückgestellt, statt zu mahnen.

Schlüssel gehören nicht in den Code

API-Schlüssel kommen aus Umgebungsvariablen. Im veröffentlichten Code steht kein einziger Schlüssel — so gehört das.

Grenzen

Ein LLM kann eine Notiz falsch deuten. Deshalb: Entwürfe statt Versand, strikte JSON-Validierung, Sonderfälle zum Menschen. Automatisierung heisst hier: 80% Routine maschinell vorbereiten, 20% Urteil beim Menschen lassen.

Transparenz

  • Kein Kundenprojekt — ein Lehrstück, gebaut für diese Website.
  • Alle Daten synthetisch: Firmen, Beträge und Notizen sind erfunden.
  • Der Code ist lauffähig, sofern Sie eigene API-Schlüssel der drei Anbieter einsetzen.
  • Die gezeigten Ausgaben stammen aus einem Testlauf über einen einzelnen DeepSeek-Endpunkt (siehe oben) — nicht aus Läufen bei Anthropic, OpenAI oder Google.
  • Die Kostentabelle ist eine Hochrechnung aus geschätzten Tokenzahlen × Listenpreisen der Anbieter (in USD publiziert, in CHF umgerechnet).

Zum Selber-Ausprobieren

Skript, Datensatz und Anleitung als ZIP — MIT-Lizenz, frei verwendbar. Hinterlassen Sie Ihre geschäftliche E-Mail, und der Download startet direkt.

Wir verwenden Ihre E-Mail, um uns einmalig persönlich zu melden — kein Newsletter, keine Weitergabe. Details in der Datenschutzerklärung.

Welcher Ihrer Prozesse hat eine «Notizen-Spalte»?

Mahnwesen, Offertwesen, Bestellabwicklung, Support-Postfach — wo Text anfällt, funktioniert dieses Muster. Im kostenlosen Erstgespräch finden wir den Prozess mit dem besten Verhältnis von Aufwand zu Wirkung.

Kostenloses Strategiegespräch