docs: README auf aktuellen Stand, Ausgabeordner heisst output/

README: Ordner je Pipeline-Stufe im Diagramm, Eingangstor ehrlich
beschrieben (nur Hinweis), fehlende Umgebungsvariablen
(UNDERSTAND_RETRIES, LLM_REASONING_EFFORT), DATA-Woerterbuch,
aktuelle Report-Reihenfolge, Regel gegen erfundene Bedeutungen,
Benchmark-Ergebnisdateien, neuer Abschnitt "Erweitern".
Ausgabeordner ausgabe/ -> output/ in .gitignore und allen Kommandos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 12:19:58 +02:00
commit cb402996ad
2 changed files with 37 additions and 23 deletions

2
.gitignore vendored
View file

@ -22,7 +22,7 @@ htmlcov/
# Laufzeitausgaben # Laufzeitausgaben
benchmark/results/ benchmark/results/
ausgabe/ output/
*.log *.log
# Editor / IDE / Betriebssystem # Editor / IDE / Betriebssystem

View file

@ -7,28 +7,29 @@ Aufgabentext (+ optional beigelegte Daten: --daten kurse.csv)
│ ohne LLM: Profil je Datei – Zeilen, Spalten, Spannweiten, Lücken │ ohne LLM: Profil je Datei – Zeilen, Spalten, Spannweiten, Lücken
▼ ▼
1. Verstehen ───────── LLM: Teilfragen a)/b)/…, alle Zahlen des Textes mit Fundstelle, 1. Verstehen ───────── LLM: Teilfragen a)/b)/…, alle Zahlen des Textes mit Fundstelle,
│ übergangene Zahlen mit Grund; ist es überhaupt eine Aufgabe? │ understanding/ übergangene Zahlen mit Grund; Hinweis, wenn es keine
│ Optimierungsaufgabe ist (die Pipeline läuft trotzdem weiter)
│ ohne LLM geprüft: keine Zahl, keine Teilfrage übersehen │ ohne LLM geprüft: keine Zahl, keine Teilfrage übersehen
▼ ▼
2. Extraktion ──────── LLM: Problemklasse, Solver-Wahl, Indizes, Parameter, 2. Extraktion ──────── LLM: Problemklasse, Solver-Wahl, Indizes, Parameter,
│ Variablen, Zielfunktion, Restriktionen (LaTeX), Annahmen │ modeling/ Variablen, Zielfunktion, Restriktionen (LaTeX), Annahmen
▼ ▼
3. Konsistenz-Checks ─ ohne LLM: Solver passt zur Klasse? Einheiten? undeklarierte 3. Konsistenz-Checks ─ ohne LLM: Solver passt zur Klasse? Einheiten? undeklarierte
│ Symbole? kamen die erfassten Zahlen im Modell an? │ checks/ Symbole? kamen die erfassten Zahlen im Modell an?
▼ ▼
4. LLM-Prüfung ─────── Zielfunktion = gefragte Größe? Annahmen textkonform? 4. LLM-Prüfung ─────── Zielfunktion = gefragte Größe? Annahmen textkonform?
│ ▲ │ ▲ checks/
▼ │ Korrekturschleife (max. 2 Runden, beste Runde gewinnt) ▼ │ Korrekturschleife (max. 2 Runden, beste Runde gewinnt)
5. Korrektur ┘ 5. Korrektur ┘ modeling/
│ │
▼ ▼
6. Solver ──────────── LLM erzeugt Python-Code, Ausführung in der Sandbox, Reparatur 6. Solver ──────────── LLM erzeugt Python-Code, Ausführung in der Sandbox, Reparatur
│ bei Fehlern; Szenarien = Datenvarianten, ohne LLM nachgerechnet │ solving/ bei Fehlern; Szenarien = Datenvarianten, ohne LLM nachgerechnet
▼ ▼
7. Abnahme ─────────── ohne LLM: jede Teilfrage beantwortet? jede Zahl der Antwort 7. Abnahme ─────────── ohne LLM: jede Teilfrage beantwortet? jede Zahl der Antwort
│ belegt? Lösung zulässig? Tabellensummen richtig? │ solving/ belegt? Lösung zulässig? Eingabedaten ausgewiesen?
▼ ▼
Report (Markdown/HTML) + JSON + eigenständiges Solver-Skript Report (Markdown/HTML) + JSON + eigenständiges Solver-Skript reporting/
``` ```
## Unterstützte Problemklassen ## Unterstützte Problemklassen
@ -68,31 +69,31 @@ Der Anbieter wird am Modellnamen erkannt. Das kostenlose Gemini-Kontingent reich
```bash ```bash
# Aufgabe aus Datei lösen, Report und Solver-Code speichern # Aufgabe aus Datei lösen, Report und Solver-Code speichern
.venv/bin/python TextToModel.py aufgabe.txt --report ausgabe/modell.html --code ausgabe/solve.py > ausgabe/ergebnis.json .venv/bin/python TextToModel.py aufgabe.txt --report output/modell.html --code output/solve.py > output/ergebnis.json
# Aufgabe über stdin # Aufgabe über stdin
echo "Ein Betrieb ..." | .venv/bin/python TextToModel.py --report ausgabe/modell.md echo "Ein Betrieb ..." | .venv/bin/python TextToModel.py --report output/modell.md
# nur modellieren und prüfen, nicht lösen; anderes Modell # nur modellieren und prüfen, nicht lösen; anderes Modell
.venv/bin/python TextToModel.py aufgabe.txt --no-solve --model deepseek/deepseek-v4-pro-0813 .venv/bin/python TextToModel.py aufgabe.txt --no-solve --model deepseek/deepseek-v4-pro-0813
# Report nachträglich aus gespeichertem Ergebnis erzeugen (keine LLM-Kosten) # Report nachträglich aus gespeichertem Ergebnis erzeugen (keine LLM-Kosten)
.venv/bin/python -m reporting.report ausgabe/ergebnis.json ausgabe/modell.md .venv/bin/python -m reporting.report output/ergebnis.json output/modell.md
# exportiertes Solver-Programm erneut ausführen # exportiertes Solver-Programm erneut ausführen
.venv/bin/python ausgabe/solve.py .venv/bin/python output/solve.py
# Variante rechnen: geänderte Eingabedaten, gleiches Programm, KEIN LLM-Aufruf # Variante rechnen: geänderte Eingabedaten, gleiches Programm, KEIN LLM-Aufruf
.venv/bin/python TextToModel.py --resolve ausgabe/ergebnis.json --data neue_daten.json > ausgabe/variante.json .venv/bin/python TextToModel.py --resolve output/ergebnis.json --data neue_daten.json > output/variante.json
# Aufgabe mit beigelegten Daten (CSV/Excel); das Profil geht in die Prompts, die Datei ins Programm # Aufgabe mit beigelegten Daten (CSV/Excel); das Profil geht in die Prompts, die Datei ins Programm
.venv/bin/python TextToModel.py aufgabe.md --daten kurse.csv --report ausgabe/modell.html .venv/bin/python TextToModel.py aufgabe.md --daten kurse.csv --report output/modell.html
# nur die Solver-Stufe wiederholen: Modell und Prüfbericht bleiben, spart die Aufrufe davor # nur die Solver-Stufe wiederholen: Modell und Prüfbericht bleiben, spart die Aufrufe davor
.venv/bin/python TextToModel.py --von solver ausgabe/ergebnis.json > ausgabe/neu.json .venv/bin/python TextToModel.py --von solver output/ergebnis.json > output/neu.json
``` ```
Der Ordner `ausgabe/` ist in `.gitignore` eingetragen. Ohne Eingabe wird eine eingebaute Beispielaufgabe verwendet. Fortschritt und Kosten erscheinen auf stderr, das Ergebnis-JSON auf stdout. Der Ordner `output/` ist in `.gitignore` eingetragen. Ohne Eingabe wird eine eingebaute Beispielaufgabe verwendet. Fortschritt und Kosten erscheinen auf stderr, das Ergebnis-JSON auf stdout.
| Option / Variable | Bedeutung | Standard | | Option / Variable | Bedeutung | Standard |
|---|---|---| |---|---|---|
@ -103,12 +104,14 @@ Der Ordner `ausgabe/` ist in `.gitignore` eingetragen. Ohne Eingabe wird eine ei
| `--von STUFE ERGEBNIS.JSON` | gespeichertes Ergebnis fortsetzen statt alles zu wiederholen: `solver` (nur Solver-Code neu) oder `pruefung` (ab Prüfung/Korrektur) | – | | `--von STUFE ERGEBNIS.JSON` | gespeichertes Ergebnis fortsetzen statt alles zu wiederholen: `solver` (nur Solver-Code neu) oder `pruefung` (ab Prüfung/Korrektur) | – |
| `--max-revisions` / `MAX_REVISIONS` | Korrekturrunden | 2 | | `--max-revisions` / `MAX_REVISIONS` | Korrekturrunden | 2 |
| `--no-solve` | keinen Solver-Code erzeugen | aus | | `--no-solve` | keinen Solver-Code erzeugen | aus |
| `SOLVER_RETRIES` | Reparaturversuche für fehlerhaften Solver-Code | 2 | | `UNDERSTAND_RETRIES` | Nachfassen, wenn die Verstehensstufe Zahlen oder Teilfragen übersehen hat | 1 |
| `SOLVER_RETRIES` | Reparaturversuche für fehlerhaften Solver-Code oder nicht ausgewiesene Eingabedaten | 2 |
| `INFEASIBLE_RECHECK` | Gegenprobe mit unabhängiger Modellierung bei „unlösbar“ (0 schaltet ab) | 1 | | `INFEASIBLE_RECHECK` | Gegenprobe mit unabhängiger Modellierung bei „unlösbar“ (0 schaltet ab) | 1 |
| `LLM_MAX_TOKENS` | Obergrenze je Antwort (OpenRouter) | 32000 | | `LLM_MAX_TOKENS` | Obergrenze je Antwort (OpenRouter) | 32000 |
| `--max-restarts` / `MAX_RESTARTS` | komplette Neustarts, wenn das Modell nach allen Korrekturrunden fehlerhaft ist | 1 | | `--max-restarts` / `MAX_RESTARTS` | komplette Neustarts, wenn das Modell nach allen Korrekturrunden fehlerhaft ist | 1 |
| `OPENROUTER_MIN_THROUGHPUT` | bevorzugte Mindestgeschwindigkeit des OpenRouter-Anbieters (Tokens/s); Präferenz, kein harter Filter | 75 | | `OPENROUTER_MIN_THROUGHPUT` | bevorzugte Mindestgeschwindigkeit des OpenRouter-Anbieters (Tokens/s); Präferenz, kein harter Filter | 75 |
| `LLM_TIMEOUT` | Timeout je LLM-Aufruf (s) | 420 | | `LLM_TIMEOUT` | Timeout je LLM-Aufruf (s) | 420 |
| `LLM_REASONING_EFFORT` | Denkaufwand des Modells über OpenRouter: `low`, `medium` oder `high` | Modellvorgabe |
| `SOLVER_SANDBOX` | Isolierung des erzeugten Codes: `auto` (bubblewrap, wenn vorhanden), `bwrap` (Pflicht), `aus` | `auto` | | `SOLVER_SANDBOX` | Isolierung des erzeugten Codes: `auto` (bubblewrap, wenn vorhanden), `bwrap` (Pflicht), `aus` | `auto` |
| `LLM_MODEL_EXTRACTION`, `LLM_MODEL_REVIEW`, `LLM_MODEL_REVISION`, `LLM_MODEL_SOLVER` | Modell einzelner Pipeline-Stufen, z. B. ein schnelles für den Solver-Code; verschiedene Anbieter sind erlaubt | Modell aus `--model` | | `LLM_MODEL_EXTRACTION`, `LLM_MODEL_REVIEW`, `LLM_MODEL_REVISION`, `LLM_MODEL_SOLVER` | Modell einzelner Pipeline-Stufen, z. B. ein schnelles für den Solver-Code; verschiedene Anbieter sind erlaubt | Modell aus `--model` |
@ -118,16 +121,16 @@ Das erzeugte Programm (`--code` sowie im HTML-/Markdown-Report) ist **separat ve
1. `#!/usr/bin/env python3` 1. `#!/usr/bin/env python3`
2. Docstring mit **Aufgabentext**, **mathematischem Modell** (Indizes, Parameter, Variablen, Zielfunktion, Restriktionen – mit Erläuterungen und Annahmen) und dem **Algorithmus** samt Begründung, warum er hier passt 2. Docstring mit **Aufgabentext**, **mathematischem Modell** (Indizes, Parameter, Variablen, Zielfunktion, Restriktionen – mit Erläuterungen und Annahmen) und dem **Algorithmus** samt Begründung, warum er hier passt
3. kommentierter Python-Code: Imports, Eingabedaten als Konstanten, Modellfunktion(en), `main()` 3. kommentierter Python-Code: Imports, alle Eingabezahlen im Wörterbuch `DATA` (austauschbar, siehe `--resolve`), Modellfunktion(en), `main()`
4. `if __name__ == "__main__": main()` 4. `if __name__ == "__main__": main()`
Die Ausgabe ist lesbar – Status, Zielwert, **verständliche Ergebnistabellen** (z. B. je Aktie: Anteil, Rendite, Kosten, Beitrag zum Ziel, Summenzeile) und die Variablenwerte – und endet mit einer maschinenlesbaren `RESULT_JSON`-Zeile. Die Tabellen berechnet das Programm aus der tatsächlichen Lösung; die Pipeline prüft automatisch, dass die Beiträge sich zum Zielwert summieren, und warnt sonst. Der Report beginnt mit dem **Ergebnis**: ein Antwortsatz, die Kennzahlen, die Ergebnistabellen samt **Diagramm** (Balken je Objekt, Wasserfall für die Zielwert-Zerlegung aus Ertrag, Risiko und Kosten) und eine Tabelle der **bindenden Restriktionen**. Darunter folgen die Rohwerte nach Variablen gruppiert, danach Aufgabe, Modell, Prüfbericht und Programm. Die Diagramme sind reines SVG ohne externe Bibliothek und funktionieren offline, hell wie dunkel. Im HTML-Report lässt sich das Programm mit einem Klick auf das Kopier-Symbol in die Zwischenablage übernehmen. Das JSON-Ergebnis enthält weiterhin nur den rohen generierten Code. Die Ausgabe ist lesbar – Status, Zielwert, **verständliche Ergebnistabellen** (z. B. je Aktie: Anteil, Rendite, Kosten, Beitrag zum Ziel, Summenzeile) und die Variablenwerte – und endet mit einer maschinenlesbaren `RESULT_JSON`-Zeile. Die Tabellen berechnet das Programm aus der tatsächlichen Lösung; die Pipeline prüft automatisch, dass die Beiträge sich zum Zielwert summieren, und warnt sonst. Der Report beginnt mit dem **Ergebnis** und erklärt, bevor er Tabellen zeigt: Antwortsatz → **Antwort je Teilfrage** → Status und Kennzahlen → Warnkästen → „Wie das Modell die Aufgabe sieht“ (Erklärung in Alltagssprache) → Graphenzeichnung (bei Wegeproblemen) → Ergebnistabellen samt **Diagramm** (Balken je Objekt, Wasserfall für die Zielwert-Zerlegung) und **bindende Restriktionen** → verglichene Konfigurationen → Sensitivität → Stabilität über Startpunkte → Rohwerte nach Variablen gruppiert. Danach folgen beigelegte Daten, „Was in der Aufgabe steht“ (Teilfragen und Zahlen), Aufgabe, Modell, Prüfbericht und Programm. Die Diagramme sind reines SVG ohne externe Bibliothek und funktionieren offline, hell wie dunkel. Im HTML-Report lässt sich das Programm mit einem Klick auf das Kopier-Symbol in die Zwischenablage übernehmen. Das JSON-Ergebnis enthält weiterhin nur den rohen generierten Code.
**Mit Datenanhängen (`--daten`):** Das erzeugte Programm liest die Dateien über ihren Dateinamen aus dem Arbeitsverzeichnis. Ein mit `--code` exportiertes Programm braucht die Datendateien deshalb **im selben Verzeichnis** – sonst endet es mit `FileNotFoundError`. Kopiere sie mit: **Mit Datenanhängen (`--daten`):** Das erzeugte Programm liest die Dateien über ihren Dateinamen aus dem Arbeitsverzeichnis. Ein mit `--code` exportiertes Programm braucht die Datendateien deshalb **im selben Verzeichnis** – sonst endet es mit `FileNotFoundError`. Kopiere sie mit:
```bash ```bash
.venv/bin/python TextToModel.py aufgabe.md --daten kurse.csv --code ausgabe/solve.py .venv/bin/python TextToModel.py aufgabe.md --daten kurse.csv --code output/solve.py
cp kurse.csv ausgabe/ && (cd ausgabe && ../.venv/bin/python solve.py) cp kurse.csv output/ && (cd output && ../.venv/bin/python solve.py)
``` ```
@ -147,6 +150,7 @@ Das LLM kann Fehler machen – deshalb gibt es mehrere Sicherungen:
- **Jede Zahl der Aufgabe erfasst:** Zahlen des Aufgabentexts müssen als Fakt mit Fundstelle oder mit Begründung als irrelevant ausgewiesen sein – sonst betreten sie die Pipeline erst im Solver-Code, hinter allen Prüfungen. - **Jede Zahl der Aufgabe erfasst:** Zahlen des Aufgabentexts müssen als Fakt mit Fundstelle oder mit Begründung als irrelevant ausgewiesen sein – sonst betreten sie die Pipeline erst im Solver-Code, hinter allen Prüfungen.
- **Selbst erzeugte Daten sind gekennzeichnet:** Erzeugt das Programm Eingabedaten (Zufallszahlen, synthetischer Markt, verkleinertes Universum), muss das in `data_assumptions` stehen, nicht nur im Fließtext – sonst folgt eine Reparatur und notfalls die Markierung „unzuverlässig“. - **Selbst erzeugte Daten sind gekennzeichnet:** Erzeugt das Programm Eingabedaten (Zufallszahlen, synthetischer Markt, verkleinertes Universum), muss das in `data_assumptions` stehen, nicht nur im Fließtext – sonst folgt eine Reparatur und notfalls die Markierung „unzuverlässig“.
- **Szenarien nachgerechnet:** Vergleicht die Aufgabe mehrere Konfigurationen, wird jede durch erneutes Ausführen des Programms bestätigt – ohne LLM. - **Szenarien nachgerechnet:** Vergleicht die Aufgabe mehrere Konfigurationen, wird jede durch erneutes Ausführen des Programms bestätigt – ohne LLM.
- **Keine erfundene Bedeutung (Prompt-Regel, nicht automatisch geprüft):** Nennt der Text keine Bedeutung – etwa bei einem reinen Formelsatz –, erklärt das Modell mathematisch (Mindest-/Höchstbedingungen) und sagt, dass die Bedeutung fehlt, statt ein Produktionsproblem zu erfinden.
- **Sandbox:** Der erzeugte Code läuft ohne Netzzugang, mit nur lesbarem Dateisystem und beschreibbar allein dem Arbeitsverzeichnis des Laufs. - **Sandbox:** Der erzeugte Code läuft ohne Netzzugang, mit nur lesbarem Dateisystem und beschreibbar allein dem Arbeitsverzeichnis des Laufs.
- **Kein falsches „optimal“:** Bleibt das Modell fehlerhaft, wird das Ergebnis als **unzuverlässig** markiert – bei falscher Zielgröße ohne Solver-Lauf, mit Warnkasten im Report und Exit-Code 2. - **Kein falsches „optimal“:** Bleibt das Modell fehlerhaft, wird das Ergebnis als **unzuverlässig** markiert – bei falscher Zielgröße ohne Solver-Lauf, mit Warnkasten im Report und Exit-Code 2.
@ -154,7 +158,7 @@ Trotzdem gilt: **Ergebnisse vor Verwendung prüfen** – der Report zeigt dafür
### Benchmark ### Benchmark
`benchmark/cases/` enthält 19 Aufgaben mit **unabhängig verifiziertem Optimum** (Brute Force bzw. zweiter Solver) – von kleinen LPs bis zu Aufgaben mit eingebauten Fallen (Rüstkosten/Mindestlos, Wenn-dann-Regel als Implikation, Volatilität statt Varianz, nichtkonvexes NLP mit globalem Optimum, beweisbar unlösbare Mischung, Losgrößen als kürzester Weg). Gemessen wird der **Zielwert** und, strenger, ob die **Aufgabe erfüllt** ist: jede Teilfrage beantwortet, jede Zahl der Antwort belegt. Dateien im Fallverzeichnis außer `task.txt`/`expected.json` werden als Datenanhang übergeben. `benchmark/cases/` enthält 19 Aufgaben mit **unabhängig verifiziertem Optimum** (Brute Force bzw. zweiter Solver) – von kleinen LPs bis zu Aufgaben mit eingebauten Fallen (Rüstkosten/Mindestlos, Wenn-dann-Regel als Implikation, Volatilität statt Varianz, nichtkonvexes NLP mit globalem Optimum, beweisbar unlösbare Mischung, Losgrößen als kürzester Weg). Gemessen wird der **Zielwert** und, strenger, ob die **Aufgabe erfüllt** ist: jede Teilfrage beantwortet, jede Zahl der Antwort belegt. Dateien im Fallverzeichnis außer `task.txt`/`expected.json` werden als Datenanhang übergeben. Ergebnisse landen als Tabelle auf stdout und im Detail in `benchmark/results/<Zeitstempel>.json`; jede Zeile hält Commit und eine Kennung der Prompts fest, damit Messungen vergleichbar bleiben.
```bash ```bash
.venv/bin/python -m benchmark.benchmark --models deepseek/deepseek-v4.1-flash deepseek/deepseek-v4-flash-0731 --runs 2 --jobs 3 .venv/bin/python -m benchmark.benchmark --models deepseek/deepseek-v4.1-flash deepseek/deepseek-v4-flash-0731 --runs 2 --jobs 3
@ -188,6 +192,16 @@ Im Wurzelverzeichnis liegt nur das Hauptprogramm; jede Pipeline-Stufe liegt mit
| `tasks/` | weitere Aufgaben zum Ausprobieren (u. a. mit fehlenden Daten) | | `tasks/` | weitere Aufgaben zum Ausprobieren (u. a. mit fehlenden Daten) |
| `tests/` | Offline-Tests (`pytest`, Konfiguration in `pyproject.toml`) | | `tests/` | Offline-Tests (`pytest`, Konfiguration in `pyproject.toml`) |
## Erweitern
Einige Stellen müssen gemeinsam geändert werden – Vollständigkeitstests schlagen fehl, wenn etwas fehlt:
- **Neuer Solver oder Problemtyp:** Enum in `core/model_schema.py`, Routing-Regel in `modeling/Routing-und-Extraktions-Prompt.txt`, `ALLOWED_STACKS` in `checks/model_checks.py`, API-Abschnitt in `solving/Solver-Prompt.txt` (API vorher real testen und in `tests/test_solver_stage.py` absichern), `ALLOWED_MODULES` in `solving/solver_stage.py`, `ALGORITHMS`/`CLASS_FIT` in `solving/script_export.py`, `requirements.txt`.
- **Neues Prüfurteil oder Prüffeld:** `core/model_schema.py`, `checks/Pruef-Prompt.txt`, `tests/test_review.py`.
- **Feldbeschreibungen** in `core/model_schema.py` gehen als Anweisung an das LLM – eine Änderung dort wirkt wie eine Prompt-Änderung.
- **Prüfkennungen:** Endet eine Prompt-Regel mit `[→ NAME]`, muss es einen Check-Code oder eine Prüffunktion dieses Namens geben (Test `test_every_checker_marker_in_the_prompts_exists`).
- Keine Datei `instructor.py` anlegen – sie überschattet das gleichnamige Paket.
## Sicherheitshinweis ## Sicherheitshinweis
Der Solver-Code wird vom LLM erzeugt. Er läuft in einer **bubblewrap-Sandbox**: kein Netzzugang, das Dateisystem nur lesbar, beschreibbar allein das Temp-Verzeichnis des jeweiligen Laufs, `HOME` zeigt ebenfalls dorthin. Davor greifen weiterhin eine Import-Whitelist (nur Mathe- und Solver-Bibliotheken), das Verbot von `open`/`eval`/`exec` und ein Timeout. Der Solver-Code wird vom LLM erzeugt. Er läuft in einer **bubblewrap-Sandbox**: kein Netzzugang, das Dateisystem nur lesbar, beschreibbar allein das Temp-Verzeichnis des jeweiligen Laufs, `HOME` zeigt ebenfalls dorthin. Davor greifen weiterhin eine Import-Whitelist (nur Mathe- und Solver-Bibliotheken), das Verbot von `open`/`eval`/`exec` und ein Timeout.