from __future__ import annotations from .shared import * class LLMTierEngineSelectionMixin: @staticmethod def _is_same_runtime_engine(a: dict[str, Any], b: dict[str, Any]) -> bool: return ( str(a.get("provider") or "").strip().lower() == str(b.get("provider") or "").strip().lower() and str(a.get("model") or "").strip().lower() == str(b.get("model") or "").strip().lower() and str(a.get("base_url") or "").strip().rstrip("/").lower() == str(b.get("base_url") or "").strip().rstrip("/").lower() ) async def _resolve_high_tier_engine_for_escalation( self, db: AsyncSession, *, tenant_id: int, primary_engine: dict[str, Any], ) -> Optional[dict[str, Any]]: _, bundle = await self._get_or_init_bundle(db, tenant_id=tenant_id) active_engine = self._resolve_active_engine_from_bundle(bundle) engines: list[dict[str, Any]] = [e for e in bundle.get("engines", []) if isinstance(e, dict)] routed = self._resolve_tier_engine( tier="high", engines=engines, active_engine=active_engine, ) if not routed: return None if self._is_same_runtime_engine(routed, primary_engine): return None provider = str(routed.get("provider") or "").strip().lower() api_key = str(routed.get("api_key") or "").strip() if provider != "ollama" and not api_key: return None return routed def _pick_tier_fallback_engine( self, *, tier: str, engines: list[dict[str, Any]], active_engine: dict[str, Any], ) -> Optional[dict[str, Any]]: for item in engines: candidate = self._normalize_engine_runtime(item) provider = str(candidate.get("provider") or "") api_key = str(candidate.get("api_key") or "").strip() if provider != "ollama" and not api_key: continue if tier == "high" and provider == "ollama": continue if str(candidate.get("id") or "") == str(active_engine.get("id") or ""): continue _runtime_log( "LLM路由", ( "分层路由回退引擎生效:" f"tier={tier}, decision=tier_fallback_engine" ), ) return candidate return None def _resolve_tier_engine( self, *, tier: str, engines: list[dict[str, Any]], active_engine: dict[str, Any], ) -> Optional[dict[str, Any]]: tier = str(tier or "").strip().lower() if tier not in {"low", "high"}: return None tier_defaults = dict(TIER_DEFAULTS.get(tier) or {}) provider_env = str(tier_defaults.get("provider") or "glm").strip() routed: Optional[dict[str, Any]] = None if routed is None and provider_env: try: provider = _normalize_provider(provider_env) 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: try: routed = self._env_provider_runtime(provider) except Exception: routed = None if routed is None: return None provider = str(routed.get("provider") or "").strip().lower() if tier_defaults.get("model"): routed["model"] = str(tier_defaults.get("model") or routed.get("model") or "").strip() if tier_defaults.get("base_url"): routed["base_url"] = str(tier_defaults.get("base_url") or routed.get("base_url") or "").strip().rstrip("/") routed = _merge_engine_routing_meta( routed, tier_alias=tier, planned_tier=tier, executed_tier=tier, route_source="default", routing_decision="tier_default", ) if str(routed.get("id") or "") != str(active_engine.get("id") or ""): _runtime_log( "LLM路由", ( "混合路由已命中:" f"tier={tier}, decision=tier_default" ), ) return self._ensure_openclaw_control_plane_engine(routed) async def _resolve_engine_for_test( self, db: AsyncSession, *, tenant_id: int, engine_id: Optional[str], ) -> dict[str, Any]: _, bundle = await self._get_or_init_bundle(db, tenant_id=tenant_id) settings = self._normalize_engine_settings(bundle.get("settings")) if not engine_id: engine = self._resolve_active_engine_from_bundle(bundle) return self._ensure_engine_allowed_for_test( engine=engine, settings=settings, engine_label="当前激活引擎", ) engines: list[dict[str, Any]] = [e for e in bundle.get("engines", []) if isinstance(e, dict)] for item in engines: if str(item.get("id") or "") == str(engine_id): engine = self._normalize_engine_runtime(item) return self._ensure_engine_allowed_for_test( engine=engine, settings=settings, engine_label="目标引擎", ) raise ValueError("engine 不存在")