from __future__ import annotations import json import os from copy import deepcopy from pathlib import Path from typing import Any DEFAULT_ROUTING_RULES_WORKSPACE_PATH = "backend/.control_plane/pmflow-routing-rules.json" _WORKSPACE_ROOT = Path(__file__).resolve().parents[4] _DEFAULT_ROUTING_RULES_PATH = (_WORKSPACE_ROOT / DEFAULT_ROUTING_RULES_WORKSPACE_PATH).resolve() _PROVIDER_ALIASES = { "deep-seek": "deepseek", "local": "ollama", } _DEFAULT_PROVIDER_DEFAULTS: dict[str, dict[str, str]] = { "deepseek": { "name": "DeepSeek", "base_url": "https://api.deepseek.com/v1", "model": "deepseek-chat", "api_env": "DEEPSEEK_API_KEY", }, "dashscope": { "name": "Qwen (DashScope)", "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", "model": "qwen-plus", "api_env": "DASHSCOPE_API_KEY", }, "openai": { "name": "OpenAI", "base_url": "https://api.edgefn.net/v1", "model": "GLM-5", "api_env": "OPENAI_API_KEY", }, "zhipu": { "name": "Zhipu BigModel", "base_url": "https://open.bigmodel.cn/api/paas/v4", "model": "glm-5.1", "api_env": "ZHIPU_API_KEY", }, "ollama": { "name": "Ollama", "base_url": "http://127.0.0.1:11434", "model": "llama3:latest", "api_env": "", }, } _DEFAULT_TIER_DEFAULTS: dict[str, dict[str, str]] = { "low": { "provider": "dashscope", "model": "qwen-plus", "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", "api_env": "DASHSCOPE_API_KEY", }, "high": { "provider": "openai", "model": "GLM-5", "base_url": "https://api.edgefn.net/v1", "api_env": "OPENAI_API_KEY", }, } VALID_TIER_ALIASES = tuple(_DEFAULT_TIER_DEFAULTS.keys()) _CONFIG_CACHE: dict[str, Any] = {"path": "", "mtime": -1.0, "bundle": None} def _normalize_provider(provider: str) -> str: value = str(provider or "").strip().lower() value = _PROVIDER_ALIASES.get(value, value) if value not in _DEFAULT_PROVIDER_DEFAULTS: raise ValueError("provider 仅支持 deepseek/dashscope/openai/zhipu/ollama") return value def _normalize_tier(tier: str) -> str: value = str(tier or "").strip().lower() if value not in _DEFAULT_TIER_DEFAULTS: raise ValueError("tier 仅支持 low/high") return value def resolve_model_config_path(path: str | Path | None = None) -> Path: raw = path if path is not None else os.getenv("CONTROL_PLANE_ROUTING_RULES_PATH", "") raw_text = str(raw or "").strip() if not raw_text: return _DEFAULT_ROUTING_RULES_PATH candidate = Path(raw_text).expanduser() if candidate.is_absolute(): return candidate return (_WORKSPACE_ROOT / candidate).resolve() def _normalize_provider_defaults(raw: Any) -> dict[str, dict[str, str]]: payload = raw if isinstance(raw, dict) else {} result: dict[str, dict[str, str]] = {} for provider, defaults in _DEFAULT_PROVIDER_DEFAULTS.items(): item = payload.get(provider) if isinstance(payload.get(provider), dict) else {} result[provider] = { "name": str(item.get("name") or defaults["name"]).strip(), "base_url": str(item.get("base_url") or defaults["base_url"]).strip().rstrip("/"), "model": str(item.get("model") or defaults["model"]).strip(), "api_env": str(item.get("api_env") or defaults["api_env"]).strip(), } return result def _normalize_tier_defaults(raw: Any) -> dict[str, dict[str, str]]: payload = raw if isinstance(raw, dict) else {} extra_keys = sorted( { str(key or "").strip().lower() for key in payload.keys() if str(key or "").strip() } - set(VALID_TIER_ALIASES) ) if extra_keys: raise ValueError("tier 仅支持 low/high") result: dict[str, dict[str, str]] = {} for tier, defaults in _DEFAULT_TIER_DEFAULTS.items(): item = payload.get(tier) if isinstance(payload.get(tier), dict) else {} provider_raw = str(item.get("provider") or defaults["provider"]).strip() provider = _normalize_provider(provider_raw) result[tier] = { "provider": provider, "model": str(item.get("model") or defaults["model"]).strip(), "base_url": str(item.get("base_url") or defaults["base_url"]).strip().rstrip("/"), "api_env": str(item.get("api_env") or defaults["api_env"]).strip(), } return result def rule_matches_function(rule: dict[str, Any], function_name: str) -> bool: if bool(rule.get("match_all", False)): return True fn = str(function_name or "").strip().lower() if not fn: return False exact = str(rule.get("function_exact") or "").strip().lower() if exact and fn == exact: return True prefix = str(rule.get("function_prefix") or "").strip().lower() if prefix in {"*", "all"}: return True if prefix and fn.startswith(prefix): return True match_any = rule.get("match_any") if isinstance(match_any, list): for hint in match_any: token = str(hint or "").strip().lower() if token and token in fn: return True return False def find_matching_rule( function_name: str, *, tier_filter: set[str] | None = None, bundle: dict[str, Any] | None = None, path: str | Path | None = None, ) -> dict[str, Any] | None: config_bundle = bundle if isinstance(bundle, dict) else load_model_config(path=path) rules = config_bundle.get("rules") if isinstance(config_bundle.get("rules"), list) else [] indexed: list[tuple[int, int, dict[str, Any]]] = [] for idx, row in enumerate(rules): if not isinstance(row, dict): continue if not bool(row.get("enabled", True)): continue tier_name = str(row.get("tier") or "").strip().lower() if tier_name not in VALID_TIER_ALIASES: continue if tier_filter and tier_name not in tier_filter: continue if not rule_matches_function(row, function_name): continue indexed.append((int(row.get("priority") or 0), -idx, row)) if not indexed: return None indexed.sort(reverse=True) return deepcopy(indexed[0][2]) def build_routing_config_snapshot( *, bundle: dict[str, Any] | None = None, path: str | Path | None = None, ) -> dict[str, str]: config_bundle = bundle if isinstance(bundle, dict) else load_model_config(path=path) return { "routing_config_version": str(config_bundle.get("version") or "").strip(), "routing_config_updated_at": str(config_bundle.get("updated_at") or "").strip(), "config_path": str(config_bundle.get("config_path") or "").strip(), } def _normalize_rules(raw: Any) -> list[dict[str, Any]]: rows = raw if isinstance(raw, list) else [] result: list[dict[str, Any]] = [] for item in rows: if not isinstance(item, dict): continue tier_name = str(item.get("tier") or "").strip().lower() if tier_name and tier_name not in VALID_TIER_ALIASES: raise ValueError("tier 仅支持 low/high") result.append(deepcopy(item)) return result def _normalize_bundle(raw: Any, *, config_path: Path) -> dict[str, Any]: payload = raw if isinstance(raw, dict) else {} bundle = { "version": str(payload.get("version") or "v3").strip(), "updated_at": str(payload.get("updated_at") or "").strip(), "source_of_truth": str(payload.get("source_of_truth") or "control_plane").strip(), "description": str(payload.get("description") or "").strip(), "config_path": str(config_path), "provider_defaults": _normalize_provider_defaults(payload.get("provider_defaults")), "tier_defaults": _normalize_tier_defaults(payload.get("tier_defaults")), "rules": _normalize_rules(payload.get("rules")), } return bundle def _load_raw_payload(config_path: Path) -> dict[str, Any]: if not config_path.exists(): return {} try: parsed = json.loads(config_path.read_text(encoding="utf-8")) return parsed if isinstance(parsed, dict) else {} except Exception: return {} def load_model_config(*, refresh: bool = False, path: str | Path | None = None) -> dict[str, Any]: config_path = resolve_model_config_path(path) mtime = config_path.stat().st_mtime if config_path.exists() else -1.0 cached = _CONFIG_CACHE if ( not refresh and cached.get("bundle") is not None and cached.get("path") == str(config_path) and cached.get("mtime") == mtime ): return deepcopy(cached["bundle"]) raw = _load_raw_payload(config_path) bundle = _normalize_bundle(raw, config_path=config_path) _CONFIG_CACHE.update({"path": str(config_path), "mtime": mtime, "bundle": deepcopy(bundle)}) return deepcopy(bundle) def get_provider_defaults(provider: str | None = None, *, refresh: bool = False) -> dict[str, Any]: bundle = load_model_config(refresh=refresh) defaults = bundle.get("provider_defaults") if isinstance(bundle.get("provider_defaults"), dict) else {} if provider is None: return deepcopy(defaults) provider_name = _normalize_provider(provider) fallback = _DEFAULT_PROVIDER_DEFAULTS[provider_name] return deepcopy(defaults.get(provider_name, fallback)) def get_tier_defaults(tier: str | None = None, *, refresh: bool = False) -> dict[str, Any]: bundle = load_model_config(refresh=refresh) defaults = bundle.get("tier_defaults") if isinstance(bundle.get("tier_defaults"), dict) else {} if tier is None: return deepcopy(defaults) tier_name = _normalize_tier(tier) fallback = _DEFAULT_TIER_DEFAULTS[tier_name] return deepcopy(defaults.get(tier_name, fallback)) def build_provider_runtime( provider: str, *, api_key: str = "", model: str | None = None, base_url: str | None = None, ) -> dict[str, Any]: provider_name = _normalize_provider(provider) defaults = get_provider_defaults(provider_name) return { "id": f"env-{provider_name}", "name": str(defaults.get("name") or _DEFAULT_PROVIDER_DEFAULTS[provider_name]["name"]).strip(), "provider": provider_name, "api_key": str(api_key or "").strip(), "base_url": str(base_url or defaults.get("base_url") or "").strip().rstrip("/"), "model": str(model or defaults.get("model") or "").strip(), "api_env": str(defaults.get("api_env") or _DEFAULT_PROVIDER_DEFAULTS[provider_name]["api_env"]).strip(), } def build_tier_runtime( tier: str, *, api_key: str = "", ) -> dict[str, Any]: tier_name = _normalize_tier(tier) tier_defaults = get_tier_defaults(tier_name) provider_name = _normalize_provider(str(tier_defaults.get("provider") or "dashscope")) provider_defaults = get_provider_defaults(provider_name) return { "id": f"tier-{tier_name}", "name": str(provider_defaults.get("name") or "").strip(), "provider": provider_name, "api_key": str(api_key or "").strip(), "base_url": str(tier_defaults.get("base_url") or provider_defaults.get("base_url") or "").strip().rstrip("/"), "model": str(tier_defaults.get("model") or provider_defaults.get("model") or "").strip(), "api_env": str(tier_defaults.get("api_env") or provider_defaults.get("api_env") or "").strip(), "tier": tier_name, } __all__ = [ "VALID_TIER_ALIASES", "build_provider_runtime", "build_routing_config_snapshot", "build_tier_runtime", "find_matching_rule", "get_provider_defaults", "get_tier_defaults", "load_model_config", "rule_matches_function", "resolve_model_config_path", ]