153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
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 不存在")
|