330 lines
12 KiB
Python
330 lines
12 KiB
Python
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",
|
|
]
|