初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.ai_provider.config.models import ModelConfig
|
||||
from app.ai_provider.core.registry import ModelRegistry
|
||||
from app.ai_provider.routing.multimodal import MultiModalDecision
|
||||
from app.ai_provider.types.errors import AIProviderError
|
||||
|
||||
_SELECTOR_CAPABILITY_MAP: dict[MultiModalDecision, str] = {
|
||||
MultiModalDecision.VISION: "vision",
|
||||
MultiModalDecision.THINKING: "thinking",
|
||||
MultiModalDecision.TOOL_USE: "tool_use",
|
||||
MultiModalDecision.STRUCTURED_OUTPUT: "structured_output",
|
||||
MultiModalDecision.PLAIN_TEXT: "chat",
|
||||
}
|
||||
|
||||
|
||||
class ModelSelector:
|
||||
"""为给定上下文选择最佳模型。
|
||||
|
||||
用法::
|
||||
|
||||
selector = ModelSelector(registry)
|
||||
model = selector.select({"capabilities": {"vision", "thinking"}})
|
||||
model = selector.select({"preferred": "fast-model"})
|
||||
model = selector.select_from_decisions({MultiModalDecision.VISION})
|
||||
"""
|
||||
|
||||
def __init__(self, registry: ModelRegistry) -> None:
|
||||
self._registry = registry
|
||||
|
||||
def select(self, context: dict[str, Any]) -> ModelConfig:
|
||||
"""根据给定的上下文选择模型。
|
||||
|
||||
上下文键:
|
||||
|
||||
* ``capabilities`` (``set[str]``, 可选) — 必需的能力。
|
||||
* ``preferred`` (``str``, 可选) — 偏好的模型名或别名。
|
||||
* ``avoid`` (``set[str]``, 可选) — 要排除的模型名。
|
||||
|
||||
如果给了偏好模型但不能满足所有必需的能力,此方法会回退到
|
||||
能满足要求的模型。
|
||||
"""
|
||||
capabilities: set[str] = set(context.get("capabilities", set()))
|
||||
preferred: str | None = context.get("preferred")
|
||||
avoid: set[str] = set(context.get("avoid", set()))
|
||||
|
||||
# If preferred is given, resolve it and check capabilities
|
||||
if preferred is not None:
|
||||
model = self._registry.resolve(preferred)
|
||||
if (
|
||||
not capabilities or self._model_satisfies_all(model, capabilities)
|
||||
) and model.name not in avoid:
|
||||
return model
|
||||
|
||||
# Find models that satisfy all required capabilities
|
||||
if capabilities:
|
||||
return self._select_by_capabilities(capabilities, avoid)
|
||||
|
||||
# Fall back to default
|
||||
return self._registry.resolve()
|
||||
|
||||
def select_from_decisions(
|
||||
self,
|
||||
decisions: set[MultiModalDecision],
|
||||
) -> ModelConfig:
|
||||
"""基于 :class:`MultiModalDecision` 值选择模型。
|
||||
|
||||
将决策翻译为能力字符串并委托给 :meth:`select`。
|
||||
"""
|
||||
capabilities: set[str] = set()
|
||||
for d in decisions:
|
||||
cap = _SELECTOR_CAPABILITY_MAP.get(d)
|
||||
if cap and cap != "chat":
|
||||
capabilities.add(cap)
|
||||
return self.select({"capabilities": capabilities})
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _select_by_capabilities(
|
||||
self,
|
||||
capabilities: set[str],
|
||||
avoid: set[str],
|
||||
) -> ModelConfig:
|
||||
"""找到支持所有必需能力的模型。"""
|
||||
# Narrow down by each capability (ModelConfig is unhashable, use lists)
|
||||
candidates: list[ModelConfig] = list(self._registry.list_all())
|
||||
|
||||
for cap in capabilities:
|
||||
cap_models = list(self._registry.list_by_capability(cap))
|
||||
cap_names = {m.name for m in cap_models}
|
||||
candidates = [m for m in candidates if m.name in cap_names]
|
||||
|
||||
# Remove avoided models
|
||||
candidates = [m for m in candidates if m.name not in avoid]
|
||||
|
||||
if not candidates:
|
||||
raise AIProviderError(
|
||||
message=f"No active model supports all capabilities: {capabilities}",
|
||||
code="CAPABILITY_NOT_FOUND",
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
# Prefer higher-weight models (higher = better)
|
||||
return max(candidates, key=lambda m: m.weight)
|
||||
|
||||
@staticmethod
|
||||
def _model_satisfies_all(model: ModelConfig, capabilities: set[str]) -> bool:
|
||||
caps = model.resolved_capabilities()
|
||||
return all(caps.get(cap, False) for cap in capabilities)
|
||||
Reference in new issue
Block a user