init
This commit is contained in:
245
backend/fake_agents.py
Normal file
245
backend/fake_agents.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""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,
|
||||
}
|
||||
Reference in New Issue
Block a user