This commit is contained in:
team3
2026-07-24 11:23:18 +02:00
parent cb68cd671b
commit 1b054497ed
49 changed files with 935 additions and 408 deletions

View File

@@ -30,16 +30,22 @@ 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}$")
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"
# ── LLM (R3/R9) — Anbieter/Modell per env überschreibbar, nicht im Code ──
LLM_ENDPOINT = os.getenv("LLM_ENDPOINT",
"https://api.minimax.io/anthropic/v1/messages")
LLM_MODEL = os.getenv("LLM_MODEL", "MiniMax-M3")
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),
"extraktion": dict(model=LLM_MODEL, thinking="disabled", temperature=0.2),
"judge": dict(model=LLM_MODEL, thinking="disabled", temperature=0.1),
"writer": dict(model=LLM_MODEL, thinking="adaptive", temperature=0.7),
}
# Anbieter-spezifische Infra-Marker (MiniMax-Statuscodes) getrennt von generischen
INFRA_MARKER = ("429", "rate limit", "rate_limit", "timeout", "timed out",
"connection", "network", "overloaded", "unavailable",
'"status_code":1002', '"status_code":1041', '"status_code":2045')
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
@@ -53,16 +59,8 @@ HEDGE_NACH_S = int(os.getenv("HEDGE_NACH_S", "90")) # Zwilling nach max(90, tim
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),
}
# Timeouts stehen als Knoten-Property in pipeline.yaml (graph.timeout_fuer);
# hier nur der Default für Knoten ohne Angabe
TIMEOUT_DEFAULT = (180, 0)
# ── Suche (R2) ──
@@ -78,18 +76,25 @@ FETCH_CONNECT_S = 10
FETCH_READ_S = 30
DOMAIN_RATE_S = 1.0
SNAPSHOT_MIN_ZEICHEN = 500
# Privates Lese-Tool, ~20 Seiten/Lauf mit Domain-Bremse — kein robots.txt-Check
# mehr (Lektion 101); Browser-UA, sonst sperren Bot-Filter trotzdem
BROWSER_UA = ("Mozilla/5.0 (X11; Linux x86_64; rv:128.0) "
"Gecko/20100101 Firefox/128.0")
URTEIL_AUSZUG_ZEICHEN = 3_000 # Inhalts-Urteil je geladener Quelle
# ── 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)
# Harte Vollständigkeits-Stops (Lektion 103): lieber PAUSE als Mini-Guide
SEED_PFLICHT = True # keine Startquelle nützlich geladen → PAUSE
QUELLEN_MIN = 5 # weniger geladene nützliche Quellen → PAUSE
BEIFANG_MAX_QUOTE = 0.5 # >50 % Atome als Beifang verworfen → PAUSE
# ── Inventar ──
ABSCHNITT_CHARS = 6_000 # 12k riss das 32k-Output-Cap (creator2 empirisch)
@@ -127,22 +132,34 @@ 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
FAKTEN_SIGNATUR_MIN = 2 # geteilte Zahlen+Namen je Satzpaar = Kandidat
# ── 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
# Gate-Runden-Caps stehen als runden_max am Gate-Knoten in pipeline.yaml
MAX_PARALLEL_GLOBAL = 24
def timeout_fuer(knoten: str, n: int = 0) -> float:
basis, pro = TIMEOUTS.get(knoten, TIMEOUT_DEFAULT)
return basis + pro * n
# ── Sprache: EIN Parameter, alles referenziert ihn (Lektion 106) ──
SPRACHE = os.getenv("SPRACHE", "de")
SPRACHEN_SUCHE = tuple(os.getenv("SPRACHEN_SUCHE", "de,en").split(","))
STEMMER_NAME = {"de": "german", "en": "english", "fr": "french",
"es": "spanish", "it": "italian"} # sonst: kein Stemming
NEGATIONEN = {"de": {"nicht", "kein", "keine", "keinen", "keiner", "ohne",
"nie", "niemals"},
"en": {"not", "no", "none", "never", "without"}}
NEGATIONS_PRAEFIX = {"de": "nicht", "en": "non"}
SUCHE_LOCALE = {"de": {"country": "DE", "search_lang": "de", "region": "de-de"},
"en": {"country": "US", "search_lang": "en", "region": "us-en"}}
# Ausgabe-Literale, die im Guide landen — eine Stelle, pro Sprache tauschbar
TEXTE = {
"sammel_titel": "Grundlagen",
"sammel_punkt": "Weitere Grundlagen zum Thema",
"sammel_ziel": "Der Lernende kann Grundlagen zu '{punkt}' erklären.",
"teil": "{titel} (Teil {n})",
"abschnitt_verweis": "- {titel} (im Abschnitt '{abschnitt}')",
}
def user_agent() -> str: