Initial: Mesh-Skills, Configs, Scripts
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
daily_model_check.py
|
||||
|
||||
Ebene 1 des Skills: läuft 1x täglich (Cron), baut die grobe
|
||||
Modell-Prioritätsliste pro Agent und schreibt sie nach
|
||||
state/model_priority.json.
|
||||
|
||||
Nutzt aktuell Mock-Daten (mock_data.py). Für den Wechsel auf die
|
||||
echte OpenRouter-API muss nur fetch_model_list() in mock_data.py
|
||||
ersetzt werden - dieses Script bleibt unverändert.
|
||||
|
||||
Aufruf:
|
||||
python3 daily_model_check.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from mock_data import fetch_model_list, fetch_timestamp # noqa: E402
|
||||
|
||||
CONFIG_PATH = Path(__file__).parent.parent / "config" / "agents.yaml"
|
||||
STATE_DIR = Path(__file__).parent.parent / "state"
|
||||
OUTPUT_PATH = STATE_DIR / "model_priority.json"
|
||||
|
||||
|
||||
def load_config():
|
||||
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
def index_models_by_id(models):
|
||||
return {m["id"]: m for m in models}
|
||||
|
||||
|
||||
def build_agent_priority(agent_name, agent_cfg, models_by_id, min_context, max_escalation_factor):
|
||||
"""Baut die Eskalationsleiter für einen Agent, gefiltert nach
|
||||
Mindest-Kontext und verfügbaren/bekannten Modellen.
|
||||
"""
|
||||
ladder = []
|
||||
warnings = []
|
||||
|
||||
for step_idx, step in enumerate(agent_cfg["escalation_ladder"]):
|
||||
model_id = step["model"]
|
||||
cost_factor = step["cost_factor"]
|
||||
|
||||
# Free-Tier-Router-Strings wie "openrouter/free" stehen nicht in
|
||||
# der Modell-Liste selbst - die werden unverändert übernommen.
|
||||
if model_id not in models_by_id:
|
||||
if model_id.startswith("openrouter/"):
|
||||
ladder.append({"model": model_id, "cost_factor": cost_factor, "note": "auto-router, nicht einzeln geprüft"})
|
||||
continue
|
||||
warnings.append(f"{agent_name}: Modell '{model_id}' nicht in aktueller Modell-Liste gefunden - übersprungen")
|
||||
continue
|
||||
|
||||
model = models_by_id[model_id]
|
||||
|
||||
if not model.get("available", True):
|
||||
warnings.append(f"{agent_name}: Modell '{model_id}' aktuell nicht verfügbar - übersprungen")
|
||||
continue
|
||||
|
||||
if model["context_length"] < min_context:
|
||||
warnings.append(
|
||||
f"{agent_name}: Modell '{model_id}' hat nur {model['context_length']} Kontext, "
|
||||
f"unter Minimum von {min_context} - übersprungen"
|
||||
)
|
||||
continue
|
||||
|
||||
# Verhältnismäßigkeits-Check: Stufen jenseits von Stufe 0 dürfen
|
||||
# den max_auto_escalation_factor nicht überschreiten, sonst braucht's
|
||||
# manual_override.
|
||||
if step_idx > 0 and cost_factor > max_escalation_factor and not agent_cfg.get("manual_override_allowed", False):
|
||||
warnings.append(
|
||||
f"{agent_name}: Eskalationsstufe '{model_id}' hat Faktor {cost_factor}x, "
|
||||
f"über dem erlaubten Automatik-Limit von {max_escalation_factor}x - "
|
||||
f"nur per manual_override nutzbar, aus Automatik-Leiter entfernt"
|
||||
)
|
||||
continue
|
||||
|
||||
ladder.append({
|
||||
"model": model_id,
|
||||
"cost_factor": cost_factor,
|
||||
"context_length": model["context_length"],
|
||||
"price_per_1k_tokens_eur": model["price_per_1k_tokens_eur"],
|
||||
"family": model["family"],
|
||||
})
|
||||
|
||||
return ladder, warnings
|
||||
|
||||
|
||||
def check_diversity(priority_by_agent, min_distinct_families):
|
||||
families_in_use = set()
|
||||
for agent_name, ladder in priority_by_agent.items():
|
||||
for step in ladder:
|
||||
if "family" in step:
|
||||
families_in_use.add(step["family"])
|
||||
|
||||
diverse_enough = len(families_in_use) >= min_distinct_families
|
||||
return diverse_enough, sorted(families_in_use)
|
||||
|
||||
|
||||
def main():
|
||||
config = load_config()
|
||||
global_cfg = config["global"]
|
||||
min_context = global_cfg["min_context_tokens"]
|
||||
max_escalation_factor = global_cfg["max_auto_escalation_factor"]
|
||||
min_distinct_families = global_cfg["min_distinct_model_families"]
|
||||
|
||||
models = fetch_model_list()
|
||||
models_by_id = index_models_by_id(models)
|
||||
|
||||
priority_by_agent = {}
|
||||
all_warnings = []
|
||||
|
||||
for agent_name, agent_cfg in config["agents"].items():
|
||||
ladder, warnings = build_agent_priority(
|
||||
agent_name, agent_cfg, models_by_id, min_context, max_escalation_factor
|
||||
)
|
||||
priority_by_agent[agent_name] = ladder
|
||||
all_warnings.extend(warnings)
|
||||
|
||||
if not ladder:
|
||||
all_warnings.append(
|
||||
f"WARNUNG: Agent '{agent_name}' hat NACH Filterung keine nutzbaren Modelle mehr - "
|
||||
f"lokaler Fallback sollte hier greifen"
|
||||
)
|
||||
|
||||
diverse_enough, families_in_use = check_diversity(priority_by_agent, min_distinct_families)
|
||||
if not diverse_enough:
|
||||
all_warnings.append(
|
||||
f"WARNUNG: Nur {len(families_in_use)} Modell-Familien im Einsatz "
|
||||
f"({', '.join(families_in_use)}), Minimum wäre {min_distinct_families}"
|
||||
)
|
||||
|
||||
output = {
|
||||
"generated_at": fetch_timestamp(),
|
||||
"min_context_tokens": min_context,
|
||||
"max_auto_escalation_factor": max_escalation_factor,
|
||||
"diversity_ok": diverse_enough,
|
||||
"model_families_in_use": families_in_use,
|
||||
"agents": priority_by_agent,
|
||||
"local_fallback": config["local_fallback"],
|
||||
"warnings": all_warnings,
|
||||
}
|
||||
|
||||
STATE_DIR.mkdir(exist_ok=True)
|
||||
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(output, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"Prioritätsliste geschrieben nach {OUTPUT_PATH}")
|
||||
if all_warnings:
|
||||
print(f"\n{len(all_warnings)} Warnung(en):")
|
||||
for w in all_warnings:
|
||||
print(f" - {w}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user