update
This commit is contained in:
@@ -241,7 +241,7 @@ async def pruefung_bewertung(
|
|||||||
topic: str, baustein: str, section: str, vertiefung: str | None,
|
topic: str, baustein: str, section: str, vertiefung: str | None,
|
||||||
frage: str, messages: list[dict], gute_antworten: int, provider: str = DEFAULT_PROVIDER,
|
frage: str, messages: list[dict], gute_antworten: int, provider: str = DEFAULT_PROVIDER,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Aktion 'antwort': Antwort bewerten (Evaluator + Kritiker).
|
"""Aktion 'antwort_pruefen': verbindlich bewerten (Evaluator + Kritiker).
|
||||||
|
|
||||||
Gibt {"feedback", "bewertung": gut|schlecht, "bestanden"} · None bei Fehler.
|
Gibt {"feedback", "bewertung": gut|schlecht, "bestanden"} · None bei Fehler.
|
||||||
"""
|
"""
|
||||||
@@ -257,6 +257,28 @@ async def pruefung_bewertung(
|
|||||||
return None
|
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(
|
async def baustein_diskussion(
|
||||||
topic: str, baustein: str, section: str, vertiefung: str | None,
|
topic: str, baustein: str, section: str, vertiefung: str | None,
|
||||||
frage: str, letzte_bewertung: str | None, messages: list[dict], provider: str = DEFAULT_PROVIDER,
|
frage: str, letzte_bewertung: str | None, messages: list[dict], provider: str = DEFAULT_PROVIDER,
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class BausteinPruefungRequest(BaseModel):
|
|||||||
topic: str = Field(min_length=1, max_length=100)
|
topic: str = Field(min_length=1, max_length=100)
|
||||||
baustein: str = Field(min_length=1, max_length=200)
|
baustein: str = Field(min_length=1, max_length=200)
|
||||||
section: str = Field(default="", max_length=20000)
|
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)
|
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)
|
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
|
score_vor_frage: int = 0 # Score, als die Frage gestellt wurde → driftfreies (Re-)Bewerten
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from database import (
|
|||||||
)
|
)
|
||||||
from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine
|
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 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 guide import generate_guide, guide_slot_dateien
|
||||||
from pipeline import cancel_guide
|
from pipeline import cancel_guide
|
||||||
from regeln import FORMATE, formate_stats, guide_lock, ist_absolviert, lade_lernstand, thema_abgeschlossen
|
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")
|
raise HTTPException(502, "Diskussion fehlgeschlagen — bitte erneut versuchen")
|
||||||
return {"reply": reply, "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
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).
|
# (nach einer Diskussion endet der Dialog mit dem Tutor; Re-Bewertung bleibt erlaubt).
|
||||||
if not any(m.get("role") == "user" for m in msgs):
|
if not any(m.get("role") == "user" for m in msgs):
|
||||||
raise HTTPException(400, "Antwort braucht eine Nutzer-Antwort")
|
raise HTTPException(400, "Antwort braucht eine Nutzer-Antwort")
|
||||||
if not req.frage.strip():
|
if not req.frage.strip():
|
||||||
raise HTTPException(400, "Antwort braucht eine laufende Frage")
|
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(
|
data = await pruefung_bewertung(
|
||||||
req.topic, req.baustein, req.section, vertiefung, req.frage, msgs, gute, provider=req.provider,
|
req.topic, req.baustein, req.section, vertiefung, req.frage, msgs, gute, provider=req.provider,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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) {
|
function bewerten(res) {
|
||||||
letztesFeedback.value = res.feedback || ''
|
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'
|
pruefPhase.value = 'bewertet'
|
||||||
|
verdictPruefen() // im Hintergrund verbindlich prüfen lassen
|
||||||
}
|
}
|
||||||
|
|
||||||
function antwortPayload() {
|
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() {
|
function antwortAbgeben() {
|
||||||
const text = pruefInput.value.trim()
|
const text = pruefInput.value.trim()
|
||||||
if (!text || pruefLoading.value) return
|
if (!text || pruefLoading.value) return
|
||||||
@@ -297,7 +325,7 @@ function neuBewerten() {
|
|||||||
|
|
||||||
<div v-if="pruefMessages.length" ref="pruefMessagesEl" class="bp-messages" @scroll="onPruefScroll">
|
<div v-if="pruefMessages.length" ref="pruefMessagesEl" class="bp-messages" @scroll="onPruefScroll">
|
||||||
<template v-for="(m, i) in pruefMessages" :key="i">
|
<template v-for="(m, i) in pruefMessages" :key="i">
|
||||||
<div v-if="m.kind === 'feedback'" class="bp-feedback" :class="m.bewertung">{{ m.content }}</div>
|
<div v-if="m.kind === 'feedback'" class="bp-feedback" :class="m.bewertung">{{ m.content }}<span v-if="!m.geprueft" class="bp-pruefend"> · wird geprüft…</span></div>
|
||||||
<div v-else-if="m.kind === 'fehler'" class="bp-error">{{ m.content }}</div>
|
<div v-else-if="m.kind === 'fehler'" class="bp-error">{{ m.content }}</div>
|
||||||
<div v-else-if="m.role === 'assistant'" class="bp-msg assistant markdown" v-html="renderMarkdown(m.content)"></div>
|
<div v-else-if="m.role === 'assistant'" class="bp-msg assistant markdown" v-html="renderMarkdown(m.content)"></div>
|
||||||
<div v-else class="bp-msg user">{{ m.content }}</div>
|
<div v-else class="bp-msg user">{{ m.content }}</div>
|
||||||
@@ -418,6 +446,7 @@ function neuBewerten() {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
.bp-pruefend { font-style: italic; opacity: 0.7; font-size: 0.92em; }
|
||||||
.bp-feedback.gut { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
|
.bp-feedback.gut { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
|
||||||
.bp-feedback.schlecht { background: var(--danger-soft, #fee2e2); border-color: var(--danger-border, #f87171); color: var(--danger); }
|
.bp-feedback.schlecht { background: var(--danger-soft, #fee2e2); border-color: var(--danger-border, #f87171); color: var(--danger); }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user