- 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>
74 lines
3.4 KiB
Python
74 lines
3.4 KiB
Python
"""Stufe 4 der Pipeline: LLM-Prüfung von Zielgröße und Annahmen; Entscheidung über Korrekturrunden."""
|
||
from pathlib import Path
|
||
from typing import List
|
||
|
||
from checks.model_checks import has_errors
|
||
from core.llm import LLMSession
|
||
from core.model_schema import AssumptionReview, AssumptionVerdict, CheckIssue, CheckSeverity, OptimizationModelSpecification
|
||
|
||
REVIEW_PROMPT_FILE = Path(__file__).parent / "Pruef-Prompt.txt"
|
||
|
||
|
||
def review_assumptions(session: LLMSession, task: str, spec: OptimizationModelSpecification) -> AssumptionReview:
|
||
return session.create(
|
||
AssumptionReview,
|
||
[
|
||
{"role": "system", "content": REVIEW_PROMPT_FILE.read_text(encoding="utf-8")},
|
||
{
|
||
"role": "user",
|
||
"content": f"AUFGABENTEXT:\n{task}\n\nMODELLSPEZIFIKATION:\n{spec.model_dump_json(indent=2)}",
|
||
},
|
||
],
|
||
step="Prüfung",
|
||
)
|
||
|
||
|
||
# Warnungen, die eine Korrekturrunde wert sind: keine Fehler (ein richtiges Ergebnis bleibt zuverlässig),
|
||
# aber in einer Runde billig zu beheben. Unerklärte Fachbegriffe kosten den Leser sonst das Verständnis.
|
||
REVISION_WORTHY_WARNINGS = {"UNEXPLAINED_JARGON", "UNUSED_FACTS"}
|
||
|
||
|
||
def needs_work(review: AssumptionReview, issues: List[CheckIssue]) -> bool:
|
||
"""Rechtfertigt der Stand eine Korrekturrunde?"""
|
||
return (review.needs_revision or has_errors(issues)
|
||
or any(i.code in REVISION_WORTHY_WARNINGS for i in issues))
|
||
|
||
|
||
def revision_reasons(review: AssumptionReview, issues: List[CheckIssue]) -> List[str]:
|
||
"""Warum eine Korrekturrunde nötig ist – fürs Log.
|
||
|
||
Bisher stand dort nur „Probleme gefunden“; welche Beanstandung eine Runde auslöste, ließ sich im
|
||
Nachhinein nicht mehr feststellen (beobachtet an wagner_whitin_dijkstra, 20260917-160618).
|
||
"""
|
||
gruende = []
|
||
oc = review.objective_check
|
||
if not oc.matches:
|
||
gruende.append(f"Zielgröße passt nicht: {oc.explanation[:160]}")
|
||
if not oc.units_consistent:
|
||
gruende.append(f"Einheiten der Zielfunktion uneinheitlich: {oc.unit_explanation[:160]}")
|
||
faulty = {AssumptionVerdict.CONTRADICTS_TASK, AssumptionVerdict.STRICTER_THAN_TASK, AssumptionVerdict.WEAKER_THAN_TASK}
|
||
for check in review.checks:
|
||
if check.verdict in faulty:
|
||
gruende.append(f"Annahme {check.verdict.value}: {check.unclear_point[:120]}")
|
||
for fehlend in review.missing_assumptions:
|
||
gruende.append(f"fehlende Annahme: {fehlend.unclear_point[:120]}")
|
||
for issue in issues:
|
||
if issue.severity == CheckSeverity.ERROR or issue.code in REVISION_WORTHY_WARNINGS:
|
||
gruende.append(f"[{issue.code}] {issue.message[:120]}")
|
||
return gruende
|
||
|
||
|
||
def select_best(candidates: List[tuple]) -> int:
|
||
"""Index der besten (spec, issues, review)-Runde.
|
||
|
||
Rangfolge: passende Zielgröße vor unpassender (sonst womöglich Fremdproblem), keine Check-Errors vor Errors,
|
||
Prüfung ohne Beanstandung vor Beanstandung, weniger Errors;
|
||
bei Gleichstand gewinnt die spätere Runde. Verhindert, dass eine Korrekturrunde, die das Modell
|
||
verschlechtert (z. B. halluziniertes Fremdproblem), die bessere frühere Fassung ersetzt.
|
||
"""
|
||
def score(index):
|
||
_, issues, review = candidates[index]
|
||
errors = sum(1 for i in issues if i.severity == CheckSeverity.ERROR)
|
||
return (not review.objective_check.matches, errors > 0, review.needs_revision, errors, -index)
|
||
|
||
return min(range(len(candidates)), key=score)
|