update
This commit is contained in:
@@ -26,6 +26,7 @@ VERTIEFUNG_TIMEOUT = 600
|
|||||||
CHAT_TIMEOUT = 240
|
CHAT_TIMEOUT = 240
|
||||||
PRUEFUNG_TIMEOUT = 120 # kurze JSON-Turns; deckelt die Serien-Latenz pro Prüfungs-Schritt
|
PRUEFUNG_TIMEOUT = 120 # kurze JSON-Turns; deckelt die Serien-Latenz pro Prüfungs-Schritt
|
||||||
KRITIK_MAX_RUNDEN = 2 # Generator → Kritiker → ggf. Neu, höchstens so oft
|
KRITIK_MAX_RUNDEN = 2 # Generator → Kritiker → ggf. Neu, höchstens so oft
|
||||||
|
MAX_NACHFRAGEN = 2 # mündliche Prüfung: höchstens so viele Folgefragen, dann Urteil erzwingen
|
||||||
|
|
||||||
|
|
||||||
def score_berechnen(
|
def score_berechnen(
|
||||||
@@ -114,14 +115,20 @@ def _frage_schema(data) -> dict | None:
|
|||||||
|
|
||||||
|
|
||||||
def _bewertung_schema(data) -> dict | None:
|
def _bewertung_schema(data) -> dict | None:
|
||||||
"""{"feedback": str, "bewertung": "gut"|"schlecht", "bestanden": bool} · sonst None."""
|
"""{"status": "gut"|"schlecht"|"nachfrage", "feedback": str, "frage": str, "bestanden": bool}.
|
||||||
|
|
||||||
|
Bei status "nachfrage" muss `frage` (die Folgefrage) gefüllt sein. · sonst None.
|
||||||
|
"""
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
return None
|
return None
|
||||||
feedback = str(data.get("feedback", "")).strip()
|
feedback = str(data.get("feedback", "")).strip()
|
||||||
bewertung = data.get("bewertung")
|
status = data.get("status")
|
||||||
if not feedback or bewertung not in ("gut", "schlecht"):
|
frage = str(data.get("frage", "")).strip()
|
||||||
|
if not feedback or status not in ("gut", "schlecht", "nachfrage"):
|
||||||
return None
|
return None
|
||||||
return {"feedback": feedback, "bewertung": bewertung, "bestanden": data.get("bestanden") is True}
|
if status == "nachfrage" and not frage:
|
||||||
|
return None
|
||||||
|
return {"status": status, "feedback": feedback, "frage": frage, "bestanden": data.get("bestanden") is True}
|
||||||
|
|
||||||
|
|
||||||
async def _gen_call(name: str, role: str, schema, provider: str, **kwargs) -> dict | None:
|
async def _gen_call(name: str, role: str, schema, provider: str, **kwargs) -> dict | None:
|
||||||
@@ -154,7 +161,7 @@ def _kritik_block(vorversion: str, probleme: list[str]) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _bewertung_text(bew: dict) -> str:
|
def _bewertung_text(bew: dict) -> str:
|
||||||
return f"Bewertung: {bew['bewertung']}\nFeedback: {bew['feedback']}"
|
return f"Bewertung: {bew['status']}\nFeedback: {bew['feedback']}"
|
||||||
|
|
||||||
|
|
||||||
async def _frage_mit_kritik(
|
async def _frage_mit_kritik(
|
||||||
@@ -186,12 +193,14 @@ async def _frage_mit_kritik(
|
|||||||
|
|
||||||
async def _bewertung_mit_kritik(
|
async def _bewertung_mit_kritik(
|
||||||
topic: str, baustein: str, section_block: str, vertiefung_block: str,
|
topic: str, baustein: str, section_block: str, vertiefung_block: str,
|
||||||
frage: str, transcript: str, gute_antworten: int, provider: str,
|
frage: str, transcript: str, gute_antworten: int, rest_nachfragen: int, provider: str,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Antwort zur Frage bewerten, vom Kritiker prüfen lassen, bei Fehlurteil neu.
|
"""Antwort beurteilen: gut/schlecht (mit Kritiker) ODER nachfrage (Folgefrage, ohne Kritiker).
|
||||||
|
|
||||||
`frage` ankert, welche Frage geprüft wird; der Dialog (transcript) liefert die
|
`frage` ankert die geprüfte Frage; der Dialog liefert Antwort + etwaige Folgefragen.
|
||||||
Antwort und eine etwaige Diskussion — so kann eine Re-Bewertung das Argument sehen.
|
`rest_nachfragen` = wie viele Folgefragen noch erlaubt sind (0 → muss entscheiden).
|
||||||
|
Eine „nachfrage" wird sofort zurückgegeben (kein Verdikt zu prüfen). Verdikte
|
||||||
|
durchlaufen den Kritiker-Loop wie bisher.
|
||||||
"""
|
"""
|
||||||
kritik_block = "(keine)"
|
kritik_block = "(keine)"
|
||||||
bew = None
|
bew = None
|
||||||
@@ -200,10 +209,13 @@ async def _bewertung_mit_kritik(
|
|||||||
"Baustein-Bewertung", "judge", _bewertung_schema, provider,
|
"Baustein-Bewertung", "judge", _bewertung_schema, provider,
|
||||||
topic=topic, baustein=baustein, section_block=section_block,
|
topic=topic, baustein=baustein, section_block=section_block,
|
||||||
vertiefung_block=vertiefung_block, frage=frage, transcript=transcript,
|
vertiefung_block=vertiefung_block, frage=frage, transcript=transcript,
|
||||||
gute_antworten=gute_antworten, noetig=NOETIG, kritik_block=kritik_block,
|
gute_antworten=gute_antworten, noetig=NOETIG, rest_nachfragen=rest_nachfragen,
|
||||||
|
kritik_block=kritik_block,
|
||||||
)
|
)
|
||||||
if bew is None:
|
if bew is None:
|
||||||
return None
|
return None
|
||||||
|
if bew["status"] == "nachfrage":
|
||||||
|
return bew # Folgefrage → kein Kritiker, keine Wertung
|
||||||
probleme = await _kritik_call(
|
probleme = await _kritik_call(
|
||||||
"Baustein-Bewertung-Kritik", provider,
|
"Baustein-Bewertung-Kritik", provider,
|
||||||
topic=topic, baustein=baustein, section_block=section_block,
|
topic=topic, baustein=baustein, section_block=section_block,
|
||||||
@@ -239,19 +251,26 @@ async def pruefung_frage(
|
|||||||
|
|
||||||
async def pruefung_bewertung(
|
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, nachfrage_runde: int = 0,
|
||||||
|
provider: str = DEFAULT_PROVIDER,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Aktion 'antwort': Antwort zur Frage bewerten (Evaluator + Kritiker).
|
"""Aktion 'antwort': Antwort beurteilen (Evaluator + Kritiker).
|
||||||
|
|
||||||
Gibt {"feedback", "bewertung", "bestanden"} · None bei Fehler.
|
Gibt {"status": gut|schlecht|nachfrage, "feedback", "frage", "bestanden"} · None bei Fehler.
|
||||||
|
`nachfrage_runde` = bisherige Folgefragen dieser Frage; bei erschöpftem Budget wird
|
||||||
|
ein erneutes „nachfrage" zu „schlecht" gezwungen (der Lerner konnte es nicht zeigen).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
section_block, vertiefung_block = _bloecke(section, vertiefung)
|
section_block, vertiefung_block = _bloecke(section, vertiefung)
|
||||||
transcript = _transcript(messages) if messages else "(leer)"
|
transcript = _transcript(messages) if messages else "(leer)"
|
||||||
return await _bewertung_mit_kritik(
|
rest = max(0, MAX_NACHFRAGEN - nachfrage_runde)
|
||||||
|
bew = await _bewertung_mit_kritik(
|
||||||
topic, baustein, section_block, vertiefung_block,
|
topic, baustein, section_block, vertiefung_block,
|
||||||
frage.strip() or "(keine Frage übergeben)", transcript, gute_antworten, provider,
|
frage.strip() or "(keine Frage übergeben)", transcript, gute_antworten, rest, provider,
|
||||||
)
|
)
|
||||||
|
if bew and bew["status"] == "nachfrage" and rest <= 0:
|
||||||
|
return {"status": "schlecht", "feedback": bew["feedback"], "frage": "", "bestanden": False}
|
||||||
|
return bew
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning("[%s] Bewertung fehlgeschlagen (%s)", topic, baustein, exc_info=True)
|
log.warning("[%s] Bewertung fehlgeschlagen (%s)", topic, baustein, exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ class BausteinPruefungRequest(BaseModel):
|
|||||||
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
|
||||||
|
nachfrage_runde: int = 0 # bisherige Folgefragen dieser Frage (mündliche Prüfung)
|
||||||
tier2: bool = False # ganzer Guide absolviert (alle ≥3) → −1 bei falsch, Deckel 10
|
tier2: bool = False # ganzer Guide absolviert (alle ≥3) → −1 bei falsch, Deckel 10
|
||||||
tier3: bool = False # ganzer Guide verstanden (alle ≥10) → Meisterpfad, −2 bei falsch, Deckel 25
|
tier3: bool = False # ganzer Guide verstanden (alle ≥10) → Meisterpfad, −2 bei falsch, Deckel 25
|
||||||
messages: list[ChatMessage] = [] # Dialog bisher; leer = erste Frage
|
messages: list[ChatMessage] = [] # Dialog bisher; leer = erste Frage
|
||||||
|
|||||||
@@ -243,14 +243,20 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
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")
|
||||||
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,
|
||||||
|
nachfrage_runde=req.nachfrage_runde, provider=req.provider,
|
||||||
)
|
)
|
||||||
if data is None:
|
if data is None:
|
||||||
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
||||||
|
|
||||||
|
# Mündliche Prüfung: noch unklar → Folgefrage stellen, KEINE Wertung, kein Score.
|
||||||
|
if data["status"] == "nachfrage":
|
||||||
|
return {"frage": data["frage"], "feedback": data["feedback"], "bewertung": None,
|
||||||
|
"gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
||||||
|
|
||||||
# Score driftfrei aus dem Basis-Score rechnen (Re-Bewertung ersetzt das vorige Ergebnis).
|
# Score driftfrei aus dem Basis-Score rechnen (Re-Bewertung ersetzt das vorige Ergebnis).
|
||||||
score = score_berechnen(
|
score = score_berechnen(
|
||||||
req.score_vor_frage, data["bewertung"] == "gut", req.tier2, req.tier3, absolviert, gemeistert,
|
req.score_vor_frage, data["status"] == "gut", req.tier2, req.tier3, absolviert, gemeistert,
|
||||||
)
|
)
|
||||||
gute = await set_baustein_score(req.topic, req.baustein, score)
|
gute = await set_baustein_score(req.topic, req.baustein, score)
|
||||||
if score >= NOETIG and not absolviert:
|
if score >= NOETIG and not absolviert:
|
||||||
@@ -263,7 +269,7 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
if score >= MEISTERN and not gemeistert:
|
if score >= MEISTERN and not gemeistert:
|
||||||
await set_baustein_gemeistert(req.topic, req.baustein)
|
await set_baustein_gemeistert(req.topic, req.baustein)
|
||||||
gemeistert = True
|
gemeistert = True
|
||||||
return {"feedback": data["feedback"], "bewertung": data["bewertung"], "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
return {"feedback": data["feedback"], "bewertung": data["status"], "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
||||||
|
|
||||||
|
|
||||||
# --- Guides ---
|
# --- Guides ---
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const pruefLoading = ref(false)
|
|||||||
const aktuelleFrage = ref('') // ankert Bewertung/Diskussion
|
const aktuelleFrage = ref('') // ankert Bewertung/Diskussion
|
||||||
const letztesFeedback = ref('') // Kontext für die Diskussion über eine Bewertung
|
const letztesFeedback = ref('') // Kontext für die Diskussion über eine Bewertung
|
||||||
const scoreVorFrage = ref(0) // Score, als die aktuelle Frage gestellt wurde → driftfreies (Re-)Bewerten
|
const scoreVorFrage = ref(0) // Score, als die aktuelle Frage gestellt wurde → driftfreies (Re-)Bewerten
|
||||||
|
const nachfrageRunde = ref(0) // mündliche Prüfung: bisherige Folgefragen dieser Frage
|
||||||
const pruefMessagesEl = ref(null)
|
const pruefMessagesEl = ref(null)
|
||||||
const pruefInputEl = ref(null)
|
const pruefInputEl = ref(null)
|
||||||
const pruefStick = ref(true) // nur auto-scrollen, wenn der Nutzer (fast) unten ist
|
const pruefStick = ref(true) // nur auto-scrollen, wenn der Nutzer (fast) unten ist
|
||||||
@@ -133,6 +134,7 @@ function frageAnfordern() {
|
|||||||
aktuelleFrage.value = res.frage
|
aktuelleFrage.value = res.frage
|
||||||
letztesFeedback.value = ''
|
letztesFeedback.value = ''
|
||||||
scoreVorFrage.value = res.gute_antworten // Basis für (Re-)Bewertung dieser Frage
|
scoreVorFrage.value = res.gute_antworten // Basis für (Re-)Bewertung dieser Frage
|
||||||
|
nachfrageRunde.value = 0 // neue Frage → Folgefragen-Zähler zurücksetzen
|
||||||
pruefMessages.value.push({ role: 'assistant', kind: 'frage', content: res.frage })
|
pruefMessages.value.push({ role: 'assistant', kind: 'frage', content: res.frage })
|
||||||
pruefPhase.value = 'frage_offen'
|
pruefPhase.value = 'frage_offen'
|
||||||
})
|
})
|
||||||
@@ -151,21 +153,36 @@ function nachfragen() {
|
|||||||
|
|
||||||
function bewerten(res) {
|
function bewerten(res) {
|
||||||
letztesFeedback.value = res.feedback || ''
|
letztesFeedback.value = res.feedback || ''
|
||||||
|
// Mündliche Prüfung: Folgefrage statt Wertung (bewertung null, frage gesetzt).
|
||||||
|
if (res.bewertung == null && res.frage) {
|
||||||
|
nachfrageRunde.value += 1
|
||||||
|
const inhalt = (res.feedback ? res.feedback + '\n\n' : '') + res.frage
|
||||||
|
pruefMessages.value.push({ role: 'assistant', kind: 'folgefrage', content: inhalt })
|
||||||
|
pruefPhase.value = 'frage_offen' // weiter antworten — kein Punkt
|
||||||
|
return
|
||||||
|
}
|
||||||
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 })
|
||||||
pruefPhase.value = 'bewertet'
|
pruefPhase.value = 'bewertet'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function antwortPayload() {
|
||||||
|
return {
|
||||||
|
aktion: 'antwort', frage: aktuelleFrage.value, score_vor_frage: scoreVorFrage.value,
|
||||||
|
nachfrage_runde: nachfrageRunde.value, tier2: props.tier2, tier3: props.tier3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function antwortAbgeben() {
|
function antwortAbgeben() {
|
||||||
const text = pruefInput.value.trim()
|
const text = pruefInput.value.trim()
|
||||||
if (!text || pruefLoading.value) return
|
if (!text || pruefLoading.value) return
|
||||||
pruefMessages.value.push({ role: 'user', kind: 'antwort', content: text })
|
pruefMessages.value.push({ role: 'user', kind: 'antwort', content: text })
|
||||||
pruefInput.value = ''
|
pruefInput.value = ''
|
||||||
pruefSenden({ aktion: 'antwort', frage: aktuelleFrage.value, score_vor_frage: scoreVorFrage.value, tier2: props.tier2, tier3: props.tier3 }, bewerten)
|
pruefSenden(antwortPayload(), bewerten)
|
||||||
}
|
}
|
||||||
|
|
||||||
function neuBewerten() {
|
function neuBewerten() {
|
||||||
if (pruefLoading.value) return
|
if (pruefLoading.value) return
|
||||||
pruefSenden({ aktion: 'antwort', frage: aktuelleFrage.value, score_vor_frage: scoreVorFrage.value, tier2: props.tier2, tier3: props.tier3 }, bewerten)
|
pruefSenden(antwortPayload(), bewerten)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Du bewertest, ob ein Lerner die geprüfte Frage verstanden beantwortet hat — Baustein "{baustein}" aus dem Lern-Guide zum Thema "{topic}".
|
Du bist Prüfer in einer MÜNDLICHEN Prüfung zum Baustein "{baustein}" aus dem Lern-Guide zum Thema "{topic}". Dein Ziel: herausfinden, ob der Lerner die geprüfte Frage WIRKLICH versteht. Wie ein echter Prüfer fragst du nach, bevor du urteilst.
|
||||||
|
|
||||||
GEPRÜFTE FRAGE:
|
GEPRÜFTE FRAGE:
|
||||||
{frage}
|
{frage}
|
||||||
@@ -14,21 +14,27 @@ STAND: {gute_antworten} von {noetig} Antworten waren bisher gut.
|
|||||||
PRÜFUNGS-VERLAUF (Antwort des Lerners und etwaige Diskussion):
|
PRÜFUNGS-VERLAUF (Antwort des Lerners und etwaige Diskussion):
|
||||||
{transcript}
|
{transcript}
|
||||||
|
|
||||||
Bewerte die Antwort des Lerners auf die GEPRÜFTE FRAGE — auf Basis seiner Antwort UND der Diskussion im Verlauf.
|
Beurteile den ganzen Verlauf — die Antwort(en) des Lerners auf die GEPRÜFTE FRAGE und etwaige Folgefragen/Diskussion.
|
||||||
|
|
||||||
FACHLICHE REFERENZ — WICHTIG:
|
FACHLICHE REFERENZ — WICHTIG:
|
||||||
- Die Guide-Fassung und die Vertiefung oben sind die fachliche Referenz. Deine Bewertung darf ihr NIE widersprechen.
|
- Die Guide-Fassung und die Vertiefung oben sind die fachliche Referenz. Dein Urteil darf ihr NIE widersprechen.
|
||||||
- Behaupte nichts, was nicht aus dem Material folgt. Erfinde keine Zusatzannahmen.
|
- Behaupte nichts, was nicht aus dem Material folgt. Erfinde keine Zusatzannahmen.
|
||||||
- Widerspricht dir der Lerner mit Bezug aufs Material: Prüfe ZUERST deine eigene Annahme gegen die Referenz. Hat der Lerner SACHLICH und mit Material-Bezug recht, gib es offen zu und bewerte als "gut" — aber gib NICHT aus Höflichkeit oder auf bloßes Beharren hin nach.
|
- Widerspricht dir der Lerner mit Bezug aufs Material: Prüfe ZUERST deine eigene Annahme. Hat er SACHLICH recht, gib es offen zu — aber gib NICHT aus Höflichkeit oder auf bloßes Beharren hin nach.
|
||||||
|
|
||||||
SO BEWERTEST DU:
|
DREI MÖGLICHE URTEILE (`status`):
|
||||||
- "gut" = die Erklärung zeigt echtes Verständnis in eigenen Worten. Eine knappe, richtige Antwort reicht — verlange keine Vollständigkeit über die Frage hinaus.
|
- "gut" = die Antwort zeigt echtes Verständnis in eigenen Worten. Eine knappe, richtige Antwort reicht — keine Vollständigkeit über die Frage hinaus verlangen.
|
||||||
- "schlecht" = falsch, oberflächlich, abgelesen oder bloße Wiederholung einer früheren Antwort.
|
- "schlecht" = klar falsch, oder der Lerner zeigt (auch nach Nachfragen), dass er es nicht weiß. Beharren auf Falschem → schlecht.
|
||||||
- `feedback`: max. 1 Satz, sprich den Lerner direkt an. KEINE neue Frage. Begründe knapp, warum gut oder schlecht.
|
- "nachfrage" = die Antwort ist UNVOLLSTÄNDIG, teilweise richtig oder unklar — du kannst noch nicht sicher sagen, ob er es versteht. Dann stelle EINE gezielte Folgefrage genau zum fehlenden/unklaren Punkt. Noch KEIN Urteil, noch keine Wertung. Gib dem Lerner so die Chance, sich zu retten.
|
||||||
- `bestanden`: true NUR, wenn du schon vor den {noetig} guten Antworten sicher bist, dass der Lerner den Baustein verstanden hat. Im Zweifel false.
|
|
||||||
|
NACHFRAGE-BUDGET: Du hast noch {rest_nachfragen} Folgefragen übrig. Ist das 0, MUSST du dich für "gut" oder "schlecht" entscheiden — keine weitere Nachfrage.
|
||||||
|
|
||||||
|
REGELN FÜR FELDER:
|
||||||
|
- `feedback`: max. 1 Satz, sprich den Lerner direkt an. Bei "nachfrage" kurz, was noch fehlt; bei gut/schlecht, warum.
|
||||||
|
- `frage`: NUR bei status "nachfrage" — genau EINE kurze, gezielte Folgefrage. Sonst leer.
|
||||||
|
- `bestanden`: true NUR, wenn du schon vor den {noetig} guten Antworten sicher bist, dass der Lerner den Baustein versteht. Im Zweifel false.
|
||||||
|
|
||||||
HINWEISE DES PRÜFERS ZUR LETZTEN FASSUNG:
|
HINWEISE DES PRÜFERS ZUR LETZTEN FASSUNG:
|
||||||
{kritik_block}
|
{kritik_block}
|
||||||
|
|
||||||
Gib NUR dieses JSON aus (kein weiterer Text):
|
Gib NUR dieses JSON aus (kein weiterer Text):
|
||||||
{{"feedback": "Bewertung in einem Satz", "bewertung": "gut" | "schlecht", "bestanden": false}}
|
{{"status": "gut" | "schlecht" | "nachfrage", "feedback": "ein Satz", "frage": "Folgefrage oder leer", "bestanden": false}}
|
||||||
|
|||||||
Reference in New Issue
Block a user