fix: abgeschnittene Antworten meiden den Anbieter; LLM-Ausfall macht unzuverlässig statt abzustürzen
Grundlinie 20260918-191400: 9 von 22 Läufen hatten Aufrufe, die an LLM_MAX_TOKENS abgeschnitten wurden (Venice 4/28, Makora 2/43, übrige 0/54); hard_portfolio_kardinalitaet brach nach vier Abschneidungen ab, Kosten gingen verloren. - Abschneiden: sofort neu versuchen, Anbieter per provider.ignore meiden (_avoid_provider, gemeinsam mit „zu langsam“) - nach allen Wiederholungen LLMUnavailable (Stufe + Ursache); fatale Fehler bleiben unverpackt - Solver-Stufe behält Modell und Prüfbericht (Solver-Fehler), Gegenprobe behält erstes Ergebnis, davor Ergebnis ohne Spezifikation mit Kosten („LLM-Fehler“, Report „Lauf abgebrochen“) - Benchmark: Status „LLM-Fehler“ statt ABBRUCH, zählt nicht als ehrliches Scheitern - TextToModel.py: Shebang Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
422360ffa5
commit
6b2b2c6466
1 changed files with 218 additions and 25 deletions
|
|
@ -55,7 +55,10 @@ class FakeSession:
|
|||
warteschlange = self.queues[response_model]
|
||||
if response_model is TaskUnderstanding and not warteschlange:
|
||||
return EINFACHES_VERSTAENDNIS # Standard, solange ein Test nichts anderes vorgibt
|
||||
return warteschlange.pop(0)
|
||||
antwort = warteschlange.pop(0)
|
||||
if isinstance(antwort, Exception): # gescheiterter Aufruf, z. B. LLMUnavailable
|
||||
raise antwort
|
||||
return antwort
|
||||
|
||||
|
||||
HALLUCINATED = make_spec(executive_summary="Produktionsplanung mit Drehen und Fräsen") # Fremdproblem
|
||||
|
|
@ -853,3 +856,50 @@ def test_second_repair_knows_the_first_error():
|
|||
reparaturen = [inhalt for stufe, inhalt in session.contents if stufe == "Solver-Reparatur"]
|
||||
assert "FRÜHERE FEHLVERSUCHE" not in reparaturen[0] # beim ersten gibt es noch keinen Verlauf
|
||||
assert "Fehler B: Typ" in reparaturen[1] and "Versuch 1: " in reparaturen[1] and "Fehler A: Index" in reparaturen[1]
|
||||
|
||||
|
||||
# --- LLM nach allen Wiederholungen gescheitert: unzuverlässiges Ergebnis statt Absturz ---
|
||||
|
||||
def _llm_weg(step):
|
||||
from core.llm import LLMUnavailable
|
||||
return LLMUnavailable(step, RuntimeError("The output is incomplete due to a max_tokens length limit."))
|
||||
|
||||
|
||||
def test_llm_failure_in_solver_stage_keeps_the_model():
|
||||
# beobachtet: hard_portfolio_kardinalitaet, viermal abgeschnitten ⇒ Absturz nach 1162 s, Kosten verloren
|
||||
session = FakeSession([GOOD], [OK], [_llm_weg("Solver-Code")])
|
||||
result = run_pipeline("Aufgabe", session=session, max_revisions=0, max_restarts=0)
|
||||
assert not result.reliable and result.specification is not None and result.review is not None
|
||||
assert result.solver_run.status == SolverStatus.ERROR and result.solver_code is None
|
||||
assert result.reliability_issues[0].startswith("Solver-Fehler: LLM-Aufruf 'Solver-Code'")
|
||||
assert result.usage is not None
|
||||
|
||||
|
||||
def test_llm_failure_before_the_model_is_a_paid_non_result(tmp_path):
|
||||
from benchmark.benchmark import observed_behaviors
|
||||
from reporting.report import write_report
|
||||
session = FakeSession([_llm_weg("Extraktion")], [])
|
||||
result = run_pipeline("Aufgabe", session=session)
|
||||
assert not result.reliable and not result.rejected and result.specification is None
|
||||
assert result.reliability_issues[0].startswith("LLM-Fehler: ") and result.usage.calls == 2
|
||||
assert observed_behaviors(result) == set() # ein Absturz ist kein ehrliches Scheitern
|
||||
write_report(result, str(tmp_path / "r.md"))
|
||||
text = (tmp_path / "r.md").read_text(encoding="utf-8")
|
||||
assert "Lauf abgebrochen" in text and "abgelehnt" not in text
|
||||
|
||||
|
||||
def test_benchmark_counts_an_llm_failure_as_failure_with_costs(monkeypatch):
|
||||
from benchmark import benchmark
|
||||
session = FakeSession([_llm_weg("Extraktion")], [])
|
||||
monkeypatch.setattr(benchmark, "run_pipeline", lambda *a, **k: run_pipeline("Aufgabe", session=session))
|
||||
zeile = benchmark.run_case({"name": "t", "task": "Aufgabe", "expected": {"objective_value": 9, "tolerance": 1e-6}},
|
||||
"fake", 1)
|
||||
assert zeile["solver_status"] == "LLM-Fehler" and zeile["llm_calls"] == 2 and "error" not in zeile
|
||||
assert zeile["objective_ok"] is False and zeile["task_ok"] is False
|
||||
|
||||
|
||||
def test_llm_failure_in_the_infeasible_recheck_keeps_the_first_result():
|
||||
session = FakeSession(specs=[GOOD, _llm_weg("Extraktion")], reviews=[OK], codes=[SolverCode(code=INFEASIBLE_CODE)])
|
||||
result = run_pipeline("Aufgabe", session=session, max_revisions=0, max_restarts=0)
|
||||
assert result.solver_run.status == SolverStatus.INFEASIBLE and not result.infeasible_confirmed
|
||||
assert not result.reliable and "Gegenprobe scheiterte am LLM" in result.reliability_issues[-1]
|
||||
|
|
|
|||
Loading…
Reference in a new issue