feat: Benchmarkklasse „ehrlich scheitern“ – bewertet Verhalten statt Zielwert
expected.json kann statt objective_value eine Liste "behavior" nennen (rejected | unreliable | assumed_data), eines genügt. Treffer = Verhalten gezeigt, erfüllt = zusätzlich alle Teilfragen beantwortet (außer bei Ablehnung). Bei erwarteter Ablehnung wird kein Routing bewertet. Vier Fälle: reine Gleichung, reine Rechenaufgabe (-> abgelehnt), fehlende Kursdaten, fehlender Kundenbedarf (-> Annahme ausgewiesen oder unzuverlässig). Erste Messung 20260918-134339: 3/4; der fehlende Bedarf wird im Text parametrisch richtig behandelt, maschinell aber als „optimal 270, zuverlässig“ ohne ausgewiesene Annahme gemeldet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6ecfdf6ae0
commit
d6bad28cb2
1 changed files with 151 additions and 9 deletions
|
|
@ -94,6 +94,31 @@ def result_correct(solver_run, expected: dict) -> bool:
|
|||
return expected_status != "optimal" or objective_matches(solver_run.objective_value, expected)
|
||||
|
||||
|
||||
# Fälle „ehrlich scheitern“: schlecht gestellte Aufgaben ohne richtige Zahl. Bewertet wird, ob die Pipeline das
|
||||
# zugibt – eines der in expected["behavior"] genannten Verhalten genügt. Gefertigte Fälle mit Zielwert sind per
|
||||
# Konstruktion wohlgestellt und bestehen seit dem 17.09.2026 durchweg; die echten Befunde kamen aus solchen Aufgaben.
|
||||
BEHAVIORS = ("rejected", "unreliable", "assumed_data")
|
||||
|
||||
|
||||
def observed_behaviors(result) -> set:
|
||||
"""Welche ehrlichen Verhalten zeigt das Ergebnis? (abgelehnt / unzuverlässig / angenommene Daten ausgewiesen)"""
|
||||
gezeigt = set()
|
||||
if result.rejected:
|
||||
return {"rejected"}
|
||||
if not result.reliable:
|
||||
gezeigt.add("unreliable")
|
||||
if result.solver_code is not None and result.solver_code.data_assumptions:
|
||||
gezeigt.add("assumed_data")
|
||||
return gezeigt
|
||||
|
||||
|
||||
def behavior_matches(result, expected: dict) -> bool:
|
||||
unbekannt = set(expected["behavior"]) - set(BEHAVIORS)
|
||||
if unbekannt:
|
||||
raise ValueError(f"Unbekanntes Verhalten in expected.json: {sorted(unbekannt)} (erlaubt: {', '.join(BEHAVIORS)})")
|
||||
return bool(observed_behaviors(result) & set(expected["behavior"]))
|
||||
|
||||
|
||||
def objective_matches(actual, expected: dict) -> bool:
|
||||
if actual is None:
|
||||
return False
|
||||
|
|
@ -118,13 +143,17 @@ def run_case(case: dict, model: str, run_no: int) -> dict:
|
|||
return row
|
||||
|
||||
spec, solver_run = result.specification, result.solver_run
|
||||
ehrlich = behavior_matches(result, expected) if "behavior" in expected else None
|
||||
if spec is None: # abgelehnt: keine Optimierungsaufgabe erkannt
|
||||
fragen = len(result.understanding.sub_questions) if result.understanding else 0
|
||||
# Bei erwarteter Ablehnung gibt es kein Routing zu bewerten; sonst ist die Ablehnung ein Routing-Fehler
|
||||
routing = None if ehrlich else False
|
||||
row.update(seconds=round(time.monotonic() - start), problem_class=None, target_stack=None,
|
||||
class_ok=False, stack_ok=False, check_errors=0, check_warnings=len(result.checks),
|
||||
class_ok=routing, stack_ok=routing, check_errors=0, check_warnings=len(result.checks),
|
||||
revisions=0, review_ok=None, data_assumptions=0, solver_status="abgelehnt", solver_attempts=0,
|
||||
objective_value=None, objective_ok=False, questions=fragen, answered=0, answer_findings=0,
|
||||
task_ok=False, solver_error="; ".join(result.reliability_issues), reliable=False, restarts=0,
|
||||
objective_value=None, objective_ok=bool(ehrlich), questions=fragen, answered=0, answer_findings=0,
|
||||
task_ok=bool(ehrlich), behavior=sorted(observed_behaviors(result)),
|
||||
solver_error="; ".join(result.reliability_issues), reliable=False, restarts=0,
|
||||
providers=result.usage.providers if result.usage else {},
|
||||
responded_models=result.usage.responded_models if result.usage else {},
|
||||
llm_calls=result.usage.calls if result.usage else 0,
|
||||
|
|
@ -136,7 +165,13 @@ def run_case(case: dict, model: str, run_no: int) -> dict:
|
|||
fragen = {q.id for q in result.understanding.sub_questions} if result.understanding else set()
|
||||
antworten = {a.question_id for a in solver_run.answers} if solver_run else set()
|
||||
abnahme = [w for w in (solver_run.warnings if solver_run else []) if "Antwort zu" in w or "Teilfragen ohne" in w]
|
||||
objective_ok = result.reliable and result_correct(solver_run, expected)
|
||||
beantwortet = bool(fragen) and fragen <= antworten and not abnahme
|
||||
if ehrlich is not None:
|
||||
# Treffer = ehrliches Verhalten; erfüllt = zusätzlich alle Teilfragen beantwortet (mit ausgewiesener Annahme)
|
||||
objective_ok, task_ok = ehrlich, ehrlich and beantwortet
|
||||
else:
|
||||
objective_ok = result.reliable and result_correct(solver_run, expected)
|
||||
task_ok = bool(objective_ok and beantwortet and len(fragen) >= expected.get("min_sub_questions", 1))
|
||||
row.update(
|
||||
seconds=round(time.monotonic() - start),
|
||||
problem_class=spec.problem_class.value,
|
||||
|
|
@ -155,8 +190,8 @@ def run_case(case: dict, model: str, run_no: int) -> dict:
|
|||
questions=len(fragen),
|
||||
answered=len(fragen & antworten),
|
||||
answer_findings=len(abnahme),
|
||||
task_ok=bool(objective_ok and fragen and fragen <= antworten and not abnahme
|
||||
and len(fragen) >= expected.get("min_sub_questions", 1)),
|
||||
task_ok=task_ok,
|
||||
behavior=sorted(observed_behaviors(result)),
|
||||
solver_error=solver_run.error if solver_run else "; ".join(result.reliability_issues),
|
||||
reliable=result.reliable,
|
||||
restarts=result.restarts,
|
||||
|
|
@ -207,9 +242,10 @@ def print_table(rows):
|
|||
hits = sum(1 for r in mine if r.get("objective_ok"))
|
||||
erfuellt = sum(1 for r in mine if r.get("task_ok"))
|
||||
routing = sum(1 for r in mine if r.get("class_ok") and r.get("stack_ok"))
|
||||
mit_routing = sum(1 for r in mine if r.get("class_ok") is not None or "error" in r)
|
||||
seconds = sum(r["seconds"] for r in mine) / len(mine)
|
||||
cost = sum(r.get("cost_usd") or 0 for r in mine)
|
||||
print(f" {model}: Ergebnis korrekt {hits}/{len(mine)}, Aufgabe erfüllt {erfuellt}/{len(mine)}, Routing korrekt {routing}/{len(mine)}, "
|
||||
print(f" {model}: Ergebnis korrekt {hits}/{len(mine)}, Aufgabe erfüllt {erfuellt}/{len(mine)}, Routing korrekt {routing}/{mit_routing}, "
|
||||
f"Ø {seconds:.0f} s pro Lauf, Kosten gesamt ${cost:.4f} (ohne abgebrochene Läufe)")
|
||||
antwortend = {}
|
||||
for r in mine:
|
||||
|
|
|
|||
Loading…
Reference in a new issue