From 466818c47cfb4ba712991bd11a09681c5deca1b3 Mon Sep 17 00:00:00 2001 From: team3 Date: Mon, 15 Jun 2026 17:02:53 +0200 Subject: [PATCH] update --- backend/lernen.py | 24 ++++++++++++++++- backend/models.py | 2 +- backend/routes.py | 17 ++++++++++-- frontend/src/components/BausteinPanel.vue | 33 +++++++++++++++++++++-- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/backend/lernen.py b/backend/lernen.py index 7cd9a1e..d3b25d3 100644 --- a/backend/lernen.py +++ b/backend/lernen.py @@ -241,7 +241,7 @@ async def pruefung_bewertung( topic: str, baustein: str, section: str, vertiefung: str | None, frage: str, messages: list[dict], gute_antworten: int, provider: str = DEFAULT_PROVIDER, ) -> dict | None: - """Aktion 'antwort': Antwort bewerten (Evaluator + Kritiker). + """Aktion 'antwort_pruefen': verbindlich bewerten (Evaluator + Kritiker). Gibt {"feedback", "bewertung": gut|schlecht, "bestanden"} · None bei Fehler. """ @@ -257,6 +257,28 @@ async def pruefung_bewertung( return None +async def pruefung_bewertung_schnell( + topic: str, baustein: str, section: str, vertiefung: str | None, + frage: str, messages: list[dict], gute_antworten: int, provider: str = DEFAULT_PROVIDER, +) -> dict | None: + """Aktion 'antwort' (Vorschau): nur Evaluator, KEIN Kritiker — sofortiges Urteil. + + Wird optimistisch angezeigt; 'antwort_pruefen' liefert danach das geprüfte Urteil. + """ + try: + section_block, vertiefung_block = _bloecke(section, vertiefung) + transcript = _transcript(messages) if messages else "(leer)" + return await _gen_call( + "Baustein-Bewertung", "judge", _bewertung_schema, provider, + topic=topic, baustein=baustein, section_block=section_block, + vertiefung_block=vertiefung_block, frage=frage.strip() or "(keine Frage übergeben)", + transcript=transcript, gute_antworten=gute_antworten, noetig=NOETIG, kritik_block="(keine)", + ) + except Exception: + log.warning("[%s] Schnell-Bewertung fehlgeschlagen (%s)", topic, baustein, exc_info=True) + return None + + async def baustein_diskussion( topic: str, baustein: str, section: str, vertiefung: str | None, frage: str, letzte_bewertung: str | None, messages: list[dict], provider: str = DEFAULT_PROVIDER, diff --git a/backend/models.py b/backend/models.py index 3c27edc..be9e2f5 100644 --- a/backend/models.py +++ b/backend/models.py @@ -191,7 +191,7 @@ class BausteinPruefungRequest(BaseModel): topic: str = Field(min_length=1, max_length=100) baustein: str = Field(min_length=1, max_length=200) section: str = Field(default="", max_length=20000) - aktion: Literal["frage", "diskussion", "antwort"] = "frage" + aktion: Literal["frage", "diskussion", "antwort", "antwort_pruefen"] = "frage" frage: str = Field(default="", max_length=2000) # aktuell geprüfte Frage (für diskussion/antwort) letzte_bewertung: str = Field(default="", max_length=2000) # Feedback der letzten Bewertung (Kontext für diskussion) score_vor_frage: int = 0 # Score, als die Frage gestellt wurde → driftfreies (Re-)Bewerten diff --git a/backend/routes.py b/backend/routes.py index 779e6df..9dc5ebc 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -18,7 +18,7 @@ from database import ( ) from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine from elements import generate_element, chat_with_guide, chat_with_element, check_element, style_element, refine_suggestion -from lernen import NOETIG, MASTERY, MEISTERN, baustein_chat, baustein_diskussion, baustein_element_anlegen, pruefung_bewertung, pruefung_frage, score_berechnen, vertiefung_generieren +from lernen import NOETIG, MASTERY, MEISTERN, baustein_chat, baustein_diskussion, baustein_element_anlegen, pruefung_bewertung, pruefung_bewertung_schnell, pruefung_frage, score_berechnen, vertiefung_generieren from guide import generate_guide, guide_slot_dateien from pipeline import cancel_guide from regeln import FORMATE, formate_stats, guide_lock, ist_absolviert, lade_lernstand, thema_abgeschlossen @@ -238,12 +238,25 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest): raise HTTPException(502, "Diskussion fehlgeschlagen — bitte erneut versuchen") return {"reply": reply, "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert} - # aktion == "antwort" — mindestens eine Nutzer-Antwort muss im Dialog stehen + # aktion "antwort"/"antwort_pruefen" — mindestens eine Nutzer-Antwort muss im Dialog stehen # (nach einer Diskussion endet der Dialog mit dem Tutor; Re-Bewertung bleibt erlaubt). if not any(m.get("role") == "user" for m in msgs): raise HTTPException(400, "Antwort braucht eine Nutzer-Antwort") if not req.frage.strip(): raise HTTPException(400, "Antwort braucht eine laufende Frage") + + if req.aktion == "antwort": + # Vorschau: nur Bewerter, kein Kritiker, KEIN Persist, KEINE Meilensteine. + data = await pruefung_bewertung_schnell( + req.topic, req.baustein, req.section, vertiefung, req.frage, msgs, gute, provider=req.provider, + ) + if data is None: + raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen") + score = score_berechnen(req.score_vor_frage, data["bewertung"] == "gut", req.tier2, req.tier3, absolviert, gemeistert) + return {"feedback": data["feedback"], "bewertung": data["bewertung"], "gute_antworten": score, + "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert} + + # aktion == "antwort_pruefen": verbindlich (Bewerter + Kritiker), persistiert Score + Meilensteine. data = await pruefung_bewertung( req.topic, req.baustein, req.section, vertiefung, req.frage, msgs, gute, provider=req.provider, ) diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue index f900ad1..7cfaabe 100644 --- a/frontend/src/components/BausteinPanel.vue +++ b/frontend/src/components/BausteinPanel.vue @@ -193,10 +193,15 @@ function nachfragen() { ) } +let letzteFeedbackMsg = null // Referenz auf die zuletzt gezeigte (provisorische) Bewertungs-Bubble +let pruefenRun = 0 // nur die jüngste Hintergrund-Prüfung darf die Anzeige korrigieren + function bewerten(res) { letztesFeedback.value = res.feedback || '' - pruefMessages.value.push({ role: 'assistant', kind: 'feedback', content: res.feedback || '', bewertung: res.bewertung }) + pruefMessages.value.push({ role: 'assistant', kind: 'feedback', content: res.feedback || '', bewertung: res.bewertung, geprueft: false }) + letzteFeedbackMsg = pruefMessages.value[pruefMessages.value.length - 1] pruefPhase.value = 'bewertet' + verdictPruefen() // im Hintergrund verbindlich prüfen lassen } function antwortPayload() { @@ -206,6 +211,29 @@ function antwortPayload() { } } +// Hintergrund: voller Bewerter+Kritiker. Persistiert serverseitig; korrigiert die Anzeige bei Abweichung. +async function verdictPruefen() { + const meine = ++pruefenRun + const ziel = letzteFeedbackMsg + try { + const res = await pruefeBaustein({ + topic: props.topic, baustein: props.baustein, section: props.section, + provider: props.provider, messages: pruefDialog(), ...antwortPayload(), aktion: 'antwort_pruefen', + }) + if (meine !== pruefenRun) return // eine neuere Bewertung läuft → diese verwerfen + applyPruefung(res) + if (ziel) { + if (res.bewertung !== ziel.bewertung || (res.feedback && res.feedback !== ziel.content)) { + ziel.content = res.feedback || ziel.content + ziel.bewertung = res.bewertung + } + ziel.geprueft = true + } + } catch { + if (meine === pruefenRun && ziel) ziel.geprueft = true + } +} + function antwortAbgeben() { const text = pruefInput.value.trim() if (!text || pruefLoading.value) return @@ -297,7 +325,7 @@ function neuBewerten() {