This commit is contained in:
team3
2026-07-23 16:07:36 +02:00
commit cb68cd671b
88 changed files with 10029 additions and 0 deletions

150
backend/config.py Normal file
View File

@@ -0,0 +1,150 @@
"""Konstanten + .env-Loader. Regel: .env-Datei gewinnt über geerbtes Env
(creator2-Lektion 12: --reload-Master pinnte sonst stale Werte)."""
import os
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def _load_env() -> None:
datei = ROOT / ".env"
if not datei.exists():
return
for zeile in datei.read_text().splitlines():
zeile = zeile.strip()
if not zeile or zeile.startswith("#") or "=" not in zeile:
continue
k, v = zeile.split("=", 1)
v = v.split("#", 1)[0].strip()
if v:
os.environ[k.strip()] = v
_load_env()
FAKE = bool(os.getenv("CREATOR_FAKE"))
LLM_LOG = bool(os.getenv("LLM_LOG")) # 1 = Prompt+Antwort je Call in call_texte
DB_PFAD = Path(os.getenv("CREATOR_DB") or ROOT / "storage" / "creator3.db")
KORPUS_DIR = ROOT / "storage" / "korpus"
SKILLS_DIR = ROOT / "skills"
PIPELINE_YAML = ROOT / "pipeline.yaml"
TOPIC_NAME_RE = re.compile(r"^[a-zA-Z0-9äöüÄÖÜß][a-zA-Z0-9äöüÄÖÜß _-]{0,63}$")
# ── LLM / MiniMax (R3/R9) — alles M3; M2.x kann Thinking nicht abschalten ──
LLM_ENDPOINT = "https://api.minimax.io/anthropic/v1/messages"
ROLLEN = {
# thinking IMMER explizit ("enabled" gibt 400; Default widersprüchlich dokumentiert)
"extraktion": dict(model="MiniMax-M3", thinking="disabled", temperature=0.2),
"judge": dict(model="MiniMax-M3", thinking="disabled", temperature=0.1),
"writer": dict(model="MiniMax-M3", thinking="adaptive", temperature=0.7),
}
LLM_MAX_TOKENS = 32_000
MAX_PARALLEL_LLM = int(os.getenv("MAX_PARALLEL_LLM", "12")) # M3: 200 RPM → 8-16 sicher
TAKT_SEKUNDEN = float(os.getenv("TAKT_SEKUNDEN", "3")) # 1 Call-Start je Takt; 0 = Breiten-Drossel
BUENDEL = {"lang": 1, "mittel": 3, "kurz": 10} # Tasks je Call nach Knoten-Klasse
INFRA_MAX_RETRIES = 3
INFRA_BACKOFF_BASE = 8.0 # → 8/16/32 s, mit Jitter (Retry-After unzuverlässig)
INHALT_MAX_RESTARTS = 2
DROSSEL_MIN_BREITE = 4 # 429/1002/1041/2045: Breite halbieren, 20 Erfolge = +1
DROSSEL_ERFOLGE_JE_PLUS = 20
HEDGE_NACH_S = int(os.getenv("HEDGE_NACH_S", "90")) # Zwilling nach max(90, timeout/2); 0 = aus
RUN_BUDGET_TOKENS = int(os.getenv("RUN_BUDGET_TOKENS", "10000000")) # DEFAULT AN; 0 = aus
PREIS_IN, PREIS_OUT = 0.60, 2.40 # $/1M konservativ (R9), real messen
# Timeouts je Knoten: (basis_s, pro_item_s) — creator2-Lektion 14: nie fix;
# pro_item skaliert mit der Bündel-Größe (n in llm.call)
TIMEOUTS = {
"recherche_plan": (60, 20), "soll_extraktion": (180, 60), "soll_konsens": (240, 2),
"atom_extraktion": (240, 60), "anker_fix": (180, 4), "dedup": (120, 8),
"soll_zuordnung": (120, 3), "luecke": (180, 45), "lernziele": (120, 3),
"bausteine": (120, 3), "ordnung": (180, 3), "writer": (420, 0),
"kapitel_intro": (90, 0), "llm_pruefung": (240, 60), "fix": (300, 0),
"redundanz": (120, 8), "klaerung": (180, 0),
}
TIMEOUT_DEFAULT = (180, 0)
# ── Suche (R2) ──
SUCHE_TIMEOUT_S = 10
SUCHE_RETRIES = 1
CIRCUIT_BREAKER_N = 3
WIKI_MAX_PARALLEL = 3
DDGS_PAUSE_S = 1.5
SUCHE_MAX_TREFFER = 8
# ── Laden (R6) ──
FETCH_CONNECT_S = 10
FETCH_READ_S = 30
DOMAIN_RATE_S = 1.0
SNAPSHOT_MIN_ZEICHEN = 500
# ── Korpus ──
RECHERCHE_LENSES = ("ueberblick", "tutorial", "referenz", "praxis") # generisch!
RECHERCHE_RUNDEN_MAX = int(os.getenv("RECHERCHE_RUNDEN_MAX", "4"))
QUERIES_JE_LENS = 3
SOLL_CHUNK_CHARS = 40_000
SOLL_MIN_BELEGE = 2 # ≥2 unabhängige Quellen je bestätigtem Punkt
SOLL_PUNKTE_MIN = int(os.getenv("SOLL_PUNKTE_MIN", "5"))
SOLL_BAND_FAKTOR = 1.0 # Ziel ≈ √Kandidaten (creator2-Band — Lektion 90)
SOLL_BAND_TOLERANZ = 1.5 # mehr als Ziel×1.5 bestätigt → nochmal falten
QUELLEN_MAX = int(os.getenv("QUELLEN_MAX", "24")) # harter Korpus-Deckel
EXTRAKT_FENSTER = 4_000 # Atome nur ±Fenster um Soll-Belege (belegnah)
# ── Inventar ──
ABSCHNITT_CHARS = 6_000 # 12k riss das 32k-Output-Cap (creator2 empirisch)
READER_JE_ABSCHNITT = 2 # Konsens entsteht über Dedup, nicht Union
CHUNK_HALBIER_TIEFE = 2
ANKER_OVERLAP_MERGE = 0.5
FUZZY_MIN_ZEICHEN = 40
FUZZY_FEHLERQUOTE = 0.08
WORT_MIN = 8
WORT_FEHLERQUOTE = 0.25
ANKER_FIX_BATCH = 20
MERGE_PANEL = 2 # Merge nur einstimmig (Falsch-Merge >> Dublette)
DEDUP_JACCARD = 0.3 # gemessener Paraphrasen-Floor
DEDUP_TITEL_FUZZY = 85 # nur bei ≥2 Inhaltstoken (Subset-100-Falle)
DEDUP_PAARE_PRO_CALL = 10
DEDUP_KANDIDATEN_CAP = 400
VERDICHTUNG_ZIEL = 8 # weiches Ziel Atome je Soll-Punkt (kein Cap!)
LUECKEN_RUNDEN_MAX = 2
WEB_NACHRECHERCHE_RUNDEN = 1
LUECKE_FENSTER = 6_000
BELEG_FENSTER_VOR, BELEG_FENSTER_NACH = 300, 1200
# ── Struktur ──
KAPITEL_SOLL_PUNKTE = 8
BAUSTEIN_MIN_ATOME, BAUSTEIN_MAX_ATOME = 4, 8
ZIELE_CHUNK_ATOME = 30
# ── Guide ──
SECTION_WOERTER_PRO_ATOM = (40, 220) # QA-Band = ×1.25
PRUEF_PANEL = 2
KLAERUNG_PANEL = 3
FIX_MAX_VERSUCHE = 3
REDUNDANZ_JACCARD = 0.5 # Satzpaare zwischen Bausteinen (Paraphrasen!)
REDUNDANZ_SHINGLE = 5 # gemeinsame 5-Wort-Folge = Kandidat
REDUNDANZ_SATZ_MIN_TOKEN = 6
ZIEL_DEDUP_JACCARD = 0.5 # synonyme Lernziele deterministisch mergen
ECHO_JACCARD = 0.7 # 1. Satz nach Marker ≈ Atom-Definition = Echo
SOLL_MIN_BELEGE_WIKI = 1 # kuratierte Quelle (Wikipedia) = 1 Beleg genügt
# ── Engine (R8) ──
POLL_SEKUNDEN = 2.0
MAX_VERSUCHE = 2 # Inhalts-Versuche je Task; Infra zählt separat
STILLSTAND_N = 2 # identischer Befund-Fingerprint n× → PAUSE
GATE_RUNDEN_MAX = {"gate_korpus": 4, "gate_inventar": 8, "gate_struktur": 3, "gate_guide": 12}
# gate_inventar 6: Anker-Fix, 2× Dedup/Lücke, Web-Nachrecherche, Nachzügler-Dedup
# gate_guide 8: Echtlauf konvergierte 45→28→10 — der Stillstand-Fingerprint
# fängt echte Schleifen, das Runden-Limit ist nur die Notbremse dahinter
MAX_PARALLEL_GLOBAL = 24
def timeout_fuer(knoten: str, n: int = 0) -> float:
basis, pro = TIMEOUTS.get(knoten, TIMEOUT_DEFAULT)
return basis + pro * n
def user_agent() -> str:
mail = os.getenv("KONTAKT_MAIL", "")
return f"creator3/0.1 (+mailto:{mail})" if mail else "creator3/0.1"