feat: Warnung bei ungeschützter Sandbox, Prüf-Regel für konstante Kosten, Grund je Korrekturrunde
- sandbox_warning: fehlt bubblewrap oder ist SOLVER_SANDBOX=aus, steht das im Log und in SolverRun.warnings (vorher lief der Code stillschweigend ungeschützt) - Dijkstra-Markierung: der Report erklärt den Begriff selbst (DIJKSTRA_EXPLANATION), check_text_quality zählt das als Erklärung; Stamm 'markier' -> 'markierung' - Pruef-Prompt Regel 7: entscheidungsunabhängige Terme ohne Zahlenwert dürfen weggelassen oder symbolisch mitgeführt werden (wagner_whitin_dijkstra: 1+1 statt 2+1 Korrekturrunden, n=2) - revision_reasons: jede Korrekturrunde nennt ihren Auslöser im Log Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
26f988a953
commit
0f1ab04a8d
1 changed files with 148 additions and 8 deletions
|
|
@ -444,6 +444,23 @@ def check_constraint_coverage(constraints: list, spec: OptimizationModelSpecific
|
|||
return problems
|
||||
|
||||
|
||||
def sandbox_warning() -> Optional[str]:
|
||||
"""Hinweis, wenn der erzeugte Code ungeschützt läuft – sonst None.
|
||||
|
||||
Mit SOLVER_SANDBOX=auto lief der Code ohne bubblewrap bisher stillschweigend mit den Rechten des
|
||||
Nutzers; aufgefallen beim Schreiben der Bedienungsanleitung. Der Hinweis steht deshalb im Log und
|
||||
in `SolverRun.warnings` (damit im Report), bewusst ohne den Lauf zu verweigern – dafür gibt es `bwrap`.
|
||||
"""
|
||||
if SANDBOX == "aus":
|
||||
return ("Sandbox abgeschaltet (SOLVER_SANDBOX=aus): der vom LLM erzeugte Code lief ohne Isolierung "
|
||||
"mit den Rechten des Benutzers.")
|
||||
if SANDBOX != "bwrap" and not shutil.which("bwrap"):
|
||||
return ("Sandbox fehlt: bubblewrap ist nicht installiert, der vom LLM erzeugte Code lief ohne Isolierung "
|
||||
"mit den Rechten des Benutzers. Abhilfe: 'sudo apt install bubblewrap'; "
|
||||
"SOLVER_SANDBOX=bwrap verweigert ungeschützte Läufe.")
|
||||
return None
|
||||
|
||||
|
||||
def sandbox_command(script: Path, tmp: str) -> List[str]:
|
||||
"""Aufrufzeile für den Solver-Lauf – mit bubblewrap isoliert, sonst direkt.
|
||||
|
||||
|
|
@ -667,18 +684,30 @@ def fix_transliterations(text: str, extra: dict | None = None) -> str:
|
|||
return re.sub(r"\w+", wort, text or "")
|
||||
|
||||
|
||||
# Feste Erklärung im Report (Graphen-Abschnitt, steht VOR den Ergebnistabellen), sobald Dijkstra-Schritte da sind
|
||||
DIJKSTRA_EXPLANATION = ("Dijkstra nimmt in jedem Schritt den noch nicht endgültigen Knoten mit der kleinsten Marke. "
|
||||
"Weil die Marken dabei nie fallen, steht jede endgültige Marke schon fest, sobald sie "
|
||||
"vergeben wird – das ist der Grund, warum ein Durchlauf genügt.")
|
||||
|
||||
|
||||
def check_text_quality(run, summary: str = "") -> list:
|
||||
"""Texte für Menschen prüfen (ohne LLM): unerklärte Fachbegriffe in Antwortsatz und Tabellen.
|
||||
|
||||
Umschriebene Umlaute werden nicht mehr gemeldet, sondern still berichtigt (`repair_texts`):
|
||||
die Warnung stand in fast jedem Lauf und bewirkte nichts – im Code sind Umschreibungen erlaubt.
|
||||
|
||||
Als Erklärung zählt neben `executive_summary` auch, was der Report selbst vor den Tabellen erklärt:
|
||||
der Markierungsverlauf bei Dijkstra. Beobachtet (20260917-160618): „Marke“ in beiden Dijkstra-Läufen
|
||||
gemeldet, obwohl die Aufgabe Dijkstra ausdrücklich verlangt und der Report den Begriff erklärt – die
|
||||
Zusammenfassung entsteht vor der Solver-Stufe und kennt ihn noch nicht.
|
||||
"""
|
||||
texte = [("headline", run.headline or "")]
|
||||
for table in run.tables:
|
||||
texte.append((f"Tabelle '{table.title}'", " ".join([table.title, *map(str, table.columns), table.note or ""])))
|
||||
befunde = []
|
||||
if summary:
|
||||
offen = unexplained_jargon(" ".join(text for _, text in texte), summary)
|
||||
erklaert = summary + (" " + DIJKSTRA_EXPLANATION if run.graph and run.graph.steps else "")
|
||||
offen = unexplained_jargon(" ".join(text for _, text in texte), erklaert)
|
||||
if offen:
|
||||
befunde.append(f"Fachbegriffe in Antwortsatz oder Ergebnistabellen, die die Zusammenfassung nicht "
|
||||
f"erklärt: {', '.join(offen)}.")
|
||||
|
|
@ -953,6 +982,8 @@ def _check_and_run(code: str, timeout: int, spec: OptimizationModelSpecification
|
|||
except CodeRejected as exc:
|
||||
return SolverRun(status=SolverStatus.ERROR, error=f"Code abgelehnt: {exc}")
|
||||
run = run_solver_code(code, timeout=timeout, data_files=data_files)
|
||||
if (ungeschuetzt := sandbox_warning()):
|
||||
run.warnings.append(ungeschuetzt)
|
||||
run.feasibility = check_feasibility(run, spec)
|
||||
run.warnings += check_sensitivity(run, spec.objective.sense.value)
|
||||
run.warnings += check_multistart(run, spec.objective.sense.value)
|
||||
|
|
@ -974,6 +1005,8 @@ def solve(session: LLMSession, task: str, spec: OptimizationModelSpecification,
|
|||
|
||||
infeasible/unbounded gelten nicht als Fehler und werden nicht repariert.
|
||||
"""
|
||||
if (ungeschuetzt := sandbox_warning()):
|
||||
log(f"WARNUNG – {ungeschuetzt}")
|
||||
solver_code = generate_solver_code(session, task, spec, understanding=understanding,
|
||||
data_prompt=data_prompt)
|
||||
solver_code.code = clean_generated_code(solver_code.code)
|
||||
|
|
|
|||
Loading…
Reference in a new issue