#!/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()