This commit is contained in:
team3
2026-07-02 03:05:57 +02:00
parent afa8b36105
commit 41c9f29a37
38 changed files with 4671 additions and 2634 deletions

View File

@@ -1,3 +1,4 @@
import os
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
@@ -8,6 +9,26 @@ DB_PATH = STORAGE_DIR / "creator.db"
PROJECTS_DIR = PROJECT_ROOT / "projects"
UNI_DIR = PROJECT_ROOT / "uni"
def _load_env(path: Path) -> None:
"""Mini .env loader (no dependency): KEY=VALUE lines; existing env always wins
(`make dev` already exports .env — this covers bare `uvicorn`/pytest starts)."""
try:
text = path.read_text(encoding="utf-8")
except OSError:
return
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
key, value = key.strip(), value.strip().strip('"').strip("'")
if key and key not in os.environ:
os.environ[key] = value
_load_env(PROJECT_ROOT / ".env")
MAX_CONCURRENT_GENERATIONS = 10
# Readability gate: deterministic checker (small German complexity model,
@@ -21,12 +42,9 @@ READABILITY_MAX = 3.5 # section too hard when the sentence average is a
READABILITY_HARD = 4.0 # an individual sentence is "hard" from here on
READABILITY_HARD_SHARE = 0.30 # … OR when this share of sentences is hard
# Block consolidation: semantic embedding clustering instead of an LLM list merge.
# A small multilingual sentence embedding (mean-pool) builds the candidate clusters
# GLOBALLY (no chunk loss) via cosine + union-find. Title variants of the same concept
# ("Vertex Cover" / "Vertex Cover Definition") merge; the consensus then counts the
# real readers per cluster (≥2 = consensus). If transformers/torch are missing or the model
# won't load → embedding silently off, `_consolidate` falls back to the old panel-judge path.
# Kanban clustering: semantic embeddings drive the online title clustering and the
# candidate pairs of the pair check. If transformers/torch are missing or the model
# won't load → embedding silently off (all pairs go to the judge, clusters stay singletons).
EMBEDDING_AKTIV = True
EMBEDDING_MODELL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" # CPU, multilingual, ~470 MB
# Stronger (larger) CPU alternative if needed: "BAAI/bge-m3".
@@ -66,10 +84,12 @@ GROUP_RECONCILE_FLOOR = 0.75
# chain (random-pair baseline), set BELOW the legitimate heterogeneous minimum so it never kills a real model.
GROUP_MIN_COS_FLOOR = 0.15
# Cap for concurrent CLI agent processes (across all generations).
# Own lane for interactive calls (chat, elements) so they don't hang behind
# running writers in the queue.
MAX_CONCURRENT_AGENTS = 10
# Caps for concurrent CLI agent processes (env-overridable). Two nested limits, both always active:
# a per-topic cap and a global cap across all topics. Defaults 10/10 = previous behavior (global
# dominates). Locally raise the global cap to actually parallelize across topics (per-topic stays 10).
# Own lane for interactive calls (chat, elements) so they don't hang behind running writers.
MAX_CONCURRENT_AGENTS = int(os.getenv("MAX_CONCURRENT_AGENTS", "16")) # global, all topics
MAX_CONCURRENT_AGENTS_PER_TOPIC = int(os.getenv("MAX_CONCURRENT_AGENTS_PER_TOPIC", "12")) # per topic
MAX_CONCURRENT_INTERACTIVE = 8
# Grace window of the consensus races (blocks, guide, OnePager): after the first
@@ -123,6 +143,10 @@ TIMEOUTS = {
"question_pattern_check": (300, 10), # critic cleans up the pattern table per block
"writer": (600, 120), # per section in the chunk
"lese_check": (300, 10), # per section in the package
# guide board (per card = one block)
"lernziele": (300, 5), # backward-design objectives per block
"fakten_gate": (600, 5), # CoVe claim check per block
"coverage": (300, 5), # objective↔section mapping per block
}
# Purpose per format — flows into the outline judge (what the guide should achieve).
@@ -169,3 +193,27 @@ PROVIDERS = {
"check_url": "http://localhost:11434/api/tags", # Ollama reachable?
},
}
# Role routing ACROSS provider stacks: generation (quick/guide) and judging (judge)
# may run on different providers within ONE run — judge model ≠ generator model
# (research-backed: cross-model judging avoids self-preference bias).
# Value: "" = provider of the run; "minimax" = that stack's role model;
# "provider:model" = explicit model override.
ROLE_ROUTING = {
"quick": os.getenv("ROLE_QUICK", "minimax"),
"judge": os.getenv("ROLE_JUDGE", "claude"),
"guide": os.getenv("ROLE_GUIDE", "minimax"),
"fast": os.getenv("ROLE_FAST", ""),
}
def resolve_role(run_provider: str, role: str) -> tuple[str, str]:
"""→ (provider, model) for one agent call. Pure routing, no availability check —
the caller (agents.run_agent) falls back to run_provider if the target is unavailable."""
target = ROLE_ROUTING.get(role, "") or run_provider
provider, _, model = target.partition(":")
if provider not in PROVIDERS:
provider, model = run_provider, ""
if not model:
model = PROVIDERS.get(provider, {}).get(role, "")
return provider, model