text_to_solver_program/BEDIENUNGSANLEITUNG.md
dschlueter d6bad28cb2 feat: Benchmarkklasse „ehrlich scheitern“ – bewertet Verhalten statt Zielwert
expected.json kann statt objective_value eine Liste "behavior" nennen
(rejected | unreliable | assumed_data), eines genügt. Treffer = Verhalten gezeigt,
erfüllt = zusätzlich alle Teilfragen beantwortet (außer bei Ablehnung). Bei erwarteter
Ablehnung wird kein Routing bewertet.

Vier Fälle: reine Gleichung, reine Rechenaufgabe (-> abgelehnt), fehlende Kursdaten,
fehlender Kundenbedarf (-> Annahme ausgewiesen oder unzuverlässig). Erste Messung
20260918-134339: 3/4; der fehlende Bedarf wird im Text parametrisch richtig behandelt,
maschinell aber als „optimal 270, zuverlässig“ ohne ausgewiesene Annahme gemeldet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:53:02 +02:00

535 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bedienungsanleitung – Math Model Generator & Solver
Diese Anleitung beschreibt Schritt für Schritt, wie man das Programm installiert, eine Aufgabe lösen lässt, das Ergebnis liest und ohne erneute LLM-Kosten weiterarbeitet. Einen Überblick über Aufbau und Idee gibt die [README](README.md); die Abwägung hinter der Bauweise steht in [docs/Konzeptionelle_Fragen_und_Antworten.md](docs/Konzeptionelle_Fragen_und_Antworten.md).
## Inhalt
1. [Was das Programm tut](#1-was-das-programm-tut)
2. [Installation](#2-installation)
3. [Schnellstart](#3-schnellstart)
4. [Aufgaben gut formulieren](#4-aufgaben-gut-formulieren)
5. [Aufrufvarianten](#5-aufrufvarianten)
6. [Was das Programm ausgibt](#6-was-das-programm-ausgibt)
7. [Das Ergebnis richtig lesen](#7-das-ergebnis-richtig-lesen)
8. [Aufgaben mit Datendateien und Abbildungen](#8-aufgaben-mit-datendateien-und-abbildungen)
9. [Weiterarbeiten ohne neue LLM-Kosten](#9-weiterarbeiten-ohne-neue-llm-kosten)
10. [Kosten und Laufzeit steuern](#10-kosten-und-laufzeit-steuern)
11. [Alle Einstellungen im Überblick](#11-alle-einstellungen-im-überblick)
12. [Benchmark und Tests](#12-benchmark-und-tests)
13. [Fehlerbehebung](#13-fehlerbehebung)
14. [Sicherheit und Datenschutz](#14-sicherheit-und-datenschutz)
15. [Grenzen](#15-grenzen)
---
## 1. Was das Programm tut
Sie geben eine Optimierungsaufgabe in deutscher Sprache ein – etwa „Wie viele Tonnen von X und Y sollen gekauft werden, damit die Kosten minimal sind?“. Das Programm
1. **versteht** die Aufgabe: zerlegt sie in Teilfragen und erfasst jede Zahl des Textes,
2. **modelliert** sie: Problemklasse, passender Solver, Variablen, Zielfunktion, Restriktionen, Annahmen,
3. **prüft** das Modell – teils ohne LLM, teils mit einer zweiten LLM-Prüfung – und **korrigiert** es,
4. **löst** es: ein LLM schreibt ein Python-Programm, das in einer abgeschotteten Umgebung mit einem echten Solver rechnet,
5. **nimmt das Ergebnis ab**: setzt die Lösung in alle Restriktionen ein, prüft die Antworten auf belegte Zahlen und Einheiten und kennzeichnet unzuverlässige Ergebnisse.
Sie erhalten einen **Report** (HTML oder Markdown) mit Antwort, Erklärung, Tabellen, Diagrammen, Modell und Prüfbericht, das **Ergebnis als JSON** und auf Wunsch das **Solver-Programm** als eigenständiges Python-Skript.
Unterstützt werden lineare, ganzzahlige, quadratische und nichtlineare Optimierung, Constraint Programming (Schicht- und Personalplanung) und kürzeste Wege. Die Tabelle der Problemklassen steht in der [README](README.md#unterstützte-problemklassen).
---
## 2. Installation
### Voraussetzungen
- **Linux** und **Python ≥ 3.10**
- **bubblewrap** für die Sandbox des erzeugten Codes:
```bash
sudo apt install bubblewrap
```
- für nichtlineare Aufgaben die **Ipopt-Bibliothek**, falls pip kein fertiges Paket für `cyipopt` findet:
```bash
sudo apt install coinor-libipopt-dev pkg-config
```
- ein **API-Schlüssel** für OpenRouter (Standard) oder Google Gemini
### Einrichten
Im Projektverzeichnis:
```bash
python -m venv .venv
.venv/bin/pip install -r requirements.txt
```
Alle Befehle dieser Anleitung rufen Python über `.venv/bin/python` auf; ein Aktivieren der Umgebung ist nicht nötig.
### API-Schlüssel setzen
Den Schlüssel **nur als Umgebungsvariable** setzen, nie in eine Datei im Projekt schreiben:
```bash
export OPENROUTER_API_KEY="sk-or-…" # OpenRouter (Standard)
export GEMINI_API_KEY="…" # nur für Gemini-Modelle
```
Welcher Anbieter benutzt wird, entscheidet der Modellname: mit `/` (z. B. `deepseek/deepseek-v4.1-flash`) geht die Anfrage an OpenRouter, ohne `/` (z. B. `gemini-2.5-flash`) an Gemini. Damit der Schlüssel in jeder neuen Shell gesetzt ist, gehört die `export`-Zeile in `~/.bashrc` oder in einen Passwortmanager.
### Installation prüfen
```bash
.venv/bin/python -m pytest -q # alle Tests, offline, kostenlos
.venv/bin/python -m pytest -q -k sandbox # Sandbox: Schreiben außerhalb des Laufs muss scheitern
```
Beide Befehle müssen ohne Fehler durchlaufen. Die Tests brauchen keinen API-Schlüssel.
---
## 3. Schnellstart
Eine kleine Beispielaufgabe liegt bei. Lösen, Report und Programm speichern:
```bash
mkdir -p output
.venv/bin/python TextToModel.py benchmark/cases/lp_klein/task.txt \
--report output/lp_klein.html --code output/lp_klein.py > output/lp_klein.json
echo $? # 0 = Ergebnis zuverlässig (direkt danach abfragen)
```
Während des Laufs zeigt das Terminal den Fortschritt (Verstehen, Extraktion, Prüfung, Solver …) und am Ende Kosten und Laufzeit je Stufe. Die Aufgabe kostet etwa zwei Cent und dauert ein bis drei Minuten.
Danach:
```bash
xdg-open output/lp_klein.html # Report im Browser öffnen
.venv/bin/python output/lp_klein.py # Solver-Programm erneut ausführen, ohne LLM
```
Erwartetes Ergebnis: Einkaufskosten von **9 Tsd. Euro** (1 Tonne X, 3 Tonnen Y).
---
## 4. Aufgaben gut formulieren
Die Qualität des Ergebnisses hängt stark vom Aufgabentext ab. Bewährt hat sich:
| Empfehlung | Warum |
|---|---|
| **Alle Zahlen vollständig angeben**, am besten als Tabelle | Fehlende Werte muss das Programm annehmen; das wird zwar gekennzeichnet, macht das Ergebnis aber zur Schätzung. |
| **Einheiten ausdrücklich nennen** („in Tausend Euro“, „in Stunden“, „Rendite in %“) | Das Programm übernimmt Zahlen so, wie sie dastehen. „6,9“ ohne Einheit bleibt 6,9 – nicht 0,069. Antworten dürfen nur Einheiten nennen, die im Text stehen. |
| **Das Ziel eindeutig formulieren** („Kosten minimieren“, „Volatilität minimieren“) | Die Prüfstufe kontrolliert, ob die Zielfunktion genau die gefragte Größe liefert – z. B. Volatilität statt Varianz. |
| **Teilfragen markieren** (`a)`, `b)`, `Teil 2`) | Jede erkannte Teilfrage bekommt eine eigene Antwort. Fehlt eine, gilt die Aufgabe als nicht erfüllt. |
| **Bedingungen genau sagen**: „wenn A, dann B“ ist etwas anderes als „A genau dann, wenn B“ | Unklare Stellen werden als Annahme mit verworfener zweiter Lesart ausgewiesen – besser ist, wenn es keine gibt. |
| **Ganzzahligkeit nennen** („nur ganze Stück“, „Mengen beliebig teilbar“) | Entscheidet über Problemklasse und Solver. |
| **Externe Daten beilegen** statt auf Webquellen zu verweisen | Das Solver-Programm hat keinen Netzzugang. Verlangt die Aufgabe z. B. Kursdaten aus dem Internet, erzeugt es Ersatzdaten und kennzeichnet das. |
Die Aufgabe kann als `.txt` oder `.md` vorliegen; Markdown-Tabellen und Formeln wie `min z = 17*x1 + 23*x2` werden verstanden.
**Was abgelehnt wird:** Texte, in denen nichts zu entscheiden ist – etwa eine Gleichung, die nur ausgerechnet werden soll. Das Programm bricht dann nach der ersten Stufe ab (Exit-Code 3) und nennt im Report den Grund. Zulässigkeitsfragen („Ist dieser Plan überhaupt machbar?“) gelten dagegen als Aufgabe.
---
## 5. Aufrufvarianten
```bash
# Aufgabe aus einer Datei
.venv/bin/python TextToModel.py aufgabe.md --report output/aufgabe.html > output/aufgabe.json
# Aufgabe über die Standardeingabe
echo "Ein Betrieb …" | .venv/bin/python TextToModel.py --report output/aufgabe.md > output/aufgabe.json
# ohne Aufgabendatei und ohne umgeleitete Eingabe (oder bei leerer Eingabe): eingebaute Beispielaufgabe (Schichtplanung)
.venv/bin/python TextToModel.py --report output/beispiel.html > output/beispiel.json
# nur modellieren und prüfen, keinen Solver-Code erzeugen (billiger, schneller)
.venv/bin/python TextToModel.py aufgabe.md --no-solve --report output/modell.html > output/modell.json
# anderes Modell
.venv/bin/python TextToModel.py aufgabe.md --model deepseek/deepseek-v4-pro-0813 > output/aufgabe.json
# mit beigelegten Daten (siehe Abschnitt 8)
.venv/bin/python TextToModel.py aufgabe.md --daten kurse.csv --report output/aufgabe.html > output/aufgabe.json
```
Hinweise:
- `--report` braucht die Endung `.html`, `.htm` oder `.md`.
- `--code` ist mit `--no-solve` nicht möglich – es entsteht dann kein Programm.
- Das **Ergebnis-JSON geht auf die Standardausgabe**. Ohne `> datei.json` erscheint es im Terminal und ist danach für `--resolve` und `--von` nicht verfügbar. Es empfiehlt sich, es immer umzuleiten.
- Der Ordner `output/` ist von Git ausgeschlossen und eignet sich für alle Ergebnisse.
---
## 6. Was das Programm ausgibt
### Terminal (Standardfehlerausgabe)
Der Fortschritt jeder Stufe, Befunde der Prüfungen, Reparaturversuche und am Ende eine Übersicht:
```text
LLM-Nutzung: 5 Aufrufe, 36874 Input- (davon 12000 aus dem Cache, 33 %)/53898 Output-Tokens (davon 31152 Reasoning), Kosten $0.0738, Anbieter: {...}
Verstehen 1x 20.8 s 4044 Output-Tokens (davon 2588 Reasoning) 5100 Input (Cache 0)
Extraktion 1x 42.0 s 7051 Output-Tokens …
```
Die Zeilen je Stufe zeigen, wo Zeit und Geld hingehen. Erscheint `ERGEBNIS UNZUVERLÄSSIG – …`, steht der Grund direkt dahinter.
### Ergebnis-JSON (Standardausgabe)
Das vollständige Ergebnis aller Stufen. Die wichtigsten Felder:
| Feld | Bedeutung |
|---|---|
| `reliable` | `true`, wenn keine Prüfung einen ernsten Befund hinterlassen hat |
| `reliability_issues` | Gründe, warum das Ergebnis unzuverlässig ist |
| `rejected` | `true`, wenn der Text keine Optimierungsaufgabe ist |
| `understanding` | erkannte Teilfragen (`sub_questions`) und Zahlen (`facts`) |
| `specification` | das Modell: Klasse, Solver, Variablen, Zielfunktion, Restriktionen, Annahmen, Erklärung |
| `review` | Prüfbericht zu Zielgröße und Annahmen |
| `solver_code.code` | das erzeugte Programm (roh) |
| `solver_code.data_assumptions` | Daten, die nicht im Text standen und angenommen wurden |
| `solver_run.status` | `optimal`, `feasible`, `infeasible`, `unbounded` oder `error` |
| `solver_run.objective_value` | Zielwert |
| `solver_run.answers` | eine Antwort je Teilfrage |
| `solver_run.tables` | Ergebnistabellen |
| `solver_run.warnings` | Hinweise der Abnahme (kein Grund für „unzuverlässig“) |
| `solver_run.data` | alle Eingabewerte des Programms – Grundlage für `--resolve --data` |
| `usage` | Aufrufe, Tokens, Cache-Anteil, Kosten, Anbieter, Zeit je Stufe |
Schnell nachsehen, ohne Zusatzwerkzeuge:
```bash
.venv/bin/python -c "import json; d=json.load(open('output/aufgabe.json')); r=d['solver_run']; \
print(d['reliable'], r['status'], r['objective_value']); [print(a['question_id'], a['text']) for a in r['answers']]"
```
### Exit-Code
| Code | Bedeutung |
|---|---|
| `0` | Ergebnis zuverlässig |
| `2` | Ergebnis vorhanden, aber **unzuverlässig** – Gründe in `reliability_issues` und im Warnkasten des Reports |
| `3` | **abgelehnt** – keine Optimierungsaufgabe, nichts gerechnet |
| andere | Programmfehler oder Abbruch (z. B. ungültiger API-Schlüssel), Meldung im Terminal |
Damit lässt sich das Programm in Skripten einsetzen:
```bash
.venv/bin/python TextToModel.py aufgabe.md > output/aufgabe.json
case $? in
0) echo "zuverlässig" ;;
2) echo "unzuverlässig – bitte Report prüfen" ;;
3) echo "keine Optimierungsaufgabe" ;;
esac
```
### Report
- **HTML** (`--report datei.html`): im Browser öffnen. Formeln werden mit MathJax gesetzt – dafür braucht der Browser beim Öffnen Internet. Diagramme sind eingebettet und funktionieren offline, im hellen und dunklen Modus. Das Solver-Programm lässt sich per Kopier-Symbol übernehmen.
- **Markdown** (`--report datei.md`): z. B. in VS Code mit Strg+Umschalt+V als Vorschau; Formeln stehen als `$…$`.
Einen Report kann man auch **nachträglich** aus einem gespeicherten Ergebnis erzeugen – ohne LLM-Kosten, auch im anderen Format:
```bash
.venv/bin/python -m reporting.report output/aufgabe.json output/aufgabe.md
```
### Solver-Programm (`--code`)
Ein eigenständiges, ausführbares Python-Skript: Docstring mit Aufgabe, Modell und Begründung des Verfahrens, alle Eingabewerte im Wörterbuch `DATA`, kommentierter Code, `main()`. Es gibt Status, Zielwert, Tabellen und Variablenwerte lesbar aus und endet mit einer maschinenlesbaren Zeile `RESULT_JSON: {...}`.
```bash
.venv/bin/python output/aufgabe.py
```
Man kann `DATA` im Skript von Hand ändern und neu rechnen – eleganter geht es mit `--resolve` (Abschnitt 9).
---
## 7. Das Ergebnis richtig lesen
Der Report beginnt mit dem Ergebnis und erklärt, bevor er Tabellen zeigt. Empfohlene Lesereihenfolge:
1. **Antwortsatz und Antworten auf die Aufgabe** – eine je Teilfrage. Jede Zahl darin stammt aus dem Ergebnis oder dem Aufgabentext.
2. **Status und Kennzahlen.**
3. **Warnkästen (⚠) – immer lesen.** „Ergebnis unzuverlässig – bitte nicht verwenden“ bedeutet genau das.
4. **„Wie das Modell die Aufgabe sieht“** – die Erklärung in Alltagssprache. Stimmt sie nicht mit Ihrer Absicht überein, ist das Modell falsch, auch wenn die Zahl plausibel aussieht.
5. **Ergebnistabellen und Diagramme**, bindende Restriktionen (welche Grenzen voll ausgeschöpft sind).
6. **Sensitivität** – was eine Lockerung einer Grenze bringen würde.
7. Weiter unten: **„Was in der Aufgabe steht“** (erkannte Teilfragen und Zahlen), **Modell mit Annahmen**, **Prüfbericht**, **Programm**.
### Wann ein Ergebnis als unzuverlässig gilt
| Grund (`reliability_issues`) | Bedeutung | Was tun |
|---|---|---|
| Zielgröße passt nicht | Das Modell optimiert etwas anderes als gefragt; es wurde **kein** Solver gestartet. | Ziel im Aufgabentext eindeutiger formulieren und neu starten. |
| Check-Fehler im Modell | z. B. Solver passt nicht zur Klasse, gemischte Einheiten in der Zielfunktion | Report „Prüfbericht“ ansehen; oft hilft `--von pruefung`. |
| Zulässigkeitsprüfung | Die gemeldete Lösung verletzt Restriktionen, Schranken oder Ganzzahligkeit. | `--von solver` versuchen. |
| Datenherkunft | Das Programm hat Eingabedaten erzeugt, ohne sie auszuweisen. | Daten beilegen (`--daten`) oder fehlende Werte in den Text schreiben. |
| Szenario ohne Ergebnis | Eine verglichene Konfiguration hat keinen Wert, ist aber nicht als unlösbar belegt (z. B. Zeitlimit). | `--von solver`. |
| Unlösbar, Gegenprobe uneindeutig | Der Solver meldet „unlösbar“, eine unabhängige zweite Modellierung konnte das weder bestätigen noch widerlegen. | Aufgabe auf widersprüchliche Bedingungen prüfen. |
### Besondere Ergebnisse
- **„infeasible“ (unlösbar)** wird nicht einfach geglaubt: Eine zweite, unabhängige Modellierung prüft es. Bestätigt sie es, steht im JSON `infeasible_confirmed: true`, und die Antworten erklären, welche Bedingungen sich widersprechen.
- **Angenommene Daten** stehen im Report unter „Angenommene Daten“ und in `solver_code.data_assumptions`. Das Ergebnis beruht dann nicht allein auf Ihren Angaben.
- **Nichtlineare Aufgaben (Ipopt)** werden von mehreren Startpunkten aus gelöst. Streuen die Ergebnisse, ist das gefundene Optimum nur die beste gefundene *lokale* Lösung – der Abschnitt „Stabilität über mehrere Startpunkte“ zeigt das.
- **Warnungen** (`solver_run.warnings`) sind Hinweise, keine Fehler – etwa eine Tabelle, deren Summe nicht zum Zielwert passt, oder ein Fachbegriff, der nicht erklärt wurde.
---
## 8. Aufgaben mit Datendateien und Abbildungen
Für größere Datenmengen (Kurse, Bedarfe, Entfernungen) legt man Dateien bei:
```bash
.venv/bin/python TextToModel.py aufgabe.md --daten kurse.csv entfernungen.xlsx \
--report output/aufgabe.html --code output/aufgabe.py > output/aufgabe.json
```
- Unterstützt werden **CSV** (Trennzeichen Komma, Semikolon oder Tabulator werden erkannt, deutsche Dezimalkommas ebenfalls) und **Excel** (`.xlsx`, `.xls`).
- Das Programm erstellt **ohne LLM** ein Profil je Datei (Zeilen, Spalten, Typen, Spannweiten, Lücken, Beispielwerte). Nur dieses Profil geht an das LLM, nicht der vollständige Inhalt.
- Die Dateien selbst werden ins Arbeitsverzeichnis des Solver-Programms kopiert; das Programm liest sie über ihren Dateinamen.
- Im Aufgabentext auf die Datei Bezug nehmen („Die Kurse stehen in kurse.csv“).
**Exportiertes Programm mit Daten ausführen:** Es erwartet die Dateien im selben Verzeichnis, sonst endet es mit `FileNotFoundError`:
```bash
cp kurse.csv entfernungen.xlsx output/
(cd output && ../.venv/bin/python aufgabe.py)
```
---
### Aufgaben mit Abbildungen (z. B. aus docling)
Stehen Daten nur in einer Abbildung – ein Graph mit bewerteten Pfeilen, eine Tabelle als Bild –, kann die Aufgabe die Abbildung als Markdown-Bild enthalten. Beide Formen, die docling erzeugt, werden erkannt:
- **eingebettet** (`ImageRefMode.EMBEDDED`, Standard): `![Image](data:image/png;base64,…)`
- **als Datei** (`ImageRefMode.REFERENCED`): `![Image](bilder/graph.png)` – der Pfad gilt relativ zur Aufgabendatei
```bash
# Dokument mit docling nach Markdown wandeln (Bilder eingebettet), dann lösen
docling aufgabe.pdf --to md --image-export-mode embedded --output output/
.venv/bin/python TextToModel.py output/aufgabe.md --report output/aufgabe.html > output/aufgabe.json
```
Was dabei passiert:
1. Die Bilder werden aus dem Text gelöst; im Text steht dort `[Abbildung 1]`.
2. **Nur die Verstehensstufe** bekommt die Bilder und überträgt jede Abbildung vollständig in Text (jede Zahl, jeder Pfeil mit Richtung und Bewertung, unsicher Gelesenes wird benannt).
3. **Gegenprobe:** Ein zweiter, unabhängiger Aufruf überträgt die Abbildungen noch einmal. Weichen die Zahlen ab, entscheidet eine dritte Lesung; stimmen keine zwei überein, gilt das Ergebnis als unzuverlässig.
4. Alle weiteren Stufen rechnen mit der Übertragung – das Bild selbst wird nicht erneut gesendet.
5. Der Report zeigt im Abschnitt „Aufgabe“ **Original und Übertragung nebeneinander**. Bitte vergleichen: Ein verlesener Wert ändert das ganze Ergebnis, und eine vertauschte Pfeilrichtung bei gleichen Zahlen fällt der Gegenprobe nicht auf.
Voraussetzungen und Grenzen:
- Das Modell der Verstehensstufe muss Bilder lesen können. Das Standardmodell `~deepseek/deepseek-flash-latest` (derzeit DeepSeek V4.1 Flash) kann das; sonst ein Bildmodell über `LLM_MODEL_UNDERSTANDING` wählen (z. B. `qwen/qwen3.8-flash`).
- Höchstens 6 Abbildungen, je 5 MB.
- Mehrkosten: ein bis zwei zusätzliche kleine Aufrufe für die Gegenprobe.
- Verweise auf Webadressen werden nicht geladen (das Programm lädt nichts aus dem Internet).
---
## 9. Weiterarbeiten ohne neue LLM-Kosten
### Andere Zahlen, gleiches Modell: `--resolve`
Alle Eingabewerte des Programms stehen im Wörterbuch `DATA` (im JSON unter `solver_run.data`). Mit `--resolve` rechnet das gespeicherte Programm neu – **ohne einen einzigen LLM-Aufruf**, inklusive Zulässigkeits- und Sensitivitätsprüfung:
```bash
# unverändert neu rechnen
.venv/bin/python TextToModel.py --resolve output/aufgabe.json > output/nachgerechnet.json
# mit geänderten Werten
.venv/bin/python TextToModel.py --resolve output/aufgabe.json --data output/neue_werte.json \
--report output/variante.html > output/variante.json
```
`neue_werte.json` enthält nur die Einträge, die sich ändern – mit denselben Schlüsseln wie in `solver_run.data`:
```bash
# vorhandene Schlüssel ansehen
.venv/bin/python -c "import json; print(json.dumps(json.load(open('output/aufgabe.json'))['solver_run']['data'], indent=2, ensure_ascii=False))"
```
```json
{ "r": [130, 105, 135, 110], "U": 30 }
```
Grenze: Ändert sich die **Struktur** (neue Produkte, neue Bedingungen), reicht `--resolve` nicht – dann die Aufgabe neu lösen lassen.
### Nur eine Stufe wiederholen: `--von`
Ist das Modell gut, aber das Programm gescheitert, muss nicht alles neu bezahlt werden:
```bash
# Modell und Prüfbericht behalten, nur Solver-Code neu erzeugen (etwa 2 statt 5–7 Aufrufe)
.venv/bin/python TextToModel.py --von solver output/aufgabe.json --report output/neu.html > output/neu.json
# Modell behalten, neu prüfen und korrigieren, dann lösen
.venv/bin/python TextToModel.py --von pruefung output/aufgabe.json > output/neu.json
```
- Der Aufgabentext kommt aus dem gespeicherten Ergebnis; keine Aufgabendatei angeben.
- Die deterministischen Checks laufen immer neu. Passt die Zielgröße laut Prüfbericht nicht, startet auch hier kein Solver.
- `--von` und `--resolve` schließen sich aus; eine abgelehnte Aufgabe lässt sich nicht fortsetzen.
- Verständnis (Teilfragen) und Abbildungen bleiben erhalten. **Beigelegte Datendateien** sind im Ergebnis nur als Profil gespeichert: bei `--von` und `--resolve` mit `--daten datei.csv …` erneut angeben, sonst findet das Programm sie nicht.
- `--resolve` prüft nur die Zulässigkeit neu; frühere Befunde (z. B. falsche Zielgröße) bleiben bestehen.
### Report nachträglich erzeugen
```bash
.venv/bin/python -m reporting.report output/aufgabe.json output/aufgabe.html
```
---
## 10. Kosten und Laufzeit steuern
**Richtwerte** (DeepSeek V4.1 Flash über OpenRouter, September 2026): kleine Aufgaben etwa 0,02–0,05 $ und 1–3 Minuten, mittlere 0,05–0,15 $ und 3–10 Minuten, große mehrteilige Aufgaben mit Datenanhang bis etwa 0,20 $ und 15 Minuten. Die tatsächlichen Werte stehen am Ende jedes Laufs im Terminal und im JSON unter `usage`.
Wo das Geld hingeht: rund zwei Drittel der Kosten sind **erzeugter Output**, davon der Großteil internes „Nachdenken“ (Reasoning) des Modells; die Laufzeit hängt fast nur am Output. Die teuerste Stufe ist die Erzeugung des Solver-Codes.
| Hebel | Wirkung |
|---|---|
| `--no-solve` | nur Modell und Prüfung; spart die teuerste Stufe |
| `--von solver` / `--resolve` | Wiederholung ohne erneute Modellierung bzw. ganz ohne LLM |
| `--max-revisions 0`, `MAX_RESTARTS=0`, `SOLVER_RETRIES=0` | weniger Nacharbeit – billiger, aber Fehler bleiben eher stehen |
| `LLM_REASONING_EFFORT=low` | weniger Nachdenken, schneller und billiger; Qualität kann sinken |
| `OPENROUTER_MIN_THROUGHPUT` | bevorzugt schnellere Anbieter (Standard 75 Tokens/s) |
| `LLM_MODEL_SOLVER=…` usw. | anderes Modell nur für einzelne Stufen, z. B. ein schnelles für den Code |
**Prompt-Cache:** Alle Aufrufe eines Laufs gehen an denselben OpenRouter-Anbieter, damit dessen Cache wiederholt gelesene Prompt-Teile billiger abrechnen kann. Der Cache-Anteil steht in der Abschlusszeile.
**Standardmodell ist der Alias `~deepseek/deepseek-flash-latest`** – er zeigt immer auf das neueste DeepSeek-Flash-Modell (September 2026: V4.1 Flash). Das hält die Pipeline aktuell, kann Ergebnisse aber unbemerkt verändern, sobald DeepSeek ein neues Flash-Modell veröffentlicht. **Für Benchmarks und Vergleiche feste Modellnamen verwenden** (z. B. `--models deepseek/deepseek-v4.1-flash`).
---
## 11. Alle Einstellungen im Überblick
### Kommandozeile
| Option | Bedeutung |
|---|---|
| `AUFGABE` | Aufgabendatei (`.txt`, `.md`); ohne: Standardeingabe, sonst Beispielaufgabe |
| `--report DATEI` | Report als `.html`/`.htm` oder `.md` |
| `--code DATEI` | Solver-Programm als ausführbares Skript speichern |
| `--model NAME` | LLM-Modell (mit `/` → OpenRouter, sonst Gemini) |
| `--daten DATEI …` | beigelegte CSV-/Excel-Dateien |
| `--no-solve` | keinen Solver-Code erzeugen |
| `--max-revisions N` | Korrekturrunden |
| `--max-restarts N` | komplette Neustarts bei dauerhaft fehlerhaftem Modell |
| `--resolve ERGEBNIS.JSON` | gespeichertes Programm ohne LLM neu rechnen |
| `--data DATEN.JSON` | nur mit `--resolve`: Eingabewerte ganz oder teilweise ersetzen |
| `--von STUFE ERGEBNIS.JSON` | fortsetzen ab `pruefung` oder `solver` |
### Umgebungsvariablen
| Variable | Bedeutung | Standard |
|---|---|---|
| `OPENROUTER_API_KEY` | Schlüssel für OpenRouter | – |
| `GEMINI_API_KEY` | Schlüssel für Gemini | – |
| `LLM_MODEL` | Modell, falls `--model` fehlt | `~deepseek/deepseek-flash-latest` |
| `LLM_MODEL_UNDERSTANDING` | Modell für Verstehen und Abbildungs-Gegenprobe (muss bei Abbildungen Bilder lesen können) | Session-Modell |
| `LLM_MODEL_EXTRACTION` | Modell nur für die Extraktion | Session-Modell |
| `LLM_MODEL_REVIEW` | Modell nur für die Prüfung | Session-Modell |
| `LLM_MODEL_REVISION` | Modell nur für die Korrektur | Session-Modell |
| `LLM_MODEL_SOLVER` | Modell für Solver-Code und dessen Reparatur | Session-Modell |
| `MAX_REVISIONS` | Korrekturrunden | 2 |
| `MAX_RESTARTS` | Neustarts der Modellierung | 1 |
| `UNDERSTAND_RETRIES` | Nachfassen, wenn Zahlen oder Teilfragen übersehen wurden | 1 |
| `SOLVER_RETRIES` | Reparaturen des Solver-Programms | 2 |
| `INFEASIBLE_RECHECK` | Gegenprobe bei „unlösbar“ (0 = aus) | 1 |
| `LLM_MAX_TOKENS` | Obergrenze je Antwort (OpenRouter) | 32000 |
| `LLM_TIMEOUT` | Zeitlimit je LLM-Aufruf in Sekunden | 420 |
| `LLM_REASONING_EFFORT` | `low`, `medium` oder `high` (OpenRouter) | Modellvorgabe |
| `OPENROUTER_MIN_THROUGHPUT` | bevorzugte Mindestgeschwindigkeit des Anbieters in Tokens/s | 75 |
| `SOLVER_TIMEOUT` | Zeitlimit je Programmlauf in Sekunden; zugleich das Zeitbudget, das der Solver-Prompt dem Programm nennt | `60` |
| `SOLVER_SANDBOX` | `auto` (bubblewrap, wenn vorhanden), `bwrap` (Pflicht), `aus` (ungeschützt) | `auto` |
Beispiel für einen einzelnen Lauf mit geänderten Einstellungen:
```bash
MAX_REVISIONS=1 LLM_REASONING_EFFORT=low .venv/bin/python TextToModel.py aufgabe.md > output/aufgabe.json
```
---
## 12. Benchmark und Tests
### Tests (offline, kostenlos)
```bash
.venv/bin/python -m pytest -q # alle
.venv/bin/python -m pytest -q tests/test_solver_stage.py -k cpsat # Auswahl
```
### Benchmark (kostet API-Aufrufe)
`benchmark/cases/` enthält 19 Aufgaben mit unabhängig verifiziertem Optimum. Gemessen wird, ob der Zielwert stimmt und ob die Aufgabe erfüllt ist (jede Teilfrage beantwortet, jede Zahl belegt).
```bash
# ausgewählte Fälle, je 2 Läufe, 3 parallel
.venv/bin/python -m benchmark.benchmark --cases lp_klein rucksack_milp --runs 2 --jobs 3
# zwei Modelle vergleichen
.venv/bin/python -m benchmark.benchmark --models deepseek/deepseek-v4.1-flash deepseek/deepseek-v4-pro-0813 --runs 2 --jobs 3
```
Ein voller Lauf über alle Fälle mit 2 Läufen kostet etwa 1,30 $ und dauert rund 50 Minuten. Die Tabelle erscheint im Terminal, Details in `benchmark/results/<Zeitstempel>.json`. Einen Report zu einem einzelnen Benchmark-Lauf erzeugt:
```bash
.venv/bin/python -m reporting.report benchmark/results/<Zeitstempel>.json output/fall.html --case lp_klein --model v4.1 --run 1
```
**Eigene Fälle** anlegen: Verzeichnis `benchmark/cases/<name>/` mit `task.txt` und `expected.json` (`problem_class`, `target_stack`, `sense`, `objective_value`, `tolerance`). Nur Fälle mit vollständigen Daten und unabhängig nachgerechnetem Optimum aufnehmen – oder bewusst schlecht gestellte Fälle „ehrlich scheitern“: statt Zielwert `"behavior": [...]` mit den akzeptierten Verhalten `rejected` (abgelehnt), `unreliable` (als unzuverlässig gekennzeichnet), `assumed_data` (angenommene Daten ausgewiesen); eines genügt. Weitere Dateien im Verzeichnis werden als Datenanhang übergeben.
Mit dem Gemini-Gratiskontingent ist der Benchmark praktisch nicht durchführbar; dort höchstens `--jobs 1`.
---
## 13. Fehlerbehebung
| Meldung / Symptom | Ursache | Lösung |
|---|---|---|
| `KeyError: 'OPENROUTER_API_KEY'` | Schlüssel nicht gesetzt | `export OPENROUTER_API_KEY=…` in derselben Shell |
| `Error code: 401` | Schlüssel ungültig | Schlüssel prüfen |
| `Error code: 402` | Guthaben bei OpenRouter aufgebraucht | Guthaben aufladen |
| `RESOURCE_EXHAUSTED` | Gemini-Kontingent erschöpft | später erneut oder OpenRouter-Modell |
| `Vorübergehender LLM-Fehler – neuer Versuch in … s` | Überlastung, Ratelimit, Zeitüberschreitung | nichts tun; es wird automatisch bis zu dreimal wiederholt |
| Lauf dauert sehr lange | langsamer Anbieter oder viel Reasoning | `OPENROUTER_MIN_THROUGHPUT` erhöhen, `LLM_REASONING_EFFORT=low` |
| `SOLVER_SANDBOX=bwrap verlangt bubblewrap, es ist aber nicht installiert.` | bubblewrap fehlt | `sudo apt install bubblewrap`; prüfen mit `which bwrap` |
| `pip` scheitert bei `cyipopt` | Ipopt-Bibliothek fehlt | `sudo apt install coinor-libipopt-dev pkg-config`, dann erneut installieren |
| `--report braucht die Endung .md oder .html` | falsche Dateiendung | `.html` oder `.md` verwenden |
| `--data wirkt nur zusammen mit --resolve` | `--data` allein angegeben | `--resolve ERGEBNIS.JSON --data …` |
| Exit-Code 2 | Ergebnis unzuverlässig | Abschnitt 7; oft hilft `--von solver` |
| Exit-Code 3 | keine Optimierungsaufgabe erkannt | Entscheidung und Ziel im Text ausdrücklich formulieren |
| `FileNotFoundError` beim exportierten Programm | Datendateien fehlen im Verzeichnis des Skripts | Dateien daneben kopieren (Abschnitt 8) |
| Formeln im HTML-Report erscheinen als Rohtext | kein Internet beim Öffnen (MathJax) | online öffnen oder Markdown-Report verwenden |
| Report zeigt „Angenommene Daten“, obwohl Daten gemeint waren | Aufgabe verlangt Daten aus dem Internet, das Programm hat keinen Netzzugang | Daten als Datei beilegen (`--daten`) |
| Antwort in falscher Größenordnung (Faktor 100 oder 1000) | Einheit im Text mehrdeutig | Einheit ausdrücklich nennen („in %“, „in Tausend Euro“) |
| Seltsame Importfehler von `instructor` | eine Datei `instructor.py` im Projekt | Datei umbenennen |
---
## 14. Sicherheit und Datenschutz
- **Der Aufgabentext und das Datenprofil werden an den LLM-Anbieter gesendet** (OpenRouter und die dahinterliegenden Modellanbieter bzw. Google). Keine vertraulichen oder personenbezogenen Daten eingeben, die diesen Weg nicht gehen dürfen. Datendateien selbst verlassen den Rechner nicht, wohl aber ihr Profil mit Beispielwerten.
- **Der Solver-Code stammt von einem LLM.** Er läuft in einer bubblewrap-Sandbox: kein Netzzugang, Dateisystem nur lesbar, das Home-Verzeichnis unsichtbar, beschreibbar nur das Temp-Verzeichnis des Laufs. API-Schlüssel und andere Umgebungsvariablen bekommt das Programm nicht zu sehen. Zusätzlich sind nur Mathematik- und Solver-Bibliotheken erlaubt, `open`, `eval` und `exec` verboten, und es gilt ein Zeitlimit.
- **Ohne bubblewrap läuft der Code ungeschützt mit Ihren Benutzerrechten – und zwar ohne Warnung** (Standard `SOLVER_SANDBOX=auto`). Prüfen mit `which bwrap`. Wer sichergehen will, setzt `export SOLVER_SANDBOX=bwrap`: Dann wird ein Lauf ohne bubblewrap abgelehnt, statt ungeschützt ausgeführt.
- **Ein exportiertes Programm (`--code`) läuft außerhalb der Sandbox**, wenn Sie es selbst starten. Vorher kurz ansehen.
- **API-Schlüssel** nur als Umgebungsvariable; `.env`-Dateien sind von Git ausgeschlossen, gehören aber trotzdem nicht ins Projekt.
---
## 15. Grenzen
- **Ergebnisse vor Verwendung prüfen.** Die Prüfungen fangen viele, aber nicht alle Fehler. Besonders die Erklärung („Wie das Modell die Aufgabe sieht“) mit der eigenen Absicht vergleichen.
- **Deutsch** ist die vorgesehene Sprache von Aufgaben, Erklärungen und Antworten.
- **Größe:** Richtwerte je Klasse – lineare Programme bis etwa 10⁴ Variablen, ganzzahlige und Constraint-Modelle 10²–10³, quadratische 10³, gemischt-ganzzahlig quadratische und nichtlineare etwa 10². Darüber bleibt die Lösung möglich, Optimalität lässt sich im Zeitlimit aber oft nicht beweisen; das wird als Annahme ausgewiesen.
- **Zeitlimit des Programms:** 60 Sekunden je Ausführung.
- **Kein Netz im Programm:** Webdaten (Kurse, Wetter, Karten) müssen als Datei beigelegt werden.
- **Nichtlineare Aufgaben** liefern ohne globales Verfahren nur lokale Optima; das Programm berichtet die Streuung über mehrere Startpunkte.
- **Schlecht gestellte Aufgaben** (fehlende Angaben, mehrdeutige Einheiten, mehrteilige Projektaufträge) sind die größte Fehlerquelle. Das Programm soll dann ehrlich scheitern – Annahmen kennzeichnen, Unlösbarkeit melden, fehlende Daten zugeben –, kann eine fehlende Angabe aber nicht ersetzen.