This commit is contained in:
Team3
2026-07-22 16:12:23 +02:00
commit f07ef9653d
851 changed files with 501480 additions and 0 deletions

155
backend/fake_agents.py Normal file
View File

@@ -0,0 +1,155 @@
"""Fake-Welt: deterministische Agenten-Antworten ohne LLM (PLANER_FAKE_AGENTS=1).
Creator-Muster: eine Welt beantwortet Calls anhand von agent_key-Mustern und kann
Störungen einspielen ({muster, modus, mal}) — damit testet die Fake-E2E den kompletten
Pfad inkl. Retry/Dead-Letter/Pause in Sekunden.
"""
import re
# Modul-globale Welt für den ENV-Weg; Tests setzen ihre eigene via set_welt().
_WELT = None
def set_welt(welt) -> None:
global _WELT
_WELT = welt
class Welt:
def __init__(self):
self.calls: list[str] = [] # Auditspur aller agent_keys
self.stoerungen: list[dict] = [] # {muster, modus: fehler|garbage|infra, mal, [antwort]}
self.antworten: list[tuple[str, object]] = [] # (regex auf agent_key, fn(prompt) -> str)
def antwort_auf(self, muster: str, fn) -> None:
self.antworten.append((muster, fn))
def _stoerung(self, agent_key: str) -> tuple[int, str, str] | None:
for s in self.stoerungen:
if s.get("mal", 0) > 0 and re.search(s["muster"], agent_key):
s["mal"] -= 1
modus = s.get("modus", "fehler")
if modus == "fehler":
return 1, "", "fake-stoerung"
if modus == "garbage":
return 0, "### kaputtes {fragment", ""
if modus == "infra":
from agents import AgentInfraError
raise AgentInfraError("fake-infra-stoerung")
if modus == "antwort":
return 0, s.get("antwort", ""), ""
return None
async def respond(self, agent_key: str, prompt: str, capabilities: str) -> tuple[int, str, str]:
self.calls.append(agent_key)
gestoert = self._stoerung(agent_key)
if gestoert is not None:
return gestoert
for muster, fn in self.antworten:
if re.search(muster, agent_key):
return 0, fn(prompt), ""
return 1, "", f"fake: keine Antwort für {agent_key}"
async def respond(agent_key: str, prompt: str, capabilities: str) -> tuple[int, str, str]:
global _WELT
if _WELT is None:
_WELT = Welt()
standard_antworten(_WELT)
return await _WELT.respond(agent_key, prompt, capabilities)
# --- Standard-Antworten: mechanisch erzeugte Ausgaben, die die ECHTEN Gates bestehen ---
def _quelle_aus(prompt: str) -> tuple[str, str]:
"""(datei, quelltext) aus einem Scan-Prompt (Template-Struktur)."""
m = re.search(r"Ausschnitt aus `(.+?)`\n\n```python\n(.*?)\n?```", prompt, re.S)
return (m.group(1), m.group(2)) if m else ("", "")
def _symbol_liste(prompt: str, kopf: str) -> list[str]:
"""`- symbol`-Liste direkt nach der Zeile mit `kopf` (Leerzeilen dazwischen erlaubt)."""
idx = prompt.find(kopf)
if idx < 0:
return []
out: list[str] = []
for zeile in prompt[idx:].splitlines()[1:]:
z = zeile.strip()
if z.startswith("- "):
if "(keine)" not in z:
out.append(z[2:].strip())
elif z:
break # erste andere Zeile beendet die Liste
return out
def _einheit_fuer(datei: str, symbol: str, quelle: str) -> str:
"""Eine gültige Einheit: Beleg = die wörtliche Definitionszeile aus der Quelle."""
beleg = next((z.strip() for z in quelle.splitlines()
if re.match(rf"\s*(?:async\s+)?(?:def|class)\s+{re.escape(symbol)}\b", z)
or re.match(rf"{re.escape(symbol)}\s*[:=]", z)), "")
beleg = beleg[:60].replace('"', "'")
return (f"## {datei}::{symbol}\n"
f"beschreibung: Fake-Verantwortung von {symbol}.\n"
f"input: Fake-Input\noutput: Fake-Output\n"
f"entscheidungen:\n- Fake-Entscheidung zu {symbol}.\n beleg: \"{beleg}\"\n"
f"kanten:\n")
def _fake_schneiden(prompt: str, lasse_aus: set[str]) -> str:
datei, quelle = _quelle_aus(prompt)
symbole = [s for s in _symbol_liste(prompt, "MÜSSEN alle abgedeckt sein") if s not in lasse_aus]
if not symbole: # Datei ohne def/class: erste Konstante als Einheit
m = re.search(r"^(\w+)\s*[:=]", quelle, re.M)
symbole = [m.group(1)] if m else []
return "\n".join(_einheit_fuer(datei, s, quelle) for s in symbole)
def _fake_nachfass(prompt: str) -> str:
datei, quelle = _quelle_aus(prompt)
fehlend = _symbol_liste(prompt, "zugeordnet sind:")
return "\n".join(_einheit_fuer(datei, s, quelle) for s in fehlend)
def _ids_aus_uebersicht(prompt: str) -> list[str]:
return re.findall(r"^- (\S+::\S+):", prompt, re.M)
def _projekt_aus(prompt: str) -> str:
m = re.search(r"Einheiten von `(.+?)`", prompt)
return m.group(1) if m else "projekt"
def _fake_features(prompt: str) -> str:
ids = _ids_aus_uebersicht(prompt)
p = _projekt_aus(prompt)
return (f"# Kern: {p}\nFake-Kern in zwei Sätzen. Mehr Fake.\n\n"
f"tragende-einheiten: {ids[0]}\n\n"
f"# Bereich: fake-bereich\nbeschreibung: Ein Fake-Bereich.\n\n"
f"## Feature: Fake-Feature [kern]\nbeschreibung: Kann alles Fake.\n\n"
f"### Kann fake arbeiten\neinheiten: {', '.join(ids)}\n")
def _fake_architektur(prompt: str) -> str:
ids = _ids_aus_uebersicht(prompt)
p = _projekt_aus(prompt)
return (f"# Architektur: {p}\n\n## Komponente: fake-komponente\n"
f"beschreibung: Trägt alles.\neinheiten: {', '.join(ids)}\nbeziehungen:\n")
def _fake_flows(prompt: str) -> str:
ids = _ids_aus_uebersicht(prompt)
p = _projekt_aus(prompt)
schritte = "\n".join(f"{i+1}. Fake-Schritt {i+1}{eid}" for i, eid in enumerate(ids[:3]))
return f"# Flows: {p}\n\n## Flow: fake-ablauf\n{schritte}\n"
def standard_antworten(welt: Welt, lasse_aus: set[str] | None = None) -> None:
"""Registriert gültige Fake-Antworten für alle Scan-Rollen. `lasse_aus`: Symbole,
die der Erst-Scan absichtlich auslässt (übt den Nachfass-Pfad)."""
aus = lasse_aus or set()
welt.antwort_auf(r"-schneiden-", lambda pr: _fake_schneiden(pr, aus))
welt.antwort_auf(r"-nachfass-", _fake_nachfass)
welt.antwort_auf(r"-sichten-sicht-features", _fake_features)
welt.antwort_auf(r"-sichten-sicht-architektur", _fake_architektur)
welt.antwort_auf(r"-sichten-sicht-flows", _fake_flows)