Files
creator3/backend/config.py
2026-07-23 16:07:36 +02:00

151 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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"