PMFlow/backend/app/services/llm_service/mixin_control_plane_routing.py

193 lines
7.6 KiB
Python

from __future__ import annotations
from .shared import *
class LLMControlPlaneRoutingMixin:
def _mixed_routing_enabled(self) -> bool:
return _env_bool("LLM_MIXED_ROUTING_ENABLED", False)
def _control_plane_routing_enabled(self) -> bool:
return _env_bool("CONTROL_PLANE_ROUTING_ENABLED", False)
def _control_plane_only(self) -> bool:
return _env_bool("CONTROL_PLANE_ONLY", False)
def _ensure_control_plane_engine(self, engine: dict[str, Any]) -> dict[str, Any]:
runtime = dict(engine or {})
runtime["source_provider"] = str(runtime.get("source_provider") or runtime.get("provider") or "").strip().lower()
return runtime
def _control_plane_rules_path(self) -> str:
return str(resolve_model_config_path())
def _load_control_plane_routing_bundle(self) -> dict[str, Any]:
now = time.time()
if self._control_plane_routing_cache and self._control_plane_routing_cache[0] > now:
return dict(self._control_plane_routing_cache[1])
path = Path(self._control_plane_rules_path())
bundle: dict[str, Any] = load_model_config(path=path)
self._control_plane_routing_cache = (now + CONTROL_PLANE_ROUTING_CACHE_TTL_SECONDS, dict(bundle))
return dict(bundle)
@staticmethod
def _rule_matches_function(rule: dict[str, Any], function_name: str) -> bool:
return rule_matches_function(rule, function_name)
def _match_control_plane_rule(
self,
function_name: str,
*,
tier_filter: Optional[set[str]] = None,
) -> Optional[dict[str, Any]]:
if not self._control_plane_routing_enabled():
return None
bundle = self._load_control_plane_routing_bundle()
return find_matching_rule(
function_name,
tier_filter=tier_filter,
bundle=bundle,
)
def _resolve_control_plane_engine(
self,
*,
function_name: str,
engines: list[dict[str, Any]],
active_engine: dict[str, Any],
slot_bundle: dict[str, Any] | None = None,
routing_meta: dict[str, Any] | None = None,
) -> Optional[dict[str, Any]]:
meta = _normalize_routing_meta(routing_meta)
tier_filter: set[str] = {"low", "high"}
explicit_tier = _optional_tier_alias(
meta.get("planned_tier")
or meta.get("tier_alias")
or meta.get("executed_tier")
)
if (
str(meta.get("routing_decision") or "").strip().lower() == "force_low_first"
and explicit_tier in {"low", "high"}
):
tier_filter = {explicit_tier}
rule = self._match_control_plane_rule(function_name, tier_filter=tier_filter)
if not rule:
return None
routed = self._resolve_engine_from_rule(
rule=rule,
engines=engines,
active_engine=active_engine,
slot_bundle=slot_bundle,
)
if routed is None:
return None
bundle = self._load_control_plane_routing_bundle()
config_snapshot = build_routing_config_snapshot(bundle=bundle)
tier_alias = str(rule.get("tier") or "").strip().lower() or "low"
routed = _merge_engine_routing_meta(
routed,
tier_alias=tier_alias,
planned_tier=tier_alias,
executed_tier=tier_alias,
route_source="default",
route_rule_name=str(rule.get("name") or "").strip(),
rule_priority=int(rule.get("priority") or 0),
routing_decision="rule_match",
routing_config_version=config_snapshot.get("routing_config_version"),
routing_config_updated_at=config_snapshot.get("routing_config_updated_at"),
)
_runtime_log(
"PMFlow 本地路由层路由",
(
"规则命中:"
f"function={function_name}, {_routing_meta_log_text(_read_engine_routing_meta(routed))}"
),
)
return routed
def _resolve_engine_from_rule(
self,
*,
rule: dict[str, Any],
engines: list[dict[str, Any]],
active_engine: dict[str, Any],
slot_bundle: dict[str, Any] | None = None,
) -> Optional[dict[str, Any]]:
tier = str(rule.get("tier") or "").strip().lower()
provider_raw = str(rule.get("provider") or "").strip().lower()
engine_id = str(rule.get("engine_id") or "").strip()
model_override = str(rule.get("model") or "").strip()
base_url_override = str(rule.get("base_url") or "").strip().rstrip("/")
routed: Optional[dict[str, Any]] = None
if engine_id:
for item in engines:
if str(item.get("id") or "").strip() == engine_id:
routed = self._normalize_engine_runtime(item)
break
if routed is None and tier in {"high", "low"}:
routed = self._resolve_tier_engine(
tier=tier,
engines=engines,
active_engine=active_engine,
slot_bundle=slot_bundle,
)
if routed is None and provider_raw:
try:
provider = _normalize_provider(provider_raw)
except Exception:
provider = ""
if provider:
for item in engines:
if str(item.get("provider") or "").strip().lower() == provider:
routed = self._normalize_engine_runtime(item)
break
if routed is None:
return None
if model_override and not self._slot_binding_exists(slot_bundle=slot_bundle, tier=tier):
routed["model"] = model_override
if base_url_override and not self._slot_binding_exists(slot_bundle=slot_bundle, tier=tier):
routed["base_url"] = base_url_override
return routed
@staticmethod
def _slot_binding_exists(*, slot_bundle: dict[str, Any] | None, tier: str) -> bool:
slots = {}
if isinstance(slot_bundle, dict):
active = slot_bundle.get("active") if isinstance(slot_bundle.get("active"), dict) else {}
if isinstance(active.get("slots"), dict):
slots = active["slots"]
elif isinstance(slot_bundle.get("slots"), dict):
slots = slot_bundle["slots"]
slot = slots.get(str(tier or "").strip().lower())
return isinstance(slot, dict) and bool(str(slot.get("primary_engine_id") or "").strip())
def _resolve_function_tier(self, function_name: str, routing_meta: dict[str, Any] | None = None) -> str:
meta = _normalize_routing_meta(routing_meta)
explicit_tier = _optional_tier_alias(
meta.get("planned_tier")
or meta.get("tier_alias")
or meta.get("executed_tier")
)
if explicit_tier:
return explicit_tier
fn = str(function_name or "").strip().lower()
if not fn:
return str(os.getenv("LLM_ROUTING_DEFAULT_TIER", "low") or "low").strip().lower()
bundle = self._load_control_plane_routing_bundle()
rule = find_matching_rule(fn, tier_filter={"low", "high"}, bundle=bundle)
if isinstance(rule, dict):
configured_tier = str(rule.get("tier") or "").strip().lower()
if configured_tier in {"low", "high"}:
return configured_tier
default_tier = str(os.getenv("LLM_ROUTING_DEFAULT_TIER", "low") or "low").strip().lower()
if default_tier in {"low", "high"}:
return default_tier
return "high" if self._mixed_routing_enabled() else "low"