Skip to content

5. Lösung der drei Probleme mit ChatGPT

5.1. Einleitung

Hier ist ein erster Screenshot einer ChatGPT-Sitzung:

 
  • In [1-3] die drei Probleme, die ChatGPT gestellt wurden;
  • In [4] die URL von ChatGPT;
  • In [5] die verwendete ChatGPT-Version;

ChatGPT ist ein Produkt von OpenAI, das unter der URL [https://chatgpt.com/] verfügbar ist. Um einen Verlauf Ihrer Frage-Antwort-Sitzungen wie oben zu erhalten, müssen Sie ein Konto erstellen. Wie alle anderen getesteten KI-Systeme begrenzt auch ChatGPT die Anzahl Ihrer Fragen und die Anzahl der hochgeladenen Dateien. Wenn dieses Limit erreicht ist, wird die Sitzung beendet und Ihnen wird angeboten, sie zu einem späteren Zeitpunkt fortzusetzen. Die von ChatGPT auferlegten Limits sind sehr schnell erreicht. Um dieses Tutorial zu erstellen, musste ich ein kostenpflichtiges Monatsabonnement abschließen.

Die Benutzeroberfläche von ChatGPT sieht wie folgt aus:

 
  • Unter [1] können Sie Dateien an die gestellte Frage anhängen;
  • Unter [2] die gestellte Frage;
  • Unter [3] starten Sie die Ausführung der KI; ## 5.2. Aufgabe 1

Die Frage an ChatGPT:

  
 

ChatGPT antwortet korrekt.

5.3. Problem 2

Es geht um die Steuerberechnung mit dem PDF. Um ehrlich zu sein, werden wir das von Gemini generierte PDF verwenden, das Fehler im ursprünglichen PDF korrigiert.

 
  • In [1] haben wir das von Gemini generierte PDF bereitgestellt;
  • In [2] haben wir den Unit-Test hinzugefügt, mit dem Gemini seine Überlegenheit unter Beweis gestellt hat:
test12: (2, 2, 49500) -> (1297, 431, 324)

Wir starten ChatGPT. Es dauert etwa 3 Minuten, bis die Antwort generiert ist. Im Gegensatz zu Gemini liefert es einen funktionierenden Link, über den man das generierte Skript abrufen kann. Wir laden dieses in PyCharm:

 

Das Skript [chatGPT1] funktioniert auf Anhieb. Hier gibt es keinen Zweifel: Bei diesem Problem war ChatGPT leistungsfähiger als Gemini.

Das von ChatGPT bereitgestellte Skript [chatGPT1] lautet wie folgt:

# -*- coding: utf-8 -*-
"""
Berechnung der Steuer 2019 (Einkommen 2018) in Python.

Annahmen und Regeln (gemäß dem bereitgestellten PDF):
- Alle Einkünfte werden dem Steuerpflichtigen 1 zugerechnet (auch bei einem Ehepaar).
- Pauschalfreibetrag von 10 % mit einem Mindest- und Höchstbetrag.
- Progressiver Steuertarif 2018, angewendet auf den Familienquotienten (QF).
- Begrenzung des Familienquotienten: Vorteil durch begrenzte Kinderanteile.
- Abschlag und Ermäßigung von 20 % für Geringverdiener, mit der Nachholregel.
- Reihenfolge der Korrekturen: Begrenzung des QF -> Abschlag -> Ermäßigung von 20 %.
- Rundungen: Freibetrag (↑), Abschlag (↑), Ermäßigung (↑), endgültige Steuer (↓).
- Die zu zahlende Steuer (auf den nächsten Euro abgerundet), der Abschlag und die Ermäßigung
  (auf den nächsten Euro aufgerundet).

Die „magischen“ Zahlen werden in Konstanten zusammengefasst, und jeder Schritt wird
der Übersichtlichkeit halber in Funktionen aus.
"""

from dataclasses import dataclass
from math import ceil, floor

# ---------- Gesetzliche Konstanten ----------

# Steuerfreibetrag für Berufstätige (Unter-/Obergrenze) für Einkünfte 2018
ABATTEMENT_TAUX = 0.10
ABATTEMENT_MIN = 437
ABATTEMENT_MAX = 12502

# Steuertabelle 2018 (QF)
# Jede Stufe ist (Obergrenze, Steuersatz). Die erste Stufe (0 %) endet bei 9 964 €.
STAFFEL_2018 = [
    (9964, 0,00),
    (27519, 0,14),
    (73779, 0,30),
    (156244, 0,41),
    (float("inf"), 0,45),
]

# Obergrenze für den Familienquotienten
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  # Maximaler Vorteil pro 1/2 Anteil KIND

# Abschlag (Schwellenwerte je nach Anzahl der Erwachsenen / Paarbeziehung)
DECOTE_SEUIL_CELIB = 1196
DECOTE_SEUIL_COUPLE = 1970

# 20 %-ige Reduzierung: Schwellenwerte je nach Anteilen
REDUCTION_BASE_1_PART = 18985
REDUCTION_BASE_2_PARTS = 37970
REDUCTION_PAR_DEMI_PART_SUP = 3803  # Aufschlag pro 1/2 Anteil über die Basis von 1 oder 2 Anteilen hinaus


# ---------- Datenstrukturen ----------

@dataclass
class Steuerergebnis:
    Steuer: int  # endgültig zu zahlende Steuer, auf den nächsten ganzen Euro abgerundet
    Rabatt: int  # Rabatt, auf den nächsten Euro aufgerundet
    reduction: int  # Ermäßigung 20 %, auf den nächsten Euro aufgerundet


# ---------- Hilfsfunktionen ----------

def arrondi_sup(euros: float) -> int:
    """Auf den nächsten Euro aufgerundet (ceil)."""
    return int(ceil(euros))


def aufrund(euros: float) -> int:
    """Auf den nächsten Euro abgerundet (floor)."""
    return int(floor(euros))


# ---------- Schritt 1: Steuerpflichtiges Nettoeinkommen (RNI) ----------

def berechnung_freibetrag(bruttoeinkommen: float) -> int:
    """
    Berechnet den Freibetrag von 10 %, begrenzt nach oben/unten, AUF DEN NÄCHSTHÖHEREN EURO GERUNDET.
    """
    abzug_berechnet = Bruttoeinkommen * ABZUGSSATZ
    berechneter_Freibetrag = max(FREIBETRAG_MIN, min(FREIBETRAG_MAX, berechneter_Freibetrag))
    return aufgerundet(abzug_berechnet)


def steuerpflichtiges_Nettoeinkommen(Bruttoeinkommen: float) -> int:
    """Nettoeinkommen = Bruttoeinkommen - Freibetrag (Freibetrag aufgerundet ↑)."""
    return int(round(Bruttoeinkommen - berechneter_Freibetrag(Bruttoeinkommen)))


# ---------- Schritt 2: Anzahl der Anteile ----------

def Anzahl_Anteile(Erwachsene: int, Kinder: int) -> float:
    """
    Regeln: 1 Anteil bei 1 Erwachsenen, 2 Anteile bei 2 Erwachsenen.
    Kinder: +0,5 für die ersten beiden, +1 pro Kind ab dem dritten.
    (Sonderfälle wie Alleinerziehende werden hier gemäß dem PDF nicht berücksichtigt.)
    """
    Anteile = 1,0, wenn Erwachsene == 1, sonst 2,0
    if Kinder <= 0:
        return Anteile
    if Kinder == 1:
        return Anteile + 0,5
    # Kinder >= 2
    Anteile += 1.0  # +0,5 +0,5 für die ersten beiden
    if Kinder > 2:
        anteile += (Kinder - 2) * 1.0  # +1 pro Kind ab dem dritten
    return Anteile


def halbe_Anteile_Kinder(Kinder: int) -> int:
    """Anzahl der HALBEN Anteile für die Kinder (nützlich für die QF-Obergrenze)."""
    if Kinder <= 0:
        return 0
    if Kinder == 1:
        return 1  # 0,5 Anteil
    # Die ersten beiden Kinder = 2 * halber Freibetrag, danach +2 halbe Freibeträge pro Kind ab dem dritten
    Ergebnis: 2 + (Anzahl Kinder  2) * 2


# ---------- Schritt 3: Bruttosteuer über QF ----------

def bruttosteuer_pro_Anteil(qf: float) -> float:
    """Steuer pro Anteil nach dem progressiven Steuertarif 2018 (ohne Rundung)."""
    imp = 0.0
    previous = 0.0
    for obergrenze, satz in BAREME_2018:
        Betrag_Stufe = max(0.0, min(qf, Obergrenze) - previous)
        Steuer += Betrag_Stufe * Steuersatz
        if qf <= Obergrenze:
            break
        vorheriger = Obergrenze
    return imp


def Bruttosteuergesamt(rni: float, Anteile: float) -> float:
    """Gesamtsteuerbetrag = Steuer pro Anteil * Anzahl der Anteile (ohne Rundung)."""
    if Anteile <= 0:
        return 0.0
    qf = rni / parts
    return Bruttosteuer_pro_Anteil(qf) * Anteile


# ---------- Schritt 4.1: Begrenzung des Familienquotienten ----------

def apply_plafonnement_qf(impot_avec_enfants: float, rni: float, adultes: int, enfants: int) -> float:
    """
    Berechnet die Steuer nach Begrenzung des Familienquotienten, wenn der Kinderfreibetrag die Obergrenze überschreitet.
    - Steuer A: mit Anteilen einschließlich Kinder
    - Steuer B: nur mit Anteilen der Erwachsenen (1 oder 2)
    - Tatsächlicher Vorteil = B - A
    - Maximaler Vorteil = 1551 € pro 1/2 Kinderanteil
    Wenn tatsächlicher Vorteil > maximaler Vorteil, Steuer = B – maximaler Vorteil, andernfalls Steuer = A.
    """
    Anteile_Erwachsene = 1,0 if Erwachsene == 1 else 2,0
    imp_b = Bruttosteuer(RNI, Anteile_Erwachsene)
    imp_a = Bruttosteuer_mit_Kindern
    tatsächlicher_Vorteil = max(0.0, imp_b - imp_a)
    Vorteil_max = PF_QF_VORTEIL_PRO_HALB_ANTEIL * halb_Kinderanteile(Kinder)
    if tatsächlicher_Vorteil > maximaler_Vorteil:
        return imp_b - realer_Vorteil
    return imp_a


# ---------- Schritt 4.2: Abschlag und Ermäßigung von 20 % ----------

def Rabattschwelle(Erwachsene: int) -> int:
    return Rabattschwelle_Paar if Erwachsene >= 2 else Rabattschwelle_Einzel


def Schwellenwert_20-Prozent-Ermäßigung(BIP: int, Erwachsene: int, Anteile: float) -> int:
    """
    Schwellenwert für den Anspruch auf den Rabatt von 20 %:
    - Basis: 18.985 € für 1 Anteil, 37.970 € für 2 Anteile
    - + 3.803 € pro zusätzlichem 1/2 Anteil über die entsprechende Basis hinaus.
    """
    Basis = REDUCTION_BASE_2_PARTS, wenn Erwachsene >= 2, sonst REDUCTION_BASE_1_PART
    basis_anteile = 2.0, wenn Erwachsene >= 2, sonst 1.0
    demi_sup = max(0, int(round((Anteile - basis_anteile) * 2)))
    return Basis + Halb_Zuschlag * REDUCTION_PAR_DEMI_PART_SUP


def calc_decote_reduction(Steuer_nach_Obergrenze: float, RNI: int, Erwachsene: int, Anteile: float):
    """
    Berechnet (abgerundeter_Abschlag, abgerundete_Reduzierung, Steuer_nach_Korrekturen).
    Interaktionsregel: Der Abschlag kann über eine Nachholregelung angewendet werden, wenn
    theoretischen Anwendung der Ermäßigung von 20 % die Steuer unter den Schwellenwert fallen würde.
    Reihenfolge: Abschlag -> Ermäßigung.
    """
    Steuer = max(0,0, Steuer_nach_Obergrenze)
    Abschlag = 0
    Ermäßigung = 0

    # Anspruch auf 20 % Ermäßigung basierend auf dem NIS
    seuil_red = seuil_reduction_20(rni, adultes, parts)
    eligible_reduction = rni < seuil_red  # „unterhalb eines Schwellenwerts“ im PDF

    # Anspruch auf Ermäßigung: direkt unterhalb des Schwellenwerts oder Nachrückverfahren, wenn die Ermäßigung dazu führen würde, dass der Schwellenwert unterschritten wird
    s_dec = Rabattschwelle(Erwachsene)
    direkter_Rabatt = imp <= s_dec
    Nachholmöglichkeit = False
    if not direkter_Rabatt and rabattberechtigt:
        theoretisches_Einkommen_nach_Reduzierung = Einkommen * 0,80  # theoretische Reduzierung 20 %
        if imp_theorique_apres_red <= s_dec:
            Nachholrunde = True

    elig_decote = (direct_decote oder repechage) und (imp > 0)

    # Zuerst den Abschlag anwenden (falls zutreffend)
    if elig_decote:
        Abschlagsbetrag = s_dec - (imp * 0,75)
        Rabatt_berechnet = max(0, aufrund(Rabattbetrag))
        # Der Abschlag darf die verbleibende Steuer nicht übersteigen: Er wird begrenzt
        Rabatt = min(Rabatt_berechnet, aufrund(Steuer))
        Steuer = max(0,0, Steuer - Abschlag)

    # Anschließend die Ermäßigung anwenden, sofern sie in Frage kommt
    if eligible_reduction and imp > 0:
        Ermässigungsbetrag = imp * 0.20
        Reduzierung = max(0, aufrund(Reduzierungsbetrag))
        imp = max(0.0, imp - reduction)

    return Rabatt, Ermäßigung, imp


# ---------- Haupt-API ----------

def berechne_steuer(Erwachsene: int, Kinder: int, Bruttoeinkommen: float) -> Steuerergebnis:
    """
    Berechnet die Steuer für 2019 (Einkommen 2018) für einen Haushalt.
    Gibt SteuerErgebnis(Steuer, Abschlag, Ermäßigung) zurück.
    """
    # 1) NIS
    rni = steuerpflichtiges_Nettoeinkommen(Bruttoeinkommen)

    # 2) Anteile
    Anteile = Anzahl_Anteile(Erwachsene, Kinder)

    # 3) Bruttosteuer nach Steuertabelle (mit allen Anteilen)
    Bruttosteuer_A = Gesamtsteuer(Nettoeinkommen, Anteile)

    # 4.1) Begrenzung des Familienquotienten
    Steuer_nach_Obergrenze = Obergrenze_Familienquotient(Bruttosteuer_A, RNI, Erwachsene, Kinder)

    # 4.2) Abschlag und anschließende Ermäßigung um 20 % (mit Nachholmöglichkeit)
    Abzug, Ermäßigung, korrigierte_Steuer = Berechnung_Abzug_Ermäßigung(Steuer_nach_Obergrenze, RNI, Erwachsene, Anteile)

    # 5) Endgültige Rundung der Steuer (↓) und Begrenzung auf 0
    endg_steuer = max(0, abrunden_unten(korrigierte_steuer))

    return ResultatImpot(impot=imp_final, decote=decote, reduction=reduction)


# ---------- Kleines Testprogramm ----------

def _nearly_equal(a: int, b: int, tol: int = 1) -> bool:
    return abs(a - b) <= tol


def unit_tests():
    """
    Gibt für jeden Test eine Liste von Tupeln (Eingaben, Erwartet, Erhalten, OK) zurück.
    Toleranz: ±1 € auf jeden Wert (Steuer, Abschlag, Rabatt).
    """
    fälle = [
        # (Erwachsene, Kinder, Einkommen) -> (Steuer, Rabatt, Ermäßigung)
        ((2, 2, 55555), (2815, 0, 0)),
        ((2, 2, 50000), (1385, 384, 346)),
        ((2, 3, 50000), (0, 720, 0)),
        ((1, 2, 100000), (19884, 0, 0)),
        ((1, 3, 100000), (16782, 0, 0)),
        ((2, 3, 100000), (9200, 0, 0)),
        ((2, 5, 100000), (4230, 0, 0)),
        ((1, 0, 100000), (22986, 0, 0)),
        ((2, 2, 30000), (0, 0, 0)),
        ((1, 0, 200000), (64211, 0, 0)),
        ((2, 3, 200000), (42843, 0, 0)),
        ((2, 2, 49500), (1297, 431, 324)),
    ]

    ergebnisse = []
    for (Erwachsene, Kinder, Einkommen), erwartet in fall:
        res = berechne_steuer(Erwachsene, Kinder, Einkommen)
        erhalten = (res.Steuer, res.Abzug, res.Ermäßigung)
        ok = _nearly_equal(erhalten[0], erwartet[0]) und _nearly_equal(erhalten[1], erwartet[1]) und _nearly_equal(erhalten[2],
                                                                                                             erwartet[2])
        ergebnisse.append(((Erwachsene, Kinder, Einkommen), erwartet, erhalten, ok))
    return Ergebnisse


if __name__ == "__main__":
    for inputs, erwartet, erhalten, ok in unit_tests():
        print(f"{inputs} -> erwartet={erwartet}, erhalten={erhalten} : {'OK' if ok else 'FEHLER'}")

5.4. Aufgabe 3

Nun bitten wir ChatGPT, die Regeln zur Steuerberechnung im Internet zu suchen:

 

Diesmal stellen wir nicht die PDF-Datei zur Verfügung, die die zu beachtenden Berechnungsregeln enthielt. Wir geben lediglich unsere Anweisungen in der Textdatei an. Zur Erinnerung: Diese Textdatei enthält nun 12 Unit-Tests, nachdem wir zu den ursprünglichen 11 Tests denjenigen hinzugefügt haben, den Gemini verwendet hat, um zu zeigen, dass meine ursprüngliche PDF-Datei fehlerhaft war.

ChatGPT antwortet innerhalb von 8 Minuten und gibt einen Link zum Herunterladen des generierten Skripts an. Sobald dieses Skript in PyCharm geladen ist, besteht es alle 12 Tests. Bei beiden gestellten Aufgaben hat ChatGPT also auf Anhieb richtig geantwortet und damit Gemini übertroffen.

ChatGPT gibt in seiner Antwort seine Quellen an:

 

Da gibt es nichts zu sagen, das ist hervorragende Arbeit.

Nun kann man es, wie zuvor bei Gemini, bitten, ein PDF für Studierende zu generieren.

 

Die Antwort von ChatGPT kam erst nach mehrmaligem Hin und Her, da das generierte PDF eine Schriftart verwendete, die Zeichen durch Quadrate ersetzte. Aber schließlich hat es das PDF generiert. Ich zeige es hier, weil es andere Regeln als das PDF von Gemini enthält und ich mich gefragt habe, wer Recht hat. Das wollen wir untersuchen.

 
 
 

Der Unterschied zum PDF von Gemini liegt in der Berechnung des Abschlags. Die beiden KI-Systeme verfolgen unterschiedliche Ansätze. Gemini hatte geschrieben:

 
 
 

Die beiden KI-Systeme verfolgen zwei unterschiedliche Ansätze. Wer hat Recht?

5.5. Aufgabe 4

Wir werden ChatGPT bitten, sich auf sein PDF zu stützen, um die Steuer zu berechnen:

Wie schon zuvor generiert es ein Python-Skript, das auf Anhieb funktioniert. Wir hatten in den Anweisungen einen zusätzlichen Test hinzugefügt:

test13: (1, 0, 18535) -> (359, 491, 90)

Alle 13 Tests wurden erfolgreich bestanden.

5.6. Zurück zu Gemini

Nun kehren wir zu Gemini zurück, dem wir das PDF von ChatGPT vorlegen werden. Da sich die in diesem PDF implementierten Regeln von denen im PDF von Gemini unterscheiden, stellt sich die Frage, was passieren wird:

 

Gemini hat zunächst ein Python-Skript generiert, das bei den Tests fehlgeschlagen ist. Wir haben ihm die Logs vorgelegt:

Frage 2

 

Frage 3

Es gibt immer noch Fehler. Wir machen weiter.

 

Frage 4

Immer noch Fehler bei der Ausführung:

 

Diesmal ist es richtig.

Es ist dennoch faszinierend, dass beide KI-Systeme bei PDF-Dateien mit recht unterschiedlichen Berechnungsregeln korrekte Ergebnisse liefern.

Wir stellen Gemini folgende Frage:

 

Die vollständige Frage lautet:

Vergleiche nach all dem, was wir getan haben, die beiden Ansätze, deinen und den von ChatGPT, in den PDF-Dateien, die ich dir beifüge [.. laut Gemini] für dich, [.. laut ChatGPT] für ChatGPT. Sag mir, welchen wir bevorzugen sollten, da du bei den 13 generierten Tests alle mit dem einen oder dem anderen PDF erfolgreich bist.

Die Antwort von Gemini ist eindeutig:

 
 
 
 
 
 

5.7. Was denkt ChatGPT darüber?

Wir stellen ChatGPT dieselbe Frage, die wir Gemini gestellt haben.

 

Die Antwort von ChatGPT lautet wie folgt:

 
 

Daraufhin schlägt ChatGPT uns einen Einzeltest vor, um zwischen den beiden Methoden zu entscheiden. Wir duplizieren:

  • Das von Gemini generierte Skript [gemini3], das dessen PDF [Das Problem laut Gemini] als Quelle verwendet, wird in das Skript [gemini4] kopiert;
  • Das von ChatGPT generierte Skript [chatGPT3], das dessen PDF [Das Problem laut ChatGPT] als Quelle verwendet, wird in das Skript [chatGPT4] kopiert;

Außerdem fügen wir in jedes der Skripte [gemini4, chatGPT4] den von ChatGPT vorgeschlagenen Unit-Test ein, um zwischen den beiden KI-Systemen zu entscheiden.

Die Ausführung von [gemini4] liefert folgende Ergebnisse:


C:\Data\st-2025\dev\python\code\python-flask-2025-cours\.venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm 2025.2.1.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py" --path "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py" 
Die Tests begannen um 17:45 Uhr ...
Starten der Unit-Tests mit den Argumenten python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py in C:\Data\st-2025\dev\python\code\python-flask-2025-cours

SubTest-Fehler: Traceback (letzter Aufruf zuletzt):
  Datei „C:\Program Files\Python313\Lib\unittest\case.py“, Zeile 58, in testPartExecutor
    yield
  Datei „C:\Program Files\Python313\Lib\unittest\case.py“, Zeile 556, in subTest
    yield
  Datei „C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py“, Zeile 234, in test_cas_verifies_simulateur_officiel
    self.assertAlmostEqual(Steuerberechnung, erwarteteSteuer, delta=1, msg="Fehler beim Steuerbetrag")
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 2669 != 2270 innerhalb von 1 Delta (399 Differenz): Fehler beim Steuerbetrag




1 Test in 0,010 s ausgeführt

FEHLGESCHLAGEN (Fehler=1)

Ein oder mehrere Untertests sind fehlgeschlagen
Liste der fehlgeschlagenen Untertests: [Test 'test12' mit Eingabe (2, 0, 43333)]

Prozess beendet mit Exit-Code 1

Gemini hat also den von ChatGPT hinzugefügten Test nicht bestanden.

Die Ausführung von [chatGPT4] liefert folgende Ergebnisse:


C:\Data\st-2025\dev\python\code\python-flask-2025-cours\.venv\Scripts\python.exe "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT4.py" 
Test (2, 2, 55555) -> Ergebnis (Steuer=2814, Abschlag=0, Ermäßigung=0) | Erwartet (2815, 0, 0) | OK
Test (2, 2, 50000) -> Ergebnis (Steuer=1384, Abschlag=384, Ermäßigung=347) | Erwartet (1385, 384, 346) | OK
Test (2, 3, 50000) -> Ergebnis (Steuer=0, Abschlag=721, Ermäßigung=0) | Erwartet (0, 720, 0) | OK
Test (1, 2, 100000) -> Ergebnis (Steuer=19884, Abschlag=0, Ermäßigung=0) | Erwartet (19884, 0, 0) | OK
Test (1, 3, 100000) -> Ergebnis (Steuer=16782, Abschlag=0, Ermäßigung=0) | Erwartet (16782, 0, 0) | OK
Test (2, 3, 100000) -> Ergebnis (Steuer=9200, Abschlag=0, Ermäßigung=0) | Erwartet (9200, 0, 0) | OK
Test (2, 5, 100000) -> Ergebnis (Steuer=4230, Abschlag=0, Ermäßigung=0) | Erwartet (4230, 0, 0) | OK
Test (1, 0, 100000) -> Ergebnis (Steuer=22986, Abschlag=0, Ermäßigung=0) | Erwartet (22986, 0, 0) | OK
Test (2, 2, 30000) -> Ergebnis (Steuer=0, Abschlag=0, Ermäßigung=0) | Erwartet (0, 0, 0) | OK
Test (1, 0, 200000) -> Ergebnis (Steuer=64210, Abschlag=0, Ermäßigung=0) | Erwartet (64211, 0, 0) | OK
Test (2, 3, 200000) -> Ergebnis (Steuer=42842, Abschlag=0, Ermäßigung=0) | Erwartet (42843, 0, 0) | OK
Test (2, 2, 49500) -> Ergebnis (Steuer=1296, Abschlag=431, Ermäßigung=325) | Erwartet (1297, 431, 324) | OK
Test (1, 0, 18535) -> Ergebnis (Steuer=359, Abschlag=491, Ermäßigung=90) | Erwartet (359, 491, 90) | OK
Test (2, 0, 43333) -> Ergebnis (Steuer=2268, Abschlag=0, Ermäßigung=401) | Erwartet (2270, 0, 400) | FEHLER
 Details zur Toleranz ±1 €: Steuer ok? False, Abschlag ok? True, Ermäßigung ok? True

Gesamtergebnis: MINDESTENS EIN TEST IST FEHLGESCHLAGEN ❌

Prozess mit Exit-Code 0 beendet

Auch ChatGPT scheitert an dem hinzugefügten Test, jedoch nicht aus denselben Gründen wie Gemini. ChatGPT hat die richtigen Ergebnisse gefunden, allerdings mit einer Abweichung von 2 Euro statt der vorgeschriebenen 1 Euro.

Daher werden wir nun das von ChatGPT generierte PDF für die folgenden KI-Modelle verwenden. Es ist anzumerken, dass beide KI-Modelle die ersten Tests bestanden haben, weil in meinen Anweisungen keine Unit-Tests vorgesehen waren. Daher ist es in diesem konkreten Beispiel wichtig, Unit-Tests für Grenzfälle der Steuerberechnung einzubauen. Da es ziemlich schwierig ist, sich diese Tests selbst vorzustellen, Wir werden die KI-Systeme bitten, diese selbst hinzuzufügen.

5.8. Problem 3 mit von den KI generierten Unit-Tests

Die mit Gemini und ChatGPT erzielten Ergebnisse lassen Zweifel aufkommen. Haben die KI eine allgemeine Lösung gefunden, die alle denkbaren Tests besteht, oder haben sie eine Lösung gefunden, die nur die vorgegebenen Tests besteht? Wir beginnen erneut mit einer Lösung ohne PDF, um die KI zu zwingen, im Internet nach den benötigten Informationen zu suchen. Und wir ändern unsere Anweisungen wie folgt:

 

Die Textdatei [instructionsSansPDF4.txt] enthält bereits 14 vorgegebene Tests. Zu diesen Tests fügen wir die folgenden Anweisungen hinzu:


7 – Du fügst so viele Unit-Tests hinzu, wie nötig sind, um die Grenzfälle der Steuerberechnung zu überprüfen.

Für den Code vervollständigst du das folgende Skript, dem du deine eigenen Tests hinzugefügt hast.

# =========================
# Unit-Tests (Toleranz von ±1 €)
# =========================

TESTS = [
    # (Erwachsene, Kinder, Einkommen) -> (Steuer, Abschlag, Ermäßigung)
    ((2, 2, 55555), (2815, 0, 0)),
    ((2, 2, 50000), (1385, 384, 346)),
    ((2, 3, 50000), (0, 720, 0)),
    ((1, 2, 100000), (19884, 0, 0)),
    ((1, 3, 100000), (16782, 0, 0)),
    ((2, 3, 100000), (9200, 0, 0)),
    ((2, 5, 100000), (4230, 0, 0)),
    ((1, 0, 100000), (22986, 0, 0)),
    ((2, 2, 30000), (0, 0, 0)),
    ((1, 0, 200000), (64211, 0, 0)),
    ((2, 3, 200000), (42843, 0, 0)),
    ((2, 2, 49500), (1297, 431, 324)),
    ((1, 0, 18535), (359, 491, 90)),
    ((2, 0, 43333), (2270, 0, 400)),
]


def _ok(a, b, tol=1):
    return abs(a - b) <= tol


def run_tests(verbose: bool = True) -> bool:
    all_ok = True
    for (params, expected) in TESTS:
        a, e, r = params
        exp_impot, exp_decote, exp_reduc = expected
        res = berechnung_steuer_2019(a, e, r)
        ok_impot = _ok(res.impot, exp_impot)
        ok_decote = _ok(res.decote, exp_decote)
        ok_reduc = _ok(res.reduction, exp_reduc)
        test_ok = ok_impot und ok_decote und ok_reduc
        if verbose:
            print(
                f"Test {params} -> Ergebnis (Steuer={res.impot}, Abschlag={res.decote}, Reduzierung={res.reduction}) | Erwartet {expected} | {'OK' if test_ok else 'FEHLER'}")
            if not test_ok:
                print(
                    f" Details Toleranz ±1 €: Steuer ok? {ok_impot}, Abschlag ok? {ok_decote}, Ermäßigung ok? {ok_reduc}")
        all_ok &= test_ok
    if verbose:
        print("\nGesamtergebnis:", "ALLE TESTS BESTANDEN ✅" if all_ok else "MINDESTENS EIN TEST FEHLGESCHLAGEN ❌")
    return all_ok


if __name__ == "__main__":
    run_tests()
  • Zeilen 11–24: die 14 vorgegebenen Tests;
  • Zeilen 5–55: Dieser Code stammt aus dem von ChatGPT generierten Skript. Wir werden Gemini anweisen, diesen Code zu verwenden, um Vergleiche zwischen den beiden generierten Skripten zu erleichtern.

Wir beginnen mit ChatGPT:

 

Seine erste Antwort ist falsch. Ich teile ihm dies mit und gebe ihm die Ausführungsprotokolle:

Seine zweite Antwort ist richtig. ChatGPT hat die folgenden 11 Tests zu den 14 vorgegebenen Tests hinzugefügt:

# Zusätzliche Grenzfälle (Stufenränder/Rundungen)
TESTS += [
    # Freibetrag 10 %: Unter- und Obergrenze
    ((1, 0, 3000), (0, 0, 0)),  # 10 % = 300 < Untergrenze 437 => niedriges Nettoeinkommen -> keine Steuer
    ((1, 0, 200000), (64211, 0, 0)),  # Obergrenze des Freibetrags bereits in den ursprünglichen Tests abgedeckt

    # Abschlag: knapp unterhalb / oberhalb der Schwellenwerte
    ((1, 0, 25000), None),  # Diagnose
    ((2, 0, 35000), None),  # Diagnose

    # Ermäßigung 20 %: voller Anspruch vs. Begrenzung
    ((1, 0, 17000), None),  # Diagnose
    ((2, 0, 34000), None),  # Diagnose
    ((1, 0, 20000), None),  # Diagnose
    ((2, 0, 40000), None),  # Diagnose

    # Änderung der Anteile (QF-Obergrenze)
    ((2, 1, 80000), None),
    ((2, 2, 80000), None),
    ((2, 3, 80000), None),
]

Es gibt nun 25 Unit-Tests. Ich habe die 11 neuen Tests manuell mit dem offiziellen Simulator der DGIP überprüft und alles ist in Ordnung.

Jetzt geht es weiter mit Gemini. Das wird viel komplizierter. Es wird ihm gelingen, ein Skript zu generieren, das die 25 ChatGPT-Tests besteht, allerdings erst nach langem Debugging.

 

Nachfolgend die Liste der Debugging-Ergebnisse:

 

Seltsamerweise ist ein Großteil der Tests fehlgeschlagen, und zwar unter den 14 vorgeschriebenen, obwohl Gemini in der Vergangenheit Code generiert hatte, der alle bestanden hatte.

Die folgende Antwort von Gemini ist immer noch nicht korrekt:

 

Die folgende Antwort auch nicht:

 

Die folgende Antwort auch nicht. Also ändere ich meine Strategie. Ich bitte es, die 25 Tests zu bestehen, die ChatGPT bestanden hat, und füge die Logs von ChatGPT bei:

 

Gemini scheitert. Es hat die Tests von ChatGPT zwar hinzugefügt. Ich füge die Protokolle seiner Ausführung bei:

 

Immer noch nicht:

 

Immer noch nicht:

 

Immer noch nicht:

 

Immer noch nicht, aber es ist besser:

 

Gemini macht neue Fehler:

 

Es wird wieder besser:

 

Diesmal ist es richtig:

Zweifellos war ChatGPT bei diesem konkreten Beispiel der Berechnung der Steuer für 2019 unter Berücksichtigung der in der Anweisungsdatei festgelegten Vorgaben treffender als Gemini. Aber das ist nur ein Beispiel.

Man kann noch weiter gehen. Man kann Gemini bitten, ein PDF gemäß den Berechnungsregeln neu zu erstellen, die es verwendet hat, um die 25 Tests zu bestehen. Wir wollen sehen, ob es seine ursprüngliche Argumentation bezüglich der Berechnung des Abschlags und der 20-prozentigen Ermäßigung geändert hat:

Diesmal hat Gemini eine Markdown-Datei generiert, die ich anschließend in ein PDF umgewandelt habe [Das Problem nach Gemini Version 2]. Und Gemini hat seine Argumentation tatsächlich geändert:

 
 

Man stellt fest, dass weder die spezielle Berechnung des Abschlags noch die Nachholregel mehr vorhanden sind. Gemini hat nun die Argumentation von ChatGPT übernommen.