fix: Solver-Fehler gilt als unzuverlässig; Aufräumrunde ohne LLM

- Status error nach allen Reparaturen -> reliable=False („Solver-Fehler: …“, auch bei
  --resolve); vorher Exit-Code 0 ohne Warnkasten. Benchmark zählt einen Absturz nicht
  als ehrliches Scheitern.
- Report: „längster Weg“ bei maximize; Harness druckt „Gefundener Weg“.
- check_requested_method: Verneinung kurz davor im selben Satz („ohne/nicht/statt/
  anders als Dijkstra“) ist keine Forderung.
- Reparatur-Prompt enthält die Fehlertexte aller früheren Versuche.
- ruff (F, E9) als Test tests/test_lint.py, Konfiguration in pyproject.toml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-18 18:15:22 +02:00
commit f101b63947

View file

@ -115,7 +115,7 @@ def report(status, objective_value, solution, tables=None, headline=None, constr
print(" Ein Knoten ist: " + str(graph.get("node_meaning", "")))
print(" Ein Bogen ist: " + str(graph.get("arc_meaning", "")))
if graph.get("path"):
print(" Kürzester Weg: " + " → ".join(str(k) for k in graph["path"]))
print(" Gefundener Weg: " + " → ".join(str(k) for k in graph["path"])) # kürzester oder längster
for schritt in graph.get("steps") or []:
print(f" Marke endgültig: {{schritt.get('node')}} = {{schritt.get('label')}}")
if solution:
@ -644,7 +644,7 @@ def solver_prompt() -> str:
def generate_solver_code(
session: LLMSession, task: str, spec: OptimizationModelSpecification,
previous: Optional[SolverCode] = None, previous_error: Optional[str] = None, understanding=None,
data_prompt: str = "",
data_prompt: str = "", earlier_errors=(),
) -> SolverCode:
content = f"AUFGABENTEXT:\n{task}\n\nMODELLSPEZIFIKATION:\n{spec.model_dump_json(indent=2)}"
if data_prompt:
@ -655,9 +655,13 @@ def generate_solver_code(
content += ("\n\nZU BEANTWORTENDE TEILFRAGEN (für jede genau ein Eintrag in answers, "
f"question_id wie angegeben):\n{fragen}")
if previous is not None:
# Ältere Fehlversuche nur als Fehlertext (nicht der ganze Code): sonst behebt die Reparatur den letzten Fehler
# und führt den vorletzten wieder ein – die Versuche gehen im Kreis.
frueher = "".join(f"\n- Versuch {n}: {fehler[:400]}" for n, fehler in enumerate(earlier_errors, 1))
content += (
f"\n\nDER VORHERIGE CODE IST FEHLGESCHLAGEN.\nCODE:\n{previous.code}\n\nFEHLER:\n{previous_error}\n\n"
"Behebe den Fehler. Das Modell selbst darf dabei nicht verändert werden."
+ (f"FRÜHERE FEHLVERSUCHE (schon einmal aufgetreten – nicht wieder einführen):{frueher}\n\n" if frueher else "")
+ "Behebe den Fehler. Das Modell selbst darf dabei nicht verändert werden."
)
return session.create(
SolverCode,
@ -1176,6 +1180,7 @@ def solve(session: LLMSession, task: str, spec: OptimizationModelSpecification,
zerlegung = check_objective_decomposition(run, spec)
verschwiegen = check_answers_disclose_assumptions(run, solver_code.code, solver_code, spec)
attempt = 1
fehlerverlauf = [] # Fehler aller bisherigen Versuche, für die Reparatur
while (run.status == SolverStatus.ERROR or run.feasibility or run.undeclared_data or run.unsolved_scenarios
or einheiten or zerlegung or verschwiegen) and attempt <= retries:
# Alle Befundarten zugleich melden – sonst kostet jede weitere Art einen eigenen Reparaturversuch
@ -1190,7 +1195,9 @@ def solve(session: LLMSession, task: str, spec: OptimizationModelSpecification,
if befunde)
log(f"Solver-Code fehlgeschlagen – Reparaturversuch {attempt}/{retries}: {fehler[:200]}")
solver_code = generate_solver_code(session, task, spec, previous=solver_code, previous_error=fehler,
understanding=understanding, data_prompt=data_prompt)
understanding=understanding, data_prompt=data_prompt,
earlier_errors=fehlerverlauf)
fehlerverlauf.append(fehler)
solver_code.code = clean_generated_code(solver_code.code)
run = _check_and_run(solver_code.code, timeout, spec, understanding, task, data_files, data_prompt)
run.undeclared_data = check_generated_data(solver_code.code, solver_code, spec, data_files)