246 lines
9.6 KiB
Python
246 lines
9.6 KiB
Python
"""Deterministische Fake-Antworten auf run_agent-Ebene (CREATOR_FAKE_AGENTS=1):
|
||
kompletter Generierungspfad in Sekunden, ohne LLM. Die Fakes erkennen das Template
|
||
am Marker `<!-- template:Name -->` und LESEN den Prompt (Zitate müssen wörtlich im
|
||
gelieferten Text stehen, ids müssen existieren) — sonst testet der E2E-Pfad nichts."""
|
||
|
||
import json
|
||
import re
|
||
|
||
from agents import AgentErgebnis
|
||
|
||
# Die Fake-Welt: drei Atome, zwei Soll-Punkte, eine Voraussetzungskette.
|
||
SATZ_DFA = "Ein deterministischer endlicher Automat (DFA) besteht aus Zuständen und Übergängen."
|
||
SATZ_POTENZ = "Die Potenzmengenkonstruktion wandelt einen NFA in einen DFA um."
|
||
SATZ_PUMPING = "Das Pumping-Lemma beweist, dass eine Sprache nicht regulär ist."
|
||
FAKE_TEXT = f"{SATZ_DFA}\n\n{SATZ_POTENZ}\n\n{SATZ_PUMPING}\n"
|
||
|
||
ATOME = [
|
||
{"titel": "Deterministischer endlicher Automat", "typ": "begriff",
|
||
"definition": SATZ_DFA, "zitat": SATZ_DFA, "level": "E", "braucht": []},
|
||
{"titel": "Potenzmengenkonstruktion", "typ": "verfahren",
|
||
"definition": SATZ_POTENZ, "zitat": SATZ_POTENZ, "level": "M",
|
||
"braucht": ["Deterministischer endlicher Automat"]},
|
||
{"titel": "Pumping-Lemma", "typ": "aussage",
|
||
"definition": SATZ_PUMPING, "zitat": SATZ_PUMPING, "level": "S",
|
||
"braucht": ["Deterministischer endlicher Automat"]},
|
||
]
|
||
PUNKTE = [("Endliche Automaten", (SATZ_DFA, SATZ_POTENZ)),
|
||
("Nachweis von Nicht-Regularität", (SATZ_PUMPING,))]
|
||
|
||
_TEMPLATE = re.compile(r"<!--\s*template:([\w-]+)\s*-->")
|
||
|
||
|
||
def _text_nach(prompt: str, marke: str) -> str:
|
||
pos = prompt.find(marke)
|
||
return prompt[pos + len(marke):] if pos >= 0 else ""
|
||
|
||
|
||
def _ids(text: str) -> list[int]:
|
||
return [int(m) for m in re.findall(r"^(\d+):", text, re.MULTILINE)]
|
||
|
||
|
||
def _ok(daten) -> AgentErgebnis:
|
||
return AgentErgebnis(0, json.dumps(daten, ensure_ascii=False), "",
|
||
{"input": 100, "output": 50})
|
||
|
||
|
||
async def antwort(key: str, prompt: str, capabilities: str) -> AgentErgebnis:
|
||
m = _TEMPLATE.search(prompt)
|
||
name = m.group(1) if m else ""
|
||
fn = _HANDLER.get(name)
|
||
if fn is None:
|
||
return AgentErgebnis(1, "", f"fake: kein Handler für Template {name!r}")
|
||
return _ok(fn(prompt))
|
||
|
||
|
||
def _recherche(prompt: str):
|
||
lens = re.search(r"\(Lens\): \*\*(\w+)\*\*", prompt)
|
||
lens = lens.group(1) if lens else "x"
|
||
inhalt = FAKE_TEXT + f"\nHinweis der Lens {lens}: " + "Übung macht den Meister. " * 40
|
||
return [{"titel": f"Fake-Quelle {lens}", "url": f"https://fake/{lens}", "inhalt": inhalt}]
|
||
|
||
|
||
def _soll(prompt: str):
|
||
text = _text_nach(prompt, "QUELLTEXT:")
|
||
return [{"punkt": p, "zitat": s[0]} for p, s in PUNKTE if s[0] in text]
|
||
|
||
|
||
def _soll_konsens(prompt: str):
|
||
zeilen = re.findall(r"^(\d+): (.+)$", _text_nach(prompt, "KANDIDATEN:"), re.MULTILINE)
|
||
gruppen: dict[str, list[int]] = {}
|
||
for i, punkt in zeilen:
|
||
gruppen.setdefault(punkt.strip(), []).append(int(i))
|
||
return [{"punkt": p, "kandidaten": ids} for p, ids in gruppen.items()]
|
||
|
||
|
||
def _soll_beleg(prompt: str):
|
||
text = _text_nach(prompt, "QUELLTEXT:")
|
||
for _, saetze in PUNKTE:
|
||
if saetze[0] in text and saetze[0] in prompt:
|
||
return {"zitat": saetze[0]}
|
||
return {"zitat": ""}
|
||
|
||
|
||
def _extraktion(prompt: str):
|
||
text = _text_nach(prompt, "QUELLTEXT:")
|
||
return [a for a in ATOME if a["zitat"] in text]
|
||
|
||
|
||
def _merge(prompt: str):
|
||
paare = re.findall(r"PAAR (\d+):\nA\(id \d+\): ([^—]+)—.*?\nB\(id \d+\): ([^—]+)—",
|
||
prompt, re.DOTALL)
|
||
return [{"paar": int(n), "gleich": a.strip() == b.strip()} for n, a, b in paare]
|
||
|
||
|
||
def _zuordnung_map(prompt: str) -> dict[str, int]:
|
||
soll = re.findall(r"^(\d+): (.+)$", _text_nach(prompt, "SOLL-PUNKTE"), re.MULTILINE)
|
||
out = {}
|
||
for i, punkt in soll:
|
||
out[punkt.strip()] = int(i)
|
||
return out
|
||
|
||
|
||
def _soll_zuordnung(prompt: str):
|
||
punkte = _zuordnung_map(prompt)
|
||
atome = re.findall(r"^(\d+): (.+?) —", _text_nach(prompt, "ATOME"), re.MULTILINE)
|
||
out = []
|
||
for i, titel in atome:
|
||
t = titel.lower()
|
||
punkt = ("Nachweis von Nicht-Regularität" if "pumping" in t else "Endliche Automaten")
|
||
out.append({"atom": int(i), "soll": punkte.get(punkt, next(iter(punkte.values()), 0))})
|
||
return out
|
||
|
||
|
||
def _soll_stich(prompt: str):
|
||
punkte = _zuordnung_map(prompt)
|
||
return {"soll": next(iter(punkte.values()), "fremd")}
|
||
|
||
|
||
def _anker_fix(prompt: str):
|
||
text = _text_nach(prompt, "QUELLTEXT:")
|
||
for a in ATOME:
|
||
if a["titel"] in prompt and a["zitat"] in text:
|
||
return {"zitat": a["zitat"]}
|
||
return {"zitat": ""}
|
||
|
||
|
||
def _anker_fix_batch(prompt: str):
|
||
text = _text_nach(prompt, "QUELLTEXT:")
|
||
zeilen = re.findall(r"^(\d+): (.+?) —", _text_nach(prompt, "ATOME:"), re.MULTILINE)
|
||
out = []
|
||
for i, titel in zeilen:
|
||
for a in ATOME:
|
||
if a["titel"] == titel.strip() and a["zitat"] in text:
|
||
out.append({"atom": int(i), "zitat": a["zitat"]})
|
||
return out
|
||
|
||
|
||
def _luecke(prompt: str):
|
||
text = _text_nach(prompt, "QUELLTEXT:")
|
||
return [{k: a[k] for k in ("titel", "typ", "definition", "zitat")}
|
||
for a in ATOME if a["zitat"] in text]
|
||
|
||
|
||
def _artefakt_generate(prompt: str):
|
||
ids = [int(i) for i in re.findall(r"ATOM (\d+):", prompt)]
|
||
out = []
|
||
for i in ids:
|
||
out += [{"atom": i, "typ": "flashcard", "frage": f"Was besagt Atom {i}?",
|
||
"antwort": "Siehe Beleg — deterministische Fake-Antwort."},
|
||
{"atom": i, "typ": "flashcard", "frage": f"Wozu dient Atom {i}?",
|
||
"antwort": "Zum Lernen — deterministische Fake-Antwort."},
|
||
{"atom": i, "typ": "beispiel", "text": f"Beispiel zu Atom {i}: Schritt 1, Schritt 2."}]
|
||
return out
|
||
|
||
|
||
def _artefakt_verify(prompt: str):
|
||
ids = [int(i) for i in re.findall(r"ARTEFAKT (\d+)", prompt)]
|
||
return [{"artefakt": i, "ok": True, "mangel": ""} for i in ids]
|
||
|
||
|
||
def _artefakt_fix(prompt: str):
|
||
return {"frage": "Fix-Frage?", "antwort": "Fix-Antwort.", "text": "Fix-Beispiel."}
|
||
|
||
|
||
def _lernziele(prompt: str):
|
||
punkt = re.search(r"Soll-Punkt: „(.+?)“", prompt) or re.search(r'Soll-Punkt: „(.+?)"', prompt)
|
||
name = punkt.group(1) if punkt else "das Thema"
|
||
ids = _ids(_text_nach(prompt, "ATOME:"))
|
||
return [{"ziel": f"Kann {name} anwenden und erklären", "titel": name, "atome": ids}]
|
||
|
||
|
||
def _kapitel_intro(prompt: str):
|
||
punkt = re.search(r"Kapitel „(.+?)“", prompt) or re.search(r'Kapitel „(.+?)"', prompt)
|
||
return {"intro": f"Dieses Kapitel behandelt {punkt.group(1) if punkt else 'das Thema'}"
|
||
" in aufeinander aufbauenden Abschnitten."}
|
||
|
||
|
||
def _kapitel_schnitt(prompt: str):
|
||
ids = [int(i) for i in re.findall(r"^(\d+): ", _text_nach(prompt, "BAUSTEINE:"),
|
||
re.MULTILINE)]
|
||
grenzen = [ids[min(i + 1, len(ids) - 1)] for i in range(0, len(ids), 2)]
|
||
return [{"titel": f"Themenblock {n + 1}", "bis": g}
|
||
for n, g in enumerate(dict.fromkeys(grenzen))]
|
||
|
||
|
||
def _zyklus(prompt: str):
|
||
m = re.search(r"^(\d+)→(\d+):", _text_nach(prompt, "ZYKLUS:"), re.MULTILINE)
|
||
return {"von": int(m.group(1)), "zu": int(m.group(2))} if m else {}
|
||
|
||
|
||
def _writer(prompt: str):
|
||
ziel = re.search(r"Lernziel: (.+)", prompt)
|
||
marker = re.findall(r"MARKER \(exakt so übernehmen\): (<!-- atom: \d+ \| (.+?) \| [EMS] -->)",
|
||
prompt)
|
||
defs = re.findall(r"Definition: (.+)", prompt)
|
||
fragen = re.findall(r"^- (.+\?)$", prompt, re.MULTILINE)[:2]
|
||
lo = int(re.search(r"hat (\d+)–", prompt).group(1)) if re.search(r"hat (\d+)–", prompt) else 40
|
||
absaetze = [f"Ziel dieser Einheit: {ziel.group(1) if ziel else 'Lernen'}. Formal: $n \\ge 0$."]
|
||
fuellung = "Daran anknüpfend gehen wir das Schritt für Schritt am Beispiel durch. " * 5
|
||
for (mk, titel), d in zip(marker, defs):
|
||
absaetze.append(f"#### {titel} verstehen\n{mk}\n{d} {fuellung}")
|
||
absaetze.append("**Kernpunkte:**\n" + "\n".join(f"- {t} sitzt" for _, t in marker))
|
||
if fragen:
|
||
absaetze.append("**Prüfe dich:**\n" + "\n".join(f"- {f}" for f in fragen))
|
||
lang = "\n\n".join(absaetze)
|
||
while len(lang.split()) < lo:
|
||
lang += "\nNoch ein Übungssatz zur Vertiefung des Gelernten."
|
||
return {"kompakt": f"- Kurzfassung in {len(marker)} Schritten\n- alles Wesentliche sitzt",
|
||
"lang": lang}
|
||
|
||
|
||
def _pruefer(prompt: str):
|
||
return {"befunde": []}
|
||
|
||
|
||
def _fix(prompt: str):
|
||
kompakt = _text_nach(prompt, "SECTION (kompakt):").split("SECTION (lang):")[0].strip()
|
||
lang = _text_nach(prompt, "SECTION (lang):").split("Regeln:")[0].strip()
|
||
return {"kompakt": kompakt, "lang": lang}
|
||
|
||
|
||
def _qa_falsch(prompt: str):
|
||
return []
|
||
|
||
|
||
def _qa_falsch_check(prompt: str):
|
||
nummern = [int(i) for i in re.findall(r"^(\d+):", _text_nach(prompt, "CLAIMS:"), re.MULTILINE)]
|
||
return [{"claim": i, "falsch": False} for i in nummern]
|
||
|
||
|
||
_HANDLER = {
|
||
"Korpus-Recherche": _recherche, "Korpus-Soll": _soll,
|
||
"Korpus-Soll-Konsens": _soll_konsens, "Korpus-Soll-Beleg": _soll_beleg,
|
||
"Atom-Extraktion": _extraktion, "Atom-Extraktion-Aufgaben": _extraktion,
|
||
"Atom-Merge": _merge,
|
||
"Atom-Soll-Zuordnung": _soll_zuordnung, "Atom-Soll-Stich": _soll_stich,
|
||
"Atom-Anker-Fix": _anker_fix, "Atom-Anker-Fix-Batch": _anker_fix_batch,
|
||
"Atom-Luecke": _luecke, "Soll-Abgedeckt": lambda p: {"abgedeckt": True},
|
||
"Artefakt-Generate": _artefakt_generate, "Artefakt-Verify": _artefakt_verify,
|
||
"Artefakt-Fix": _artefakt_fix, "Lernziele": _lernziele, "Kapitel-Intro": _kapitel_intro,
|
||
"Kapitel-Schnitt": _kapitel_schnitt,
|
||
"Kanten-Zyklus": _zyklus,
|
||
"Guide-Writer": _writer, "Guide-Pruefer": _pruefer, "Guide-Fix": _fix,
|
||
"QA-Guide-Falsch": _qa_falsch, "QA-Guide-Falsch-Check": _qa_falsch_check,
|
||
}
|