This commit is contained in:
team3
2026-07-07 02:25:29 +02:00
parent 41f96330fd
commit f2bc6bcedb
5 changed files with 92 additions and 51 deletions

View File

@@ -269,7 +269,13 @@ def _rating_text(rating: dict) -> str:
# Deterministic guard against double questions — the AI critic misses "…, and which…".
_QUESTION_WORD = r"(was|welche[rsnm]?|wie|wieso|warum|wofür|wozu|wann|wo|wer|wem|wen|nenne)"
_DOUBLE_RE = re.compile(r"[,;]?\s+(und|sowie|außerdem|bzw\.?)\s+" + _QUESTION_WORD + r"\b", re.IGNORECASE)
# Optional preposition between the conjunction and the question word — the second question
# hides behind it: "und IN welcher Datei", "und UNTER welcher Bedingung".
_PREP = r"(?:in|unter|auf|für|mit|bei|zu[rm]?|von|vom|über|durch|aus|nach|an|am|um|gegen|ohne|wobei)\s+"
_DOUBLE_RE = re.compile(
r"[,;]?\s+(?:und|sowie|außerdem|bzw\.?)\s+(?:" + _PREP + r")?" + _QUESTION_WORD + r"\b",
re.IGNORECASE,
)
def _double_question_flaw(question: str) -> str | None:
@@ -368,9 +374,9 @@ def _avoid_block(avoid: list[str] | None) -> str:
# the model takes the easy path (mere recall) — the cues lift higher tiers to apply/analyze/transfer.
TIER_ROLE = {
"beginner": "The learner is a BEGINNER. Cognitive: REMEMBER/UNDERSTAND. Ask about the basic understanding — the core concept, simple and direct.",
"advanced": "The learner is ADVANCED. Cognitive: APPLY. Pose a small concrete situation and have the concept applied to it — don't just ask for the definition.",
"expert": "The learner is an EXPERT. Cognitive: ANALYZE. Have them distinguish/compare, classify a special case or uncover a typical pitfall (hurdle) — don't quiz textbook knowledge.",
"master": "The learner is at MASTER level. Cognitive: EVALUATE/TRANSFER. Have the concept transferred to a NEW problem, justify a decision or weigh a trade-off.",
"advanced": "The learner is ADVANCED. Cognitive: APPLY. Have the concept applied to ONE minimal case — name the case in a half-sentence (no story, no roles, no backstory), then ask ONE single thing.",
"expert": "The learner is an EXPERT. Cognitive: ANALYZE. Ask for ONE distinction, comparison, or typical pitfall — a single question, no scenario prose.",
"master": "The learner is at MASTER level. Cognitive: EVALUATE/TRANSFER. Have them justify ONE decision or weigh ONE trade-off — one question, no build-up, no second sub-question.",
}
@@ -409,18 +415,27 @@ async def exam_question_variant(
pattern: str, tier: str = "beginner", provider: str = DEFAULT_PROVIDER,
) -> str | None:
"""Action 'question' with a pattern: from a predefined pattern, phrase a concrete question in
the addressee role of the tier. No critic (the pattern is build-checked).
The style guard stays as a cheap protection against double questions · None on error."""
the addressee role of the tier. No AI critic (the pattern is build-checked), but the
deterministic double-question guard runs and forces a regenerate on a flaw · None on error."""
try:
section_block, compact_block = _section_blocks(section, compact)
data = await _gen_call(
"Block-Question-Variante", "guide", _question_schema, provider, lane="batch",
topic=topic, block=block, section_block=section_block,
compact_block=compact_block, pattern=pattern, tier_block=_tier_block(tier),
)
if data is None:
return None
return data["question"]
kritik_block = "(none)"
question = None
for _ in range(CRITIC_MAX_ROUNDS):
data = await _gen_call(
"Block-Question-Variante", "guide", _question_schema, provider, lane="batch",
topic=topic, block=block, section_block=section_block,
compact_block=compact_block, pattern=pattern, tier_block=_tier_block(tier),
kritik_block=kritik_block,
)
if data is None:
return None
question = data["question"]
flaw = _double_question_flaw(question)
if not flaw:
return question
kritik_block = _critique_block(question, [flaw])
return question # best-effort after the last round
except Exception:
log.warning("[%s] Question variant failed (%s)", topic, block, exc_info=True)
return None