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

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 不存在")