diff --git a/docs/Konzeptionelle_Fragen_und_Antworten.md b/docs/Konzeptionelle_Fragen_und_Antworten.md index 26c7228..c0b04cb 100644 --- a/docs/Konzeptionelle_Fragen_und_Antworten.md +++ b/docs/Konzeptionelle_Fragen_und_Antworten.md @@ -1,29 +1,24 @@ # Konzeptionelle Fragen und Antworten -Grundsatzfragen zur Bauweise des Projekts – mit Begründung, eigenen Messdaten und Quellen, damit -Entscheidungen später nachvollziehbar bleiben. +Grundsatzfragen zur Bauweise des Projekts – mit Begründung, eigenen Messdaten und Quellen, damit Entscheidungen später nachvollziehbar bleiben. --- ## Unsere Pipeline (Stand 17.09.2026) -Sieben Stufen; Ablauf und Prüfungen gesteuert von `TextToModel.py` (`run_pipeline`, `_solver_stage`). -Alle LLM-Aufrufe laufen über `core/llm.py` (`LLMSession.create`), alle Datenstrukturen stehen in -`core/model_schema.py`. +Sieben Stufen; Ablauf und Prüfungen gesteuert von `TextToModel.py` (`run_pipeline`, `_solver_stage`). Alle LLM-Aufrufe laufen über `core/llm.py` (`LLMSession.create`), alle Datenstrukturen stehen in `core/model_schema.py`. -| # | Stufe | LLM | Python-Datei (Funktion) | Prompt | -|---|---|---|---|---| -| 1 | **Verstehen** – Teilfragen und alle Zahlen erfassen; Nicht-Aufgaben ablehnen; Vollständigkeit ohne LLM prüfen | ja | `understanding/understand.py` (`understand_task`), Prüfung `checks/model_checks.py` (`check_understanding`) | `understanding/Verstehen-Prompt.txt` | -| 2 | **Extraktion** – Problemklasse, Solver, Indizes, Parameter, Variablen, Zielfunktion, Restriktionen, Annahmen | ja | `modeling/extraction.py` (`text_to_model`) | `modeling/Routing-und-Extraktions-Prompt.txt` | -| 3 | **Deterministische Checks** – Stack passt zur Klasse, Einheiten, Symbole, Datenübernahme, Fachbegriffe | nein | `checks/model_checks.py` (`check_spec`) | – | -| 4 | **LLM-Prüfung** – liefert die Zielfunktion die gefragte Größe? Sind die Annahmen textkonform? | ja | `checks/review.py` (`review_assumptions`, `needs_work`, `select_best`) | `checks/Pruef-Prompt.txt` | -| 5 | **Korrektur** – Schleife 3–5, beste Runde gewinnt, notfalls Neustart | ja | `modeling/extraction.py` (`revise_model`) | `modeling/Routing-und-Extraktions-Prompt.txt` | -| 6 | **Solver** – Python-Code erzeugen, in der Sandbox ausführen, bei Fehlern reparieren, Szenarien nachrechnen | ja | `solving/solver_stage.py` (`solve`, `run_solver_code`, `verify_scenarios`) | `solving/Solver-Prompt.txt` | -| 7 | **Abnahme** – Zulässigkeit, Antworten, Zahlen und Einheiten belegt, Datenherkunft, Szenarien mit Ergebnis | nein | `solving/solver_stage.py` (`_check_and_run`, `check_feasibility`, `check_answers`, `check_answer_units`, `check_generated_data`, `check_scenarios_solved`) | – | +| # | Stufe | LLM | Python-Datei (Funktion) | Prompt | +| --- | ------------------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| 1 | **Verstehen** – Teilfragen und alle Zahlen erfassen; Nicht-Aufgaben ablehnen; Vollständigkeit ohne LLM prüfen | ja | `understanding/understand.py` (`understand_task`), Prüfung `checks/model_checks.py` (`check_understanding`) | `understanding/Verstehen-Prompt.txt` | +| 2 | **Extraktion** – Problemklasse, Solver, Indizes, Parameter, Variablen, Zielfunktion, Restriktionen, Annahmen | ja | `modeling/extraction.py` (`text_to_model`) | `modeling/Routing-und-Extraktions-Prompt.txt` | +| 3 | **Deterministische Checks** – Stack passt zur Klasse, Einheiten, Symbole, Datenübernahme, Fachbegriffe | nein | `checks/model_checks.py` (`check_spec`) | – | +| 4 | **LLM-Prüfung** – liefert die Zielfunktion die gefragte Größe? Sind die Annahmen textkonform? | ja | `checks/review.py` (`review_assumptions`, `needs_work`, `select_best`) | `checks/Pruef-Prompt.txt` | +| 5 | **Korrektur** – Schleife 3–5, beste Runde gewinnt, notfalls Neustart | ja | `modeling/extraction.py` (`revise_model`) | `modeling/Routing-und-Extraktions-Prompt.txt` | +| 6 | **Solver** – Python-Code erzeugen, in der Sandbox ausführen, bei Fehlern reparieren, Szenarien nachrechnen | ja | `solving/solver_stage.py` (`solve`, `run_solver_code`, `verify_scenarios`) | `solving/Solver-Prompt.txt` | +| 7 | **Abnahme** – Zulässigkeit, Antworten, Zahlen und Einheiten belegt, Datenherkunft, Szenarien mit Ergebnis | nein | `solving/solver_stage.py` (`_check_and_run`, `check_feasibility`, `check_answers`, `check_answer_units`, `check_generated_data`, `check_scenarios_solved`) | – | -Ausgabe: Report `reporting/report.py` mit Diagrammen aus `reporting/charts.py`, eigenständiges Skript -`solving/script_export.py`. Beigelegte Daten profiliert `data/data_profile.py` ohne LLM. Messung: -`benchmark/benchmark.py` mit den Fällen in `benchmark/cases/`. +Ausgabe: Report `reporting/report.py` mit Diagrammen aus `reporting/charts.py`, eigenständiges Skript `solving/script_export.py`. Beigelegte Daten profiliert `data/data_profile.py` ohne LLM. Messung: `benchmark/benchmark.py` mit den Fällen in `benchmark/cases/`. --- @@ -31,37 +26,19 @@ Ausgabe: Report `reporting/report.py` mit Diagrammen aus `reporting/charts.py`, ### Die Frage (17.09.2026) -> Wir haben festgestellt, dass im GitHub-Repository MathModelAgent (Fall 1) alle Schritte hintereinander -> weg mit nur einer LLM erledigt werden, während im GitHub-Repository LLM-MM-Agent (Fall 2) mit -> verschiedenen Agenten gearbeitet wird, die auch unterschiedliche LLMs benutzen können. Ich unterstelle: -> Vermutlich sind die Ergebnisse ähnlich. Aber welche Methode würde bezogen auf unser Projekt -> zuverlässiger, schneller und günstiger funktionieren? […] Meine Überlegung: Im Fall 1 müssen alle Daten -> und Erkenntnisse nur einmal ins LLM geladen werden. Im Fall 2 müssen sie bei jedem Schritt der Pipeline -> erneut ins LLM geladen werden. Das kostet mehr Geld und Zeit. Im Fall 1 brauchen wir einen möglicherweise -> sehr langen Prompt, der möglicherweise nicht ganz korrekt ausgeführt wird. Im Fall 2 arbeiten wir mit -> verschiedenen kürzeren Prompts, die individueller auf die jeweilige Aufgabe zugeschnitten sein können. -> Sehe ich das richtig? Was habe ich vergessen? +> Wir haben festgestellt, dass im GitHub-Repository MathModelAgent (Fall 1) alle Schritte hintereinander weg mit nur einer LLM erledigt werden, während im GitHub-Repository LLM-MM-Agent (Fall 2) mit verschiedenen Agenten gearbeitet wird, die auch unterschiedliche LLMs benutzen können. Ich unterstelle: Vermutlich sind die Ergebnisse ähnlich. Aber welche Methode würde bezogen auf unser Projekt zuverlässiger, schneller und günstiger funktionieren? […] Meine Überlegung: Im Fall 1 müssen alle Daten und Erkenntnisse nur einmal ins LLM geladen werden. Im Fall 2 müssen sie bei jedem Schritt der Pipeline erneut ins LLM geladen werden. Das kostet mehr Geld und Zeit. Im Fall 1 brauchen wir einen möglicherweise sehr langen Prompt, der möglicherweise nicht ganz korrekt ausgeführt wird. Im Fall 2 arbeiten wir mit verschiedenen kürzeren Prompts, die individueller auf die jeweilige Aufgabe zugeschnitten sein können. Sehe ich das richtig? Was habe ich vergessen? ### Kurzantwort -**Für unser Projekt ist die Stufenkette zuverlässiger, ungefähr gleich schnell und kaum teurer als ein -einzelner Aufruf – wir behalten sie.** Die Hebel liegen innerhalb der Kette: Prompt-Caching messbar machen -und nutzen, und für die Prüfung gezielt ein *anderes* Modell einsetzen. +**Für unser Projekt ist die Stufenkette zuverlässiger, ungefähr gleich schnell und kaum teurer als ein einzelner Aufruf – wir behalten sie.** Die Hebel liegen innerhalb der Kette: Prompt-Caching messbar machen und nutzen, und für die Prüfung gezielt ein *anderes* Modell einsetzen. ### 1. Die Prämisse ist vertauscht – und beide Projekte sind Ketten Im Quellcode nachgesehen: -- **MathModelAgent** hat vier Agenten (Coordinator, Modeler, Coder, Writer) mit **je eigenem Modell** - (`COORDINATOR_MODEL`, `MODELER_MODEL`, `CODER_MODEL`, `WRITER_MODEL` in `backend/.env.example`; - `llm_factory.get_all_llms()` in `backend/app/core/workflow.py`). Jeder Agent führt einen eigenen, - **wachsenden Gesprächsverlauf**; übergeben wird ein Objekt (`CoordinatorToModeler`, `ModelerToCoder`). -- **LLM-MM-Agent** nutzt **ein Modell** (`--model_name`, Standard gpt-4o) für alle Agenten, aber in vielen - getrennten Aufrufen (27 Aufrufstellen `llm.generate(prompt, system)`). Jeder Aufruf beginnt frisch, - Vorwissen geht als zusammengefasster Text („memory“) mit, dazu Erzeugen–Kritisieren–Verbessern je Stufe. -- **Wir** arbeiten mit einer Kette frischer Aufrufe, **strukturierter** Übergabe (Schemas), einem Modell - (Modell je Stufe möglich über `LLM_MODEL_EXTRACTION`, `LLM_MODEL_REVIEW`, `LLM_MODEL_REVISION`, - `LLM_MODEL_SOLVER` – nie gemessen) und **Prüfungen ohne LLM zwischen den Stufen**. +- **MathModelAgent** hat vier Agenten (Coordinator, Modeler, Coder, Writer) mit **je eigenem Modell** (`COORDINATOR_MODEL`, `MODELER_MODEL`, `CODER_MODEL`, `WRITER_MODEL` in `backend/.env.example`; `llm_factory.get_all_llms()` in `backend/app/core/workflow.py`). Jeder Agent führt einen eigenen, **wachsenden Gesprächsverlauf**; übergeben wird ein Objekt (`CoordinatorToModeler`, `ModelerToCoder`). +- **LLM-MM-Agent** nutzt **ein Modell** (`--model_name`, Standard gpt-4o) für alle Agenten, aber in vielen getrennten Aufrufen (27 Aufrufstellen `llm.generate(prompt, system)`). Jeder Aufruf beginnt frisch, Vorwissen geht als zusammengefasster Text („memory“) mit, dazu Erzeugen–Kritisieren–Verbessern je Stufe. +- **Wir** arbeiten mit einer Kette frischer Aufrufe, **strukturierter** Übergabe (Schemas), einem Modell (Modell je Stufe möglich über `LLM_MODEL_EXTRACTION`, `LLM_MODEL_REVIEW`, `LLM_MODEL_REVISION`, `LLM_MODEL_SOLVER` – nie gemessen) und **Prüfungen ohne LLM zwischen den Stufen**. „Alles in einem Aufruf“ macht keines der Projekte. Es gibt drei unabhängige Unterscheidungen, nicht eine: @@ -73,66 +50,47 @@ Im Quellcode nachgesehen: Letzter voller Benchmark `benchmark/results/20260917-043002.json` (26 Läufe, `deepseek/deepseek-v4.1-flash`): -- 5,8 Aufrufe je Lauf; Input 31 000, Output 33 000 Tokens je Lauf, davon **61 % Reasoning**; - $0,045 und Ø 194 s je Lauf. -- Preise per Regression über 228 Läufe geschätzt: Input ≈ $0,50/M, Output ≈ $0,78/M – - **Input ≈ 35 % der Kosten, Output ≈ 65 %**. -- Zeitanteile: Solver-Code 36 %, Extraktion 24 %, Prüfung 15 %, Verstehen 13 %, Solver-Reparatur 9 %, - Korrektur 3 %. Die Zeit folgt dem **erzeugten Output**, nicht dem gelesenen Input. +- 5,8 Aufrufe je Lauf; Input 31 000, Output 33 000 Tokens je Lauf, davon **61 % Reasoning**; $0,045 und Ø 194 s je Lauf. +- Preise per Regression über 228 Läufe geschätzt: Input ≈ $0,50/M, Output ≈ $0,78/M – **Input ≈ 35 % der Kosten, Output ≈ 65 %**. +- Zeitanteile: Solver-Code 36 %, Extraktion 24 %, Prüfung 15 %, Verstehen 13 %, Solver-Reparatur 9 %, Korrektur 3 %. Die Zeit folgt dem **erzeugten Output**, nicht dem gelesenen Input. - Cache-Treffer wurden bis dahin nicht erfasst. ### 3. Die Annahmen im Einzelnen -| Annahme | Urteil | Begründung | -|---|---|---| -| Einmal laden spart Geld | **nur zum Teil** | LLM-Schnittstellen merken sich nichts zwischen Aufrufen; auch ein Gesprächsverlauf wird bei jedem Zug komplett neu gesendet und wächst dabei. Sparen lässt sich höchstens am Input-Anteil (35 %), realistisch ~20 %, und nur ohne Wiederholungen. Prompt-Caching verbilligt Wiederholtes in *beiden* Bauweisen. | -| Einmal laden spart Zeit | **kaum** | Einlesen ist schnell, Erzeugen langsam. Ein Einzelaufruf müsste dieselben ~33 000 Output-Tokens erzeugen. | -| Ein langer Prompt wird schlechter befolgt | **belegt** | IFScale: Befolgung sinkt mit der Zahl gleichzeitiger Regeln, häufigster Fehler ist das Auslassen, frühe Regeln werden bevorzugt. Chroma „Context Rot“: alle 18 getesteten Modelle werden mit wachsender Eingabe schlechter (Herausgeber hat ein Produktinteresse). Neuere Modelle sind deutlich robuster. | -| Kürzere, zugeschnittene Prompts sind besser | **belegt** | ACL 2024: getrennte Aufrufe gewinnen 77-mal, derselbe Ablauf in einem Prompt 53-mal; im Einzel-Prompt „simuliert“ das Modell die Verbesserung und baut dafür Fehler ein. | -| Die Ergebnisse sind ähnlich | **bei leichten Aufgaben ja** | Auf NL4Opt erreicht ein starkes Modell ohne Gerüst laut Sekundärquelle ähnliche oder bessere Werte als Multi-Agent-Frameworks; das Gerüst lohnt bei komplexen Aufgaben. Unser Benchmark ist gesättigt (38 von 38) und kann den Unterschied nicht zeigen. | +| Annahme | Urteil | Begründung | +| ------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Einmal laden spart Geld | **nur zum Teil** | LLM-Schnittstellen merken sich nichts zwischen Aufrufen; auch ein Gesprächsverlauf wird bei jedem Zug komplett neu gesendet und wächst dabei. Sparen lässt sich höchstens am Input-Anteil (35 %), realistisch ~20 %, und nur ohne Wiederholungen. Prompt-Caching verbilligt Wiederholtes in *beiden* Bauweisen. | +| Einmal laden spart Zeit | **kaum** | Einlesen ist schnell, Erzeugen langsam. Ein Einzelaufruf müsste dieselben ~33 000 Output-Tokens erzeugen. | +| Ein langer Prompt wird schlechter befolgt | **belegt** | IFScale: Befolgung sinkt mit der Zahl gleichzeitiger Regeln, häufigster Fehler ist das Auslassen, frühe Regeln werden bevorzugt. Chroma „Context Rot“: alle 18 getesteten Modelle werden mit wachsender Eingabe schlechter (Herausgeber hat ein Produktinteresse). Neuere Modelle sind deutlich robuster. | +| Kürzere, zugeschnittene Prompts sind besser | **belegt** | ACL 2024: getrennte Aufrufe gewinnen 77-mal, derselbe Ablauf in einem Prompt 53-mal; im Einzel-Prompt „simuliert“ das Modell die Verbesserung und baut dafür Fehler ein. | +| Die Ergebnisse sind ähnlich | **bei leichten Aufgaben ja** | Auf NL4Opt erreicht ein starkes Modell ohne Gerüst laut Sekundärquelle ähnliche oder bessere Werte als Multi-Agent-Frameworks; das Gerüst lohnt bei komplexen Aufgaben. Unser Benchmark ist gesättigt (38 von 38) und kann den Unterschied nicht zeigen. | ### 4. Was in der Überlegung fehlte – die eigentlich entscheidenden Punkte -1. **Prüftore zwischen den Stufen.** Unsere Zuverlässigkeit entsteht durch Code-Prüfungen *zwischen* den - Aufrufen: Ist der Text keine Aufgabe, endet der Lauf nach einem Aufruf; passt die Zielgröße nicht, startet - kein Solver. In einem Einzelaufruf zeigen sich alle Fehler erst am Ende, und jeder kostet den ganzen Lauf. -2. **Reparatur dort, wo der Fehler liegt.** In 26 Läufen gab es 9 Korrekturrunden und 11 Solver-Reparaturen. - Jede wiederholte nur ihre Stufe (~3 000–9 000 Output-Tokens) statt den ganzen Lauf (~30 000). -3. **Harte Grenzen.** `LLM_MAX_TOKENS` ist 32 000, ein Lauf erzeugt ~33 000 Output-Tokens – ein Einzelaufruf - liefe an die Grenze. Ein riesiges Ausgabeschema scheitert außerdem als Ganzes an einer einzigen Stelle. -4. **Selbstprüfung ist schwach.** Modelle finden ihre eigenen Fehler kaum, und Fehler sind modellspezifisch - (nur 9,6 % der festen Fehler eines Modells traten beim anderen auf). Prüfung im selben Aufruf bringt wenig, - ein frischer Aufruf mehr, **ein anderes Modell als Prüfer am meisten** – das stärkste Argument für ein - Modell je Stufe. -5. **Übergabeverlust ist das echte Risiko einer Kette.** Google/MIT 2025: Multi-Agent-Aufbauten wurden bei - aufeinander aufbauenden Aufgaben um 39–70 % schlechter; Fehler verstärkten sich ohne zentrale Prüfung um - das 17-Fache, mit zentraler Prüfung um das 4-Fache. Das betrifft frei zusammenarbeitende Agenten, nicht - feste Ketten mit Prüftoren – wir haben den Effekt aber selbst gemessen: Von 7, 10 und 14 Zahlen der Aufgabe - kam jeweils keine in der Spezifikation an, bis die Verstehensstufe und `check_data_transfer` die Übergabe - prüften. -6. **Multi-Agent ist nicht gleich Kette.** Autonome Agenten verbrauchen laut Anthropic ~15-mal so viele Tokens - wie ein Chat; eine feste Kette nicht. -7. **Messbarkeit.** Kosten und Zeit lassen sich je Stufe zuordnen, `--von solver` braucht 2 statt 5–7 Aufrufe, - Tests gibt es je Stufe. -8. **Caching wurde verschenkt.** Cache-Treffer wurden nicht erfasst, und die Anfragen landeten wechselnd bei - verschiedenen Anbietern (Modal, Together, Venice), die sich nichts teilen. +1. **Prüftore zwischen den Stufen.** Unsere Zuverlässigkeit entsteht durch Code-Prüfungen *zwischen* den Aufrufen: Ist der Text keine Aufgabe, endet der Lauf nach einem Aufruf; passt die Zielgröße nicht, startet kein Solver. In einem Einzelaufruf zeigen sich alle Fehler erst am Ende, und jeder kostet den ganzen Lauf. +2. **Reparatur dort, wo der Fehler liegt.** In 26 Läufen gab es 9 Korrekturrunden und 11 Solver-Reparaturen. Jede wiederholte nur ihre Stufe (~3 000–9 000 Output-Tokens) statt den ganzen Lauf (~30 000). +3. **Harte Grenzen.** `LLM_MAX_TOKENS` ist 32 000, ein Lauf erzeugt ~33 000 Output-Tokens – ein Einzelaufruf liefe an die Grenze. Ein riesiges Ausgabeschema scheitert außerdem als Ganzes an einer einzigen Stelle. +4. **Selbstprüfung ist schwach.** Modelle finden ihre eigenen Fehler kaum, und Fehler sind modellspezifisch (nur 9,6 % der festen Fehler eines Modells traten beim anderen auf). Prüfung im selben Aufruf bringt wenig, + ein frischer Aufruf mehr, **ein anderes Modell als Prüfer am meisten** – das stärkste Argument für ein Modell je Stufe. +5. **Übergabeverlust ist das echte Risiko einer Kette.** Google/MIT 2025: Multi-Agent-Aufbauten wurden bei aufeinander aufbauenden Aufgaben um 39–70 % schlechter; Fehler verstärkten sich ohne zentrale Prüfung um das 17-Fache, mit zentraler Prüfung um das 4-Fache. Das betrifft frei zusammenarbeitende Agenten, nicht feste Ketten mit Prüftoren – wir haben den Effekt aber selbst gemessen: Von 7, 10 und 14 Zahlen der Aufgabe kam jeweils keine in der Spezifikation an, bis die Verstehensstufe und `check_data_transfer` die Übergabe prüften. +6. **Multi-Agent ist nicht gleich Kette.** Autonome Agenten verbrauchen laut Anthropic ~15-mal so viele Tokens wie ein Chat; eine feste Kette nicht. +7. **Messbarkeit.** Kosten und Zeit lassen sich je Stufe zuordnen, `--von solver` braucht 2 statt 5–7 Aufrufe, Tests gibt es je Stufe. +8. **Caching wurde verschenkt.** Cache-Treffer wurden nicht erfasst, und die Anfragen landeten wechselnd bei verschiedenen Anbietern (Modal, Together, Venice), die sich nichts teilen. ### 5. Einschätzung für unser Projekt -| Bauweise | Zuverlässigkeit | Zeit | Kosten | -|---|---|---|---| -| Ein Aufruf für alles | schlechter | gleich oder länger | −20 % ohne Fehler, mehr mit Wiederholungen | -| Kette mit wachsendem Verlauf (MathModelAgent) | etwa gleich | gleich | höher, weil der Verlauf wächst | -| **Kette mit frischen Aufrufen (wir)** | gemessen 26/26 | 194 s | $0,045 je Lauf | -| Kette mit anderem Modell für Prüfung und schnellem Modell für Code | vermutlich besser | vermutlich kürzer | ungefähr gleich | +| Bauweise | Zuverlässigkeit | Zeit | Kosten | +| ------------------------------------------------------------------ | ----------------- | ------------------ | ------------------------------------------ | +| Ein Aufruf für alles | schlechter | gleich oder länger | −20 % ohne Fehler, mehr mit Wiederholungen | +| Kette mit wachsendem Verlauf (MathModelAgent) | etwa gleich | gleich | höher, weil der Verlauf wächst | +| **Kette mit frischen Aufrufen (wir)** | gemessen 26/26 | 194 s | $0,045 je Lauf | +| Kette mit anderem Modell für Prüfung und schnellem Modell für Code | vermutlich besser | vermutlich kürzer | ungefähr gleich | ### 6. Folgerungen - Bauweise behalten. -- **Umgesetzt im Anschluss:** Cache-Treffer je Aufruf erfassen und die Aufrufe eines Laufs per `session_id` - beim selben OpenRouter-Anbieter halten (Sticky Routing). Messen beim nächsten ohnehin fälligen Lauf. -- **Später, mit Freigabe:** `LLM_MODEL_REVIEW` auf eine andere Modellfamilie – als A/B nur mit Aufgaben, die - Kopfraum haben (unordentliche Aufgaben), nicht auf dem gesättigten Benchmark. +- **Umgesetzt im Anschluss:** Cache-Treffer je Aufruf erfassen und die Aufrufe eines Laufs per `session_id` beim selben OpenRouter-Anbieter halten (Sticky Routing). Messen beim nächsten ohnehin fälligen Lauf. +- **Später, mit Freigabe:** `LLM_MODEL_REVIEW` auf eine andere Modellfamilie – als A/B nur mit Aufgaben, die Kopfraum haben (unordentliche Aufgaben), nicht auf dem gesättigten Benchmark. ### Quellen