mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 22:17:51 +00:00
refactor(models): simplify provider capability catalog
This commit is contained in:
parent
2eb5e4d9f5
commit
60cf5dd4aa
16 changed files with 520 additions and 1368 deletions
|
|
@ -1,297 +0,0 @@
|
|||
"""Canonical, exact-match model/provider behavior observations.
|
||||
|
||||
The registry captures behavior that cannot safely be promoted to a provider-
|
||||
wide capability. Selectors accept already-structured identity (provider,
|
||||
model ID/family/version, API dialect, and canonical capabilities); they never
|
||||
extract those facts from a display name with regexes or substring matching.
|
||||
|
||||
This is shape/evidence data only. Runtime request builders can consume it in a
|
||||
later integration pass after their endpoint has supplied structured identity.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from src import model_capabilities as mc
|
||||
from src import provider_capability_schemas as pcs
|
||||
|
||||
|
||||
def _identity(value: Any) -> str:
|
||||
return str(value or "").strip().lower()
|
||||
|
||||
|
||||
def _version(value: Any) -> tuple[int, ...]:
|
||||
if not isinstance(value, (list, tuple)):
|
||||
return ()
|
||||
out: list[int] = []
|
||||
for part in value:
|
||||
try:
|
||||
out.append(int(part))
|
||||
except (TypeError, ValueError):
|
||||
return ()
|
||||
return tuple(out)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ModelBehaviorSelector:
|
||||
providers: tuple[str, ...] = ()
|
||||
model_ids: tuple[str, ...] = ()
|
||||
model_families: tuple[str, ...] = ()
|
||||
minimum_model_version: tuple[int, ...] = ()
|
||||
minimum_provider_version: tuple[int, ...] = ()
|
||||
api_dialects: tuple[str, ...] = ()
|
||||
required_capabilities: tuple[str, ...] = ()
|
||||
|
||||
def matches(
|
||||
self,
|
||||
*,
|
||||
provider: Any,
|
||||
model_id: Any = "",
|
||||
model_family: Any = "",
|
||||
model_version: Any = (),
|
||||
provider_version: Any = (),
|
||||
api_dialect: Any = "",
|
||||
capabilities: Any = (),
|
||||
) -> bool:
|
||||
provider_id = pcs.normalize_provider_id(provider)
|
||||
if self.providers and provider_id not in self.providers:
|
||||
return False
|
||||
|
||||
identity_constraints = bool(self.model_ids or self.model_families)
|
||||
identity_match = (
|
||||
_identity(model_id) in self.model_ids
|
||||
or _identity(model_family) in self.model_families
|
||||
)
|
||||
if identity_constraints and not identity_match:
|
||||
return False
|
||||
|
||||
actual_model_version = _version(model_version)
|
||||
if self.minimum_model_version and (
|
||||
not actual_model_version or actual_model_version < self.minimum_model_version
|
||||
):
|
||||
return False
|
||||
actual_provider_version = _version(provider_version)
|
||||
if self.minimum_provider_version and (
|
||||
not actual_provider_version or actual_provider_version < self.minimum_provider_version
|
||||
):
|
||||
return False
|
||||
if self.api_dialects and str(api_dialect or "").strip() not in self.api_dialects:
|
||||
return False
|
||||
|
||||
if isinstance(capabilities, Mapping):
|
||||
capability_values: Iterable[Any] = (
|
||||
key for key, enabled in capabilities.items() if enabled is True
|
||||
)
|
||||
elif isinstance(capabilities, str):
|
||||
capability_values = (capabilities,)
|
||||
elif isinstance(capabilities, Iterable):
|
||||
capability_values = capabilities
|
||||
else:
|
||||
capability_values = ()
|
||||
normalized_caps = {
|
||||
normalized
|
||||
for value in capability_values
|
||||
if (normalized := mc.normalize_capability(value))
|
||||
}
|
||||
return set(self.required_capabilities).issubset(normalized_caps)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ModelBehaviorQuirk:
|
||||
quirk_id: str
|
||||
selector: ModelBehaviorSelector
|
||||
request_omit_paths: tuple[str, ...] = ()
|
||||
request_fixed_values: tuple[tuple[str, Any], ...] = ()
|
||||
required_history_paths: tuple[str, ...] = ()
|
||||
response_reasoning_paths: tuple[str, ...] = ()
|
||||
reasoning_controls: tuple[mc.ReasoningControl, ...] = ()
|
||||
status: str = mc.ASSERTION_CLAIMED
|
||||
source: str = mc.SOURCE_PROVIDER_DOCS_REGISTRY
|
||||
confidence: str = mc.CONFIDENCE_REGISTRY
|
||||
evidence_refs: tuple[str, ...] = ()
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"quirk_id": self.quirk_id,
|
||||
"selector": {
|
||||
"providers": list(self.selector.providers),
|
||||
"model_ids": list(self.selector.model_ids),
|
||||
"model_families": list(self.selector.model_families),
|
||||
"minimum_model_version": list(self.selector.minimum_model_version),
|
||||
"minimum_provider_version": list(self.selector.minimum_provider_version),
|
||||
"api_dialects": list(self.selector.api_dialects),
|
||||
"required_capabilities": list(self.selector.required_capabilities),
|
||||
},
|
||||
"request_omit_paths": list(self.request_omit_paths),
|
||||
"request_fixed_values": dict(self.request_fixed_values),
|
||||
"required_history_paths": list(self.required_history_paths),
|
||||
"response_reasoning_paths": list(self.response_reasoning_paths),
|
||||
"reasoning_controls": [control.to_dict() for control in self.reasoning_controls],
|
||||
"status": self.status,
|
||||
"source": self.source,
|
||||
"confidence": self.confidence,
|
||||
"evidence_refs": list(self.evidence_refs),
|
||||
}
|
||||
|
||||
|
||||
MODEL_BEHAVIOR_QUIRKS = (
|
||||
ModelBehaviorQuirk(
|
||||
quirk_id="moonshot.kimi-k2.5-k2.6.provider-fixed-temperature",
|
||||
selector=ModelBehaviorSelector(
|
||||
providers=("moonshot",),
|
||||
model_ids=("kimi-k2.5", "kimi-k2.6"),
|
||||
model_families=("kimi-k2.5", "kimi-k2.6"),
|
||||
api_dialects=(pcs.DIALECT_OPENAI_CHAT,),
|
||||
),
|
||||
request_omit_paths=("temperature",),
|
||||
evidence_refs=(
|
||||
"github:odysseus-dev/odysseus#3960",
|
||||
"commit:f5d3e509",
|
||||
),
|
||||
),
|
||||
ModelBehaviorQuirk(
|
||||
quirk_id="moonshot.kimi-k2.5-k2.6.tool-history-reasoning-content",
|
||||
selector=ModelBehaviorSelector(
|
||||
providers=("moonshot",),
|
||||
model_ids=("kimi-k2.5", "kimi-k2.6"),
|
||||
model_families=("kimi-k2.5", "kimi-k2.6"),
|
||||
api_dialects=(pcs.DIALECT_OPENAI_CHAT,),
|
||||
),
|
||||
required_history_paths=("messages[assistant+tool_calls].reasoning_content",),
|
||||
response_reasoning_paths=(
|
||||
"choices[].message.reasoning_content",
|
||||
"choices[].delta.reasoning_content",
|
||||
),
|
||||
evidence_refs=(
|
||||
"github:odysseus-dev/odysseus#3118",
|
||||
"commit:2e6fff22",
|
||||
),
|
||||
),
|
||||
ModelBehaviorQuirk(
|
||||
quirk_id="anthropic.claude-opus-4.7-plus.omit-sampling-controls",
|
||||
selector=ModelBehaviorSelector(
|
||||
providers=("anthropic",),
|
||||
model_families=("claude-opus",),
|
||||
minimum_model_version=(4, 7),
|
||||
api_dialects=(pcs.DIALECT_ANTHROPIC_MESSAGES,),
|
||||
),
|
||||
request_omit_paths=("temperature", "top_p", "top_k"),
|
||||
evidence_refs=(
|
||||
"github:odysseus-dev/odysseus#3117",
|
||||
"commit:4f48cfa9",
|
||||
),
|
||||
),
|
||||
ModelBehaviorQuirk(
|
||||
quirk_id="mistral.reasoning.structured-content",
|
||||
selector=ModelBehaviorSelector(
|
||||
providers=("mistral",),
|
||||
model_families=("magistral", "mistral-small", "mistral-medium"),
|
||||
api_dialects=(pcs.DIALECT_OPENAI_CHAT,),
|
||||
required_capabilities=(mc.CAP_REASONING,),
|
||||
),
|
||||
response_reasoning_paths=(
|
||||
"choices[].message.content[type=thinking].thinking[].text",
|
||||
"choices[].delta.content[type=thinking].thinking[].text",
|
||||
),
|
||||
reasoning_controls=(
|
||||
mc.ReasoningControl.build(
|
||||
mechanism=mc.REASONING_CONTROL_EFFORT,
|
||||
values=(mc.REASONING_CONTROL_VALUE_ON, mc.REASONING_CONTROL_VALUE_OFF),
|
||||
native_values=("high", "medium", "low", "none"),
|
||||
request_path="reasoning_effort",
|
||||
response_paths=("choices[].message.content[type=thinking]",),
|
||||
status=mc.ASSERTION_CLAIMED,
|
||||
source=mc.SOURCE_PROVIDER_DOCS_REGISTRY,
|
||||
confidence=mc.CONFIDENCE_REGISTRY,
|
||||
),
|
||||
),
|
||||
evidence_refs=(
|
||||
"github:odysseus-dev/odysseus#4698",
|
||||
"commit:bd9149f7",
|
||||
"https://docs.mistral.ai/capabilities/reasoning/",
|
||||
),
|
||||
),
|
||||
ModelBehaviorQuirk(
|
||||
quirk_id="ollama.native.reasoning-control",
|
||||
selector=ModelBehaviorSelector(
|
||||
providers=("ollama",),
|
||||
model_families=("qwen3", "deepseek-v3.1", "deepseek-r1"),
|
||||
api_dialects=(pcs.DIALECT_OLLAMA_NATIVE,),
|
||||
required_capabilities=(mc.CAP_REASONING,),
|
||||
),
|
||||
response_reasoning_paths=("message.thinking", "thinking"),
|
||||
reasoning_controls=(
|
||||
mc.ReasoningControl.build(
|
||||
mechanism=mc.REASONING_CONTROL_NATIVE_BOOL,
|
||||
values=(mc.REASONING_CONTROL_VALUE_ON, mc.REASONING_CONTROL_VALUE_OFF),
|
||||
native_values=(True, False),
|
||||
request_path="think",
|
||||
response_paths=("message.thinking", "thinking"),
|
||||
status=mc.ASSERTION_CLAIMED,
|
||||
source=mc.SOURCE_PROVIDER_DOCS_REGISTRY,
|
||||
confidence=mc.CONFIDENCE_REGISTRY,
|
||||
),
|
||||
),
|
||||
evidence_refs=(
|
||||
"https://docs.ollama.com/capabilities/thinking",
|
||||
"github:odysseus-dev/odysseus#3031",
|
||||
),
|
||||
),
|
||||
ModelBehaviorQuirk(
|
||||
quirk_id="ollama.native.gpt-oss-reasoning-level",
|
||||
selector=ModelBehaviorSelector(
|
||||
providers=("ollama",),
|
||||
model_families=("gpt-oss", "gptoss"),
|
||||
api_dialects=(pcs.DIALECT_OLLAMA_NATIVE,),
|
||||
required_capabilities=(mc.CAP_REASONING,),
|
||||
),
|
||||
response_reasoning_paths=("message.thinking", "thinking"),
|
||||
reasoning_controls=(
|
||||
mc.ReasoningControl.build(
|
||||
mechanism=mc.REASONING_CONTROL_EFFORT,
|
||||
values=(mc.REASONING_CONTROL_VALUE_ON,),
|
||||
native_values=("low", "medium", "high"),
|
||||
request_path="think",
|
||||
response_paths=("message.thinking", "thinking"),
|
||||
status=mc.ASSERTION_CLAIMED,
|
||||
source=mc.SOURCE_PROVIDER_DOCS_REGISTRY,
|
||||
confidence=mc.CONFIDENCE_REGISTRY,
|
||||
),
|
||||
),
|
||||
evidence_refs=("https://docs.ollama.com/capabilities/thinking",),
|
||||
),
|
||||
ModelBehaviorQuirk(
|
||||
quirk_id="ollama.openai-compat.0.20.6-reasoning-disable",
|
||||
selector=ModelBehaviorSelector(
|
||||
providers=("ollama",),
|
||||
model_families=("qwen3.5",),
|
||||
minimum_provider_version=(0, 20, 6),
|
||||
api_dialects=(pcs.DIALECT_OPENAI_CHAT,),
|
||||
required_capabilities=(mc.CAP_REASONING,),
|
||||
),
|
||||
request_fixed_values=(("reasoning_effort", "none"),),
|
||||
status=mc.ASSERTION_CLAIMED,
|
||||
source=mc.SOURCE_HEURISTIC,
|
||||
confidence=mc.CONFIDENCE_HEURISTIC,
|
||||
evidence_refs=("github:odysseus-dev/odysseus#5503",),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def matching_quirks(**identity: Any) -> tuple[ModelBehaviorQuirk, ...]:
|
||||
return tuple(
|
||||
quirk
|
||||
for quirk in MODEL_BEHAVIOR_QUIRKS
|
||||
if quirk.selector.matches(**identity)
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MODEL_BEHAVIOR_QUIRKS",
|
||||
"ModelBehaviorQuirk",
|
||||
"ModelBehaviorSelector",
|
||||
"matching_quirks",
|
||||
]
|
||||
|
|
@ -657,106 +657,6 @@ class DeterministicControl:
|
|||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ReasoningControl:
|
||||
"""A provider/model-supported request mechanism for reasoning.
|
||||
|
||||
This is intentionally separate from :class:`DeterministicControl` and
|
||||
from the user's on/off/auto preference. The mechanism records the native
|
||||
request shape that a later resolver may choose after provider and model
|
||||
evidence have been reconciled.
|
||||
"""
|
||||
|
||||
mechanism: str = ""
|
||||
values: tuple[str, ...] = ()
|
||||
native_values: tuple[Any, ...] = ()
|
||||
request_path: str = ""
|
||||
response_paths: tuple[str, ...] = ()
|
||||
status: str = ASSERTION_UNKNOWN
|
||||
source: str = SOURCE_UNKNOWN
|
||||
confidence: str = CONFIDENCE_UNKNOWN
|
||||
evidence: tuple[tuple[str, Any], ...] = ()
|
||||
tested_at: str = ""
|
||||
|
||||
@classmethod
|
||||
def build(
|
||||
cls,
|
||||
*,
|
||||
mechanism: Any,
|
||||
values: Any = None,
|
||||
native_values: Any = None,
|
||||
request_path: Any = "",
|
||||
response_paths: Any = None,
|
||||
status: Any = ASSERTION_UNKNOWN,
|
||||
source: Any = SOURCE_UNKNOWN,
|
||||
confidence: Any = CONFIDENCE_UNKNOWN,
|
||||
evidence: Mapping[str, Any] | None = None,
|
||||
tested_at: Any = "",
|
||||
) -> "ReasoningControl":
|
||||
normalized_mechanism = normalize_reasoning_control_mechanism(mechanism)
|
||||
normalized_status = normalize_assertion_status(status)
|
||||
if not normalized_mechanism:
|
||||
normalized_status = ASSERTION_UNKNOWN
|
||||
if isinstance(response_paths, str):
|
||||
response_paths = (response_paths,)
|
||||
elif isinstance(response_paths, Mapping) or not isinstance(response_paths, Iterable):
|
||||
response_paths = ()
|
||||
if native_values is None:
|
||||
native_values = ()
|
||||
elif (
|
||||
isinstance(native_values, (str, Mapping))
|
||||
or not isinstance(native_values, Iterable)
|
||||
):
|
||||
native_values = (native_values,)
|
||||
return cls(
|
||||
mechanism=normalized_mechanism,
|
||||
values=_normalize_tokens(values, normalize_reasoning_control_value),
|
||||
native_values=tuple(native_values),
|
||||
request_path=str(request_path or "").strip(),
|
||||
response_paths=tuple(
|
||||
str(path or "").strip()
|
||||
for path in response_paths
|
||||
if str(path or "").strip()
|
||||
),
|
||||
status=normalized_status,
|
||||
source=normalize_source(source),
|
||||
confidence=normalize_confidence(confidence),
|
||||
evidence=_normalize_limits(evidence),
|
||||
tested_at=str(tested_at or "").strip(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, value: Mapping[str, Any]) -> "ReasoningControl":
|
||||
if not isinstance(value, Mapping):
|
||||
return cls.build(mechanism="")
|
||||
return cls.build(
|
||||
mechanism=value.get("mechanism"),
|
||||
values=value.get("values"),
|
||||
native_values=value.get("native_values"),
|
||||
request_path=value.get("request_path"),
|
||||
response_paths=value.get("response_paths"),
|
||||
status=value.get("status"),
|
||||
source=value.get("source"),
|
||||
confidence=value.get("confidence"),
|
||||
evidence=value.get("evidence"),
|
||||
tested_at=value.get("tested_at"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"mechanism": self.mechanism,
|
||||
"values": list(self.values),
|
||||
"native_values": list(self.native_values),
|
||||
"request_path": self.request_path,
|
||||
"response_paths": list(self.response_paths),
|
||||
"status": self.status,
|
||||
"source": self.source,
|
||||
"confidence": self.confidence,
|
||||
"evidence": dict(self.evidence),
|
||||
"tested_at": self.tested_at,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CapabilityProbeResult:
|
||||
provider: str
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from src.model_capability_readers import (
|
|||
sglang,
|
||||
)
|
||||
from src.model_capability_readers.base import (
|
||||
CANONICAL_MODEL_SHAPE_VERSION,
|
||||
ModelCapabilityRecord,
|
||||
VENDOR_ANTHROPIC,
|
||||
VENDOR_CEREBRAS,
|
||||
|
|
@ -104,7 +105,7 @@ def records_from_payload(
|
|||
vendor_id = detect_vendor(base_url, endpoint_kind)
|
||||
reader = reader_for_vendor(vendor_id)
|
||||
if reader is generic_openai:
|
||||
record_vendor = vendor_id if vendor_id not in {VENDOR_UNKNOWN, ""} else VENDOR_GENERIC_OPENAI
|
||||
record_vendor = vendor_id if vendor_id else VENDOR_UNKNOWN
|
||||
records = reader.records_from_payload(
|
||||
payload,
|
||||
vendor_id=record_vendor,
|
||||
|
|
@ -113,13 +114,12 @@ def records_from_payload(
|
|||
)
|
||||
else:
|
||||
records = reader.records_from_payload(payload, endpoint_id=endpoint_id, base_url=base_url)
|
||||
shape_id = resolution.catalog_shape.shape_id if resolution.catalog_shape else ""
|
||||
return tuple(
|
||||
replace(
|
||||
record,
|
||||
provider_schema_id=resolution.schema.provider_id,
|
||||
catalog_shape_id=shape_id,
|
||||
provider_resolution=resolution.stage,
|
||||
provider_source=resolution.provider_source,
|
||||
catalog_shape_id=resolution.shape_id,
|
||||
fallback=resolution.fallback,
|
||||
)
|
||||
for record in records
|
||||
)
|
||||
|
|
@ -127,6 +127,7 @@ def records_from_payload(
|
|||
|
||||
__all__ = [
|
||||
"ModelCapabilityRecord",
|
||||
"CANONICAL_MODEL_SHAPE_VERSION",
|
||||
"PLACEHOLDER_VENDOR_IDS",
|
||||
"READER_MODULES",
|
||||
"VENDOR_ANTHROPIC",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ VENDOR_XAI = "xai"
|
|||
VENDOR_ZAI = "zai"
|
||||
VENDOR_UNKNOWN = "unknown"
|
||||
|
||||
CANONICAL_MODEL_SHAPE_VERSION = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ModelCapabilityRecord:
|
||||
|
|
@ -54,14 +56,9 @@ class ModelCapabilityRecord:
|
|||
stable_model_id: str = ""
|
||||
capability_assertions: tuple[mc.CapabilityAssertion, ...] = ()
|
||||
deterministic_controls: tuple[mc.DeterministicControl, ...] = ()
|
||||
reasoning_controls: tuple[mc.ReasoningControl, ...] = ()
|
||||
model_family: str = ""
|
||||
model_version: tuple[int, ...] = ()
|
||||
provider_version: tuple[int, ...] = ()
|
||||
api_dialect: str = ""
|
||||
provider_schema_id: str = ""
|
||||
provider_source: str = "unknown"
|
||||
catalog_shape_id: str = ""
|
||||
provider_resolution: str = ""
|
||||
fallback: bool = False
|
||||
raw: Mapping[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
|
|
@ -80,22 +77,31 @@ class ModelCapabilityRecord:
|
|||
)
|
||||
|
||||
def to_dict(self, *, include_raw: bool = False) -> dict[str, Any]:
|
||||
controls = tuple(
|
||||
dict.fromkeys(
|
||||
control.control
|
||||
for control in self.deterministic_controls
|
||||
if control.control
|
||||
)
|
||||
)
|
||||
data = {
|
||||
"vendor": self.vendor,
|
||||
"model_id": self.model_id,
|
||||
"stable_model_id": self.stable_model_id,
|
||||
"display_name": self.display_name,
|
||||
"capability": self.capability.to_dict(),
|
||||
"capability_assertions": [assertion.to_dict() for assertion in self.capability_assertions],
|
||||
"deterministic_controls": [control.to_dict() for control in self.deterministic_controls],
|
||||
"reasoning_controls": [control.to_dict() for control in self.reasoning_controls],
|
||||
"model_family": self.model_family,
|
||||
"model_version": list(self.model_version),
|
||||
"provider_version": list(self.provider_version),
|
||||
"api_dialect": self.api_dialect,
|
||||
"provider_schema_id": self.provider_schema_id,
|
||||
"catalog_shape_id": self.catalog_shape_id,
|
||||
"provider_resolution": self.provider_resolution,
|
||||
"schema_version": CANONICAL_MODEL_SHAPE_VERSION,
|
||||
"provider": self.vendor,
|
||||
"model": self.model_id,
|
||||
"stable_id": self.stable_model_id,
|
||||
"family": self.capability.family,
|
||||
"task": self.capability.primary_task,
|
||||
"modalities": self.capability.modalities.to_dict(),
|
||||
"features": list(self.capability.capabilities),
|
||||
"limits": dict(self.capability.limits),
|
||||
"controls": list(controls),
|
||||
"evidence": {
|
||||
"source": self.capability.source,
|
||||
"confidence": self.capability.confidence,
|
||||
"provider_source": self.provider_source,
|
||||
"shape": self.catalog_shape_id,
|
||||
"fallback": self.fallback,
|
||||
},
|
||||
}
|
||||
if include_raw:
|
||||
data["raw"] = dict(self.raw)
|
||||
|
|
@ -314,5 +320,4 @@ def detect_vendor(base_url: Any = "", endpoint_kind: Any = "") -> str:
|
|||
)
|
||||
if resolution.provider_id != pcs.PROVIDER_UNKNOWN:
|
||||
return resolution.provider_id
|
||||
parsed = urlparse(compact_str(base_url))
|
||||
return VENDOR_GENERIC_OPENAI if parsed.hostname else VENDOR_UNKNOWN
|
||||
return VENDOR_UNKNOWN
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ def record_from_model(
|
|||
source=mc.SOURCE_PROVIDER_READER,
|
||||
confidence=mc.CONFIDENCE_UNKNOWN,
|
||||
),
|
||||
model_family=compact_str(raw.get("family")),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ def record_from_model(
|
|||
),
|
||||
display_name=compact_str(raw.get("name")) or model_id,
|
||||
capability=capability,
|
||||
model_family=compact_str(raw.get("family")),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
"""General structural reader for OpenAI-compatible model-list payloads.
|
||||
"""Inventory-only reader for unrecognized model-list envelopes.
|
||||
|
||||
Identity-only model cards remain unknown. Rich records are promoted only from
|
||||
recognized explicit fields (modalities, task/type, capability booleans,
|
||||
supported parameters, and numeric limits). Names and descriptions are never
|
||||
parsed for capability hints.
|
||||
Common field names are not a cross-provider capability contract. This reader
|
||||
therefore recovers model identity and preserves the original record, but never
|
||||
promotes tasks, modalities, parameters, limits, or capability booleans.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -15,18 +14,9 @@ from src import model_capabilities as mc
|
|||
from src.model_capability_readers.base import (
|
||||
ModelCapabilityRecord,
|
||||
VENDOR_GENERIC_OPENAI,
|
||||
as_list,
|
||||
as_mapping,
|
||||
build_capability,
|
||||
compact_str,
|
||||
deterministic_controls_from_supported_parameters,
|
||||
family_from_modalities,
|
||||
int_limit,
|
||||
merge_unique,
|
||||
model_id_from,
|
||||
modalities_from_value,
|
||||
openai_model_items,
|
||||
split_modality_arrow,
|
||||
stable_model_id_for,
|
||||
)
|
||||
|
||||
|
|
@ -34,191 +24,6 @@ from src.model_capability_readers.base import (
|
|||
vendor = VENDOR_GENERIC_OPENAI
|
||||
|
||||
|
||||
_TYPE_FAMILIES = {
|
||||
"llm": mc.FAMILY_CHAT,
|
||||
"chat": mc.FAMILY_CHAT,
|
||||
"chat_completion": mc.FAMILY_CHAT,
|
||||
"text_generation": mc.FAMILY_CHAT,
|
||||
"causal_lm": mc.FAMILY_CHAT,
|
||||
"image_text_to_text": mc.FAMILY_CHAT,
|
||||
"image_question_answering": mc.FAMILY_CHAT,
|
||||
"embedding": mc.FAMILY_EMBEDDING,
|
||||
"embeddings": mc.FAMILY_EMBEDDING,
|
||||
"text_embedding": mc.FAMILY_EMBEDDING,
|
||||
"feature_extraction": mc.FAMILY_EMBEDDING,
|
||||
"text_to_image": mc.FAMILY_IMAGE,
|
||||
"image_to_image": mc.FAMILY_IMAGE,
|
||||
"text_to_video": mc.FAMILY_VIDEO,
|
||||
"automatic_speech_recognition": mc.FAMILY_AUDIO,
|
||||
"text_to_speech": mc.FAMILY_AUDIO,
|
||||
"rerank": mc.FAMILY_RERANK,
|
||||
"reranking": mc.FAMILY_RERANK,
|
||||
"classification": mc.FAMILY_CLASSIFICATION,
|
||||
"text_classification": mc.FAMILY_CLASSIFICATION,
|
||||
"moderation": mc.FAMILY_MODERATION,
|
||||
}
|
||||
|
||||
_PARAMETER_CAPABILITIES = {
|
||||
"tools": mc.CAP_TOOL_CALL,
|
||||
"tool_choice": mc.CAP_TOOL_CALL,
|
||||
"parallel_tool_calls": mc.CAP_TOOL_CALL,
|
||||
"function_calling": mc.CAP_TOOL_CALL,
|
||||
"response_format": mc.CAP_JSON_MODE,
|
||||
"structured_output": mc.CAP_STRUCTURED_OUTPUT,
|
||||
"structured_outputs": mc.CAP_STRUCTURED_OUTPUT,
|
||||
"json_schema": mc.CAP_STRUCTURED_OUTPUT,
|
||||
"reasoning": mc.CAP_REASONING,
|
||||
"reasoning_effort": mc.CAP_REASONING,
|
||||
"include_reasoning": mc.CAP_REASONING,
|
||||
"web_search": mc.CAP_WEB_SEARCH,
|
||||
"web_search_options": mc.CAP_WEB_SEARCH,
|
||||
}
|
||||
|
||||
|
||||
def _shape_token(value: Any) -> str:
|
||||
return compact_str(value).lower().replace("-", "_").replace(" ", "_")
|
||||
|
||||
|
||||
def _family_from_explicit_fields(raw: Mapping[str, Any]) -> str:
|
||||
for key in ("type", "model_type", "task", "pipeline_tag"):
|
||||
family = _TYPE_FAMILIES.get(_shape_token(raw.get(key)))
|
||||
if family:
|
||||
return family
|
||||
return mc.FAMILY_UNKNOWN
|
||||
|
||||
|
||||
def _modalities(raw: Mapping[str, Any]) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
||||
architecture = as_mapping(raw.get("architecture"))
|
||||
input_modalities = modalities_from_value(
|
||||
raw.get("input_modalities") or architecture.get("input_modalities")
|
||||
)
|
||||
output_modalities = modalities_from_value(
|
||||
raw.get("output_modalities") or architecture.get("output_modalities")
|
||||
)
|
||||
if not input_modalities or not output_modalities:
|
||||
arrow_input, arrow_output = split_modality_arrow(
|
||||
raw.get("modality") or architecture.get("modality")
|
||||
)
|
||||
input_modalities = input_modalities or arrow_input
|
||||
output_modalities = output_modalities or arrow_output
|
||||
return input_modalities, output_modalities
|
||||
|
||||
|
||||
def _capabilities_from_modalities(
|
||||
input_modalities: tuple[str, ...],
|
||||
output_modalities: tuple[str, ...],
|
||||
) -> tuple[str, ...]:
|
||||
input_set = set(input_modalities)
|
||||
output_set = set(output_modalities)
|
||||
out: list[str] = []
|
||||
if mc.MODALITY_IMAGE in input_set and mc.MODALITY_TEXT in output_set:
|
||||
out.append(mc.CAP_VISION)
|
||||
if mc.MODALITY_FILE in input_set:
|
||||
out.append(mc.CAP_FILES)
|
||||
if mc.MODALITY_PDF in input_set:
|
||||
out.append(mc.CAP_PDF)
|
||||
if mc.MODALITY_AUDIO in input_set:
|
||||
out.append(mc.CAP_AUDIO_INPUT)
|
||||
if mc.MODALITY_AUDIO in output_set:
|
||||
out.append(mc.CAP_AUDIO_OUTPUT)
|
||||
if mc.MODALITY_IMAGE in output_set:
|
||||
out.append(mc.CAP_IMAGE_GENERATION)
|
||||
if mc.MODALITY_IMAGE in input_set:
|
||||
out.append(mc.CAP_IMAGE_EDITING)
|
||||
if mc.MODALITY_VIDEO in output_set:
|
||||
out.append(mc.CAP_VIDEO_GENERATION)
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def _explicit_capabilities(raw: Mapping[str, Any]) -> tuple[str, ...]:
|
||||
values: list[Any] = []
|
||||
payload = raw.get("capabilities")
|
||||
if isinstance(payload, Mapping):
|
||||
supports = payload.get("supports")
|
||||
if isinstance(supports, Mapping):
|
||||
values.extend(key for key, enabled in supports.items() if enabled is True)
|
||||
values.extend(key for key, enabled in payload.items() if enabled is True)
|
||||
elif isinstance(payload, (list, tuple)):
|
||||
values.extend(payload)
|
||||
|
||||
out: list[str] = []
|
||||
for value in values:
|
||||
cap = mc.normalize_capability(value)
|
||||
if cap and cap not in out:
|
||||
out.append(cap)
|
||||
for value in as_list(raw.get("supported_parameters")):
|
||||
cap = _PARAMETER_CAPABILITIES.get(_shape_token(value))
|
||||
if cap and cap not in out:
|
||||
out.append(cap)
|
||||
task = next(
|
||||
(_shape_token(raw.get(key)) for key in ("type", "model_type", "task", "pipeline_tag") if raw.get(key)),
|
||||
"",
|
||||
)
|
||||
task_capability = {
|
||||
"automatic_speech_recognition": mc.CAP_TRANSCRIPTION,
|
||||
"text_to_speech": mc.CAP_TTS,
|
||||
"text_to_image": mc.CAP_IMAGE_GENERATION,
|
||||
"image_to_image": mc.CAP_IMAGE_EDITING,
|
||||
"text_to_video": mc.CAP_VIDEO_GENERATION,
|
||||
"image_text_to_text": mc.CAP_VISION,
|
||||
"image_question_answering": mc.CAP_VISION,
|
||||
}.get(task)
|
||||
if task_capability and task_capability not in out:
|
||||
out.append(task_capability)
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def _limits(raw: Mapping[str, Any]) -> dict[str, int]:
|
||||
architecture = as_mapping(raw.get("architecture"))
|
||||
top_provider = as_mapping(raw.get("top_provider"))
|
||||
limits: dict[str, int] = {}
|
||||
for keys, target in (
|
||||
(("context_length", "max_context_length", "max_model_len"), "context_tokens"),
|
||||
(("input_token_limit", "inputTokenLimit"), "input_tokens"),
|
||||
(("output_token_limit", "outputTokenLimit", "max_completion_tokens"), "output_tokens"),
|
||||
):
|
||||
for key in keys:
|
||||
value = int_limit(raw.get(key)) or int_limit(architecture.get(key)) or int_limit(top_provider.get(key))
|
||||
if value:
|
||||
limits[target] = value
|
||||
break
|
||||
return limits
|
||||
|
||||
|
||||
def _default_modalities(
|
||||
family: str,
|
||||
raw: Mapping[str, Any],
|
||||
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
||||
task = next(
|
||||
(_shape_token(raw.get(key)) for key in ("type", "model_type", "task", "pipeline_tag") if raw.get(key)),
|
||||
"",
|
||||
)
|
||||
task_modalities = {
|
||||
"automatic_speech_recognition": ((mc.MODALITY_AUDIO,), (mc.MODALITY_TEXT,)),
|
||||
"text_to_speech": ((mc.MODALITY_TEXT,), (mc.MODALITY_AUDIO,)),
|
||||
"text_to_image": ((mc.MODALITY_TEXT,), (mc.MODALITY_IMAGE,)),
|
||||
"image_to_image": ((mc.MODALITY_IMAGE,), (mc.MODALITY_IMAGE,)),
|
||||
"text_to_video": ((mc.MODALITY_TEXT,), (mc.MODALITY_VIDEO,)),
|
||||
"image_text_to_text": ((mc.MODALITY_TEXT, mc.MODALITY_IMAGE), (mc.MODALITY_TEXT,)),
|
||||
"image_question_answering": ((mc.MODALITY_TEXT, mc.MODALITY_IMAGE), (mc.MODALITY_TEXT,)),
|
||||
}.get(task)
|
||||
if task_modalities:
|
||||
return task_modalities
|
||||
if family == mc.FAMILY_CHAT:
|
||||
return (mc.MODALITY_TEXT,), (mc.MODALITY_TEXT,)
|
||||
if family == mc.FAMILY_EMBEDDING:
|
||||
return (mc.MODALITY_TEXT,), (mc.MODALITY_EMBEDDING,)
|
||||
if family == mc.FAMILY_IMAGE:
|
||||
return (mc.MODALITY_TEXT,), (mc.MODALITY_IMAGE,)
|
||||
if family == mc.FAMILY_VIDEO:
|
||||
return (mc.MODALITY_TEXT,), (mc.MODALITY_VIDEO,)
|
||||
if family == mc.FAMILY_AUDIO:
|
||||
return (), ()
|
||||
if family in {mc.FAMILY_RERANK, mc.FAMILY_CLASSIFICATION, mc.FAMILY_MODERATION}:
|
||||
return (mc.MODALITY_TEXT,), (mc.MODALITY_TEXT,)
|
||||
return (), ()
|
||||
|
||||
|
||||
def record_from_model(
|
||||
raw: Mapping[str, Any],
|
||||
*,
|
||||
|
|
@ -226,44 +31,28 @@ def record_from_model(
|
|||
endpoint_id: Any = "",
|
||||
base_url: Any = "",
|
||||
) -> ModelCapabilityRecord | None:
|
||||
model_id = model_id_from(raw, "id", "name", "model")
|
||||
model_id = model_id_from(raw, "id", "name", "model", "key", "slug")
|
||||
if not model_id:
|
||||
return None
|
||||
|
||||
family = _family_from_explicit_fields(raw)
|
||||
input_modalities, output_modalities = _modalities(raw)
|
||||
if family == mc.FAMILY_UNKNOWN:
|
||||
family = family_from_modalities(input_modalities, output_modalities)
|
||||
if family != mc.FAMILY_UNKNOWN and not input_modalities and not output_modalities:
|
||||
input_modalities, output_modalities = _default_modalities(family, raw)
|
||||
capabilities = merge_unique(
|
||||
_explicit_capabilities(raw),
|
||||
_capabilities_from_modalities(input_modalities, output_modalities),
|
||||
)
|
||||
limits = _limits(raw)
|
||||
if family == mc.FAMILY_UNKNOWN and not capabilities and not limits:
|
||||
capability = mc.unknown_capability(
|
||||
source=mc.SOURCE_PROVIDER_READER,
|
||||
confidence=mc.CONFIDENCE_UNKNOWN,
|
||||
)
|
||||
else:
|
||||
capability = build_capability(
|
||||
family=family,
|
||||
input_modalities=input_modalities,
|
||||
output_modalities=output_modalities,
|
||||
capabilities=capabilities,
|
||||
limits=limits,
|
||||
)
|
||||
return ModelCapabilityRecord(
|
||||
vendor=vendor_id,
|
||||
model_id=model_id,
|
||||
stable_model_id=stable_model_id_for(vendor_id, model_id, endpoint_id=endpoint_id, base_url=base_url),
|
||||
display_name=compact_str(raw.get("display_name") or raw.get("name")),
|
||||
capability=capability,
|
||||
deterministic_controls=deterministic_controls_from_supported_parameters(
|
||||
raw.get("supported_parameters")
|
||||
stable_model_id=stable_model_id_for(
|
||||
vendor_id,
|
||||
model_id,
|
||||
endpoint_id=endpoint_id,
|
||||
base_url=base_url,
|
||||
),
|
||||
display_name=compact_str(
|
||||
raw.get("display_name")
|
||||
or raw.get("name")
|
||||
or raw.get("key")
|
||||
or raw.get("slug")
|
||||
),
|
||||
capability=mc.unknown_capability(
|
||||
source=mc.SOURCE_PROVIDER_READER,
|
||||
confidence=mc.CONFIDENCE_UNKNOWN,
|
||||
),
|
||||
model_family=compact_str(raw.get("root") or raw.get("model_family")),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
|
@ -277,7 +66,12 @@ def records_from_payload(
|
|||
) -> tuple[ModelCapabilityRecord, ...]:
|
||||
records: list[ModelCapabilityRecord] = []
|
||||
for item in openai_model_items(payload):
|
||||
record = record_from_model(item, vendor_id=vendor_id, endpoint_id=endpoint_id, base_url=base_url)
|
||||
record = record_from_model(
|
||||
item,
|
||||
vendor_id=vendor_id,
|
||||
endpoint_id=endpoint_id,
|
||||
base_url=base_url,
|
||||
)
|
||||
if record:
|
||||
records.append(record)
|
||||
return tuple(records)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ def record_from_model(
|
|||
display_name=compact_str(raw.get("displayName")) or model_id,
|
||||
capability=ai_studio.capability_from_model(raw),
|
||||
deterministic_controls=ai_studio.deterministic_controls_from_model(raw),
|
||||
model_family=compact_str(raw.get("baseModelId")),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ from collections.abc import Mapping
|
|||
from typing import Any
|
||||
|
||||
from src import model_capabilities as mc
|
||||
from src.model_capability_readers import generic_openai
|
||||
from src.model_capability_readers.base import (
|
||||
ModelCapabilityRecord,
|
||||
VENDOR_HUGGINGFACE,
|
||||
as_mapping,
|
||||
build_capability,
|
||||
compact_str,
|
||||
openai_model_items,
|
||||
stable_model_id_for,
|
||||
|
|
@ -20,6 +19,86 @@ from src.model_capability_readers.base import (
|
|||
vendor = VENDOR_HUGGINGFACE
|
||||
|
||||
|
||||
# Hugging Face publishes ``pipeline_tag`` as a provider-owned task enum. Keep
|
||||
# its interpretation here, rather than teaching the inventory fallback that a
|
||||
# similarly named field has the same meaning for every provider.
|
||||
_PIPELINE_SHAPES = {
|
||||
"text-generation": (mc.FAMILY_CHAT, (mc.MODALITY_TEXT,), (mc.MODALITY_TEXT,), ()),
|
||||
"image-text-to-text": (
|
||||
mc.FAMILY_CHAT,
|
||||
(mc.MODALITY_TEXT, mc.MODALITY_IMAGE),
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.CAP_VISION,),
|
||||
),
|
||||
"image-question-answering": (
|
||||
mc.FAMILY_CHAT,
|
||||
(mc.MODALITY_TEXT, mc.MODALITY_IMAGE),
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.CAP_VISION,),
|
||||
),
|
||||
"feature-extraction": (
|
||||
mc.FAMILY_EMBEDDING,
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.MODALITY_EMBEDDING,),
|
||||
(),
|
||||
),
|
||||
"text-to-image": (
|
||||
mc.FAMILY_IMAGE,
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.MODALITY_IMAGE,),
|
||||
(mc.CAP_IMAGE_GENERATION,),
|
||||
),
|
||||
"image-to-image": (
|
||||
mc.FAMILY_IMAGE,
|
||||
(mc.MODALITY_IMAGE,),
|
||||
(mc.MODALITY_IMAGE,),
|
||||
(mc.CAP_IMAGE_GENERATION, mc.CAP_IMAGE_EDITING),
|
||||
),
|
||||
"text-to-video": (
|
||||
mc.FAMILY_VIDEO,
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.MODALITY_VIDEO,),
|
||||
(mc.CAP_VIDEO_GENERATION,),
|
||||
),
|
||||
"automatic-speech-recognition": (
|
||||
mc.FAMILY_AUDIO,
|
||||
(mc.MODALITY_AUDIO,),
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.CAP_TRANSCRIPTION,),
|
||||
),
|
||||
"text-to-speech": (
|
||||
mc.FAMILY_AUDIO,
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.MODALITY_AUDIO,),
|
||||
(mc.CAP_TTS,),
|
||||
),
|
||||
"text-classification": (
|
||||
mc.FAMILY_CLASSIFICATION,
|
||||
(mc.MODALITY_TEXT,),
|
||||
(mc.MODALITY_TEXT,),
|
||||
(),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _capability_from_pipeline_tag(value: Any) -> mc.ModelCapability:
|
||||
shape = _PIPELINE_SHAPES.get(compact_str(value).lower())
|
||||
if not shape:
|
||||
return mc.unknown_capability(
|
||||
source=mc.SOURCE_COOKBOOK_HF,
|
||||
confidence=mc.CONFIDENCE_UNKNOWN,
|
||||
)
|
||||
family, input_modalities, output_modalities, capabilities = shape
|
||||
return build_capability(
|
||||
family=family,
|
||||
input_modalities=input_modalities,
|
||||
output_modalities=output_modalities,
|
||||
capabilities=capabilities,
|
||||
source=mc.SOURCE_COOKBOOK_HF,
|
||||
confidence=mc.CONFIDENCE_REGISTRY,
|
||||
)
|
||||
|
||||
|
||||
def record_from_model(
|
||||
raw: Mapping[str, Any],
|
||||
*,
|
||||
|
|
@ -29,25 +108,6 @@ def record_from_model(
|
|||
model_id = compact_str(raw.get("modelId") or raw.get("id"))
|
||||
if not model_id:
|
||||
return None
|
||||
structural = generic_openai.record_from_model(
|
||||
{**raw, "id": model_id},
|
||||
vendor_id=VENDOR_HUGGINGFACE,
|
||||
endpoint_id=endpoint_id,
|
||||
base_url=base_url,
|
||||
)
|
||||
if not structural:
|
||||
return None
|
||||
capability = mc.ModelCapability.build(
|
||||
family=structural.capability.family,
|
||||
primary_task=structural.capability.primary_task,
|
||||
input_modalities=structural.capability.modalities.input,
|
||||
output_modalities=structural.capability.modalities.output,
|
||||
capabilities=structural.capability.capabilities,
|
||||
limits=dict(structural.capability.limits),
|
||||
source=mc.SOURCE_COOKBOOK_HF,
|
||||
confidence=mc.CONFIDENCE_REGISTRY,
|
||||
)
|
||||
config = as_mapping(raw.get("config"))
|
||||
return ModelCapabilityRecord(
|
||||
vendor=VENDOR_HUGGINGFACE,
|
||||
model_id=model_id,
|
||||
|
|
@ -65,9 +125,7 @@ def record_from_model(
|
|||
)
|
||||
or model_id
|
||||
),
|
||||
capability=capability,
|
||||
deterministic_controls=structural.deterministic_controls,
|
||||
model_family=compact_str(config.get("model_type")),
|
||||
capability=_capability_from_pipeline_tag(raw.get("pipeline_tag")),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -160,7 +160,6 @@ def record_from_native_model(
|
|||
),
|
||||
display_name=compact_str(raw.get("display_name") or raw.get("name")) or model_id,
|
||||
capability=capability,
|
||||
model_family=compact_str(raw.get("architecture") or raw.get("arch")),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ def record_from_model(
|
|||
),
|
||||
display_name=compact_str(raw.get("name")) or model_id,
|
||||
capability=capability,
|
||||
model_family=compact_str(raw.get("root")),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ def record_from_show_payload(
|
|||
stable_model_id=stable_model_id_for(VENDOR_OLLAMA, model_id, endpoint_id=endpoint_id, base_url=base_url),
|
||||
display_name=model_id,
|
||||
capability=capability,
|
||||
model_family=compact_str(as_mapping(payload.get("details")).get("family")),
|
||||
raw=payload,
|
||||
)
|
||||
|
||||
|
|
@ -175,7 +174,6 @@ def records_from_tags_payload(
|
|||
source=mc.SOURCE_PROVIDER_READER,
|
||||
confidence=mc.CONFIDENCE_UNKNOWN,
|
||||
),
|
||||
model_family=compact_str(as_mapping(item.get("details")).get("family")),
|
||||
raw=item,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ def record_from_model_info(
|
|||
display_name=model_id,
|
||||
capability=capability,
|
||||
deterministic_controls=deterministic_controls_from_supported_parameters(sampling.keys()),
|
||||
model_family=compact_str(payload.get("model_type")),
|
||||
raw=payload,
|
||||
)
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,7 @@ from src.model_capability_readers.base import (
|
|||
VENDOR_OLLAMA,
|
||||
VENDOR_OPENAI,
|
||||
VENDOR_OPENROUTER,
|
||||
VENDOR_UNKNOWN,
|
||||
detect_vendor,
|
||||
stable_model_id_for,
|
||||
)
|
||||
|
|
@ -24,10 +25,10 @@ def test_detect_vendor_uses_endpoint_kind_and_host_but_not_ambiguous_local_ports
|
|||
assert detect_vendor("https://openrouter.ai/api/v1") == VENDOR_OPENROUTER
|
||||
assert detect_vendor("https://api.openai.com/v1") == VENDOR_OPENAI
|
||||
assert detect_vendor("https://generativelanguage.googleapis.com/v1beta/openai") == VENDOR_GOOGLE
|
||||
assert detect_vendor("http://127.0.0.1:11434") == VENDOR_GENERIC_OPENAI
|
||||
assert detect_vendor("http://127.0.0.1:1234") == VENDOR_GENERIC_OPENAI
|
||||
assert detect_vendor("http://127.0.0.1:8080") == VENDOR_GENERIC_OPENAI
|
||||
assert detect_vendor("http://localhost:7000/v1") == VENDOR_GENERIC_OPENAI
|
||||
assert detect_vendor("http://127.0.0.1:11434") == VENDOR_UNKNOWN
|
||||
assert detect_vendor("http://127.0.0.1:1234") == VENDOR_UNKNOWN
|
||||
assert detect_vendor("http://127.0.0.1:8080") == VENDOR_UNKNOWN
|
||||
assert detect_vendor("http://localhost:7000/v1") == VENDOR_UNKNOWN
|
||||
|
||||
|
||||
def test_generic_openai_reader_keeps_basic_model_payload_unknown():
|
||||
|
|
@ -362,7 +363,6 @@ def test_ollama_reader_maps_show_capabilities_and_tags_are_unknown():
|
|||
|
||||
assert len(tags) == 1
|
||||
assert tags[0].capability.family == mc.FAMILY_UNKNOWN
|
||||
assert tags[0].model_family == "qwen3"
|
||||
assert surfaces(tags[0]) == set()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from src import model_behavior_quirks as quirks
|
||||
from src import model_capabilities as mc
|
||||
from src import provider_capability_schemas as pcs
|
||||
from src.model_capability_readers import (
|
||||
CANONICAL_MODEL_SHAPE_VERSION,
|
||||
anthropic,
|
||||
chatgpt_subscription,
|
||||
cohere,
|
||||
|
|
@ -14,7 +14,7 @@ from src.model_capability_readers import (
|
|||
)
|
||||
|
||||
|
||||
def test_provider_resolution_order_explicit_then_host_then_native_then_general():
|
||||
def test_provider_identity_and_catalog_shape_are_resolved_separately():
|
||||
google_payload = {
|
||||
"models": [
|
||||
{
|
||||
|
|
@ -27,21 +27,39 @@ def test_provider_resolution_order_explicit_then_host_then_native_then_general()
|
|||
explicit = pcs.resolve_provider(google_payload, provider="openrouter")
|
||||
host = pcs.resolve_provider(google_payload, base_url="https://api.mistral.ai/v1")
|
||||
native = pcs.resolve_provider(google_payload)
|
||||
general = pcs.resolve_provider([{"id": "future-model", "future": {"x": True}}])
|
||||
fallback = pcs.resolve_provider([{"id": "future-model", "future": {"x": True}}])
|
||||
unknown = pcs.resolve_provider({"future": [{"not_an_identity": True}]})
|
||||
|
||||
assert (explicit.provider_id, explicit.stage) == ("openrouter", pcs.RESOLUTION_EXPLICIT)
|
||||
assert (host.provider_id, host.stage) == ("mistral", pcs.RESOLUTION_HOST)
|
||||
assert (native.provider_id, native.stage) == ("google", pcs.RESOLUTION_NATIVE_SHAPE)
|
||||
assert native.catalog_shape.shape_id == "google.generative-language.models.v1beta"
|
||||
assert (general.provider_id, general.stage) == (
|
||||
pcs.PROVIDER_GENERIC_OPENAI,
|
||||
pcs.RESOLUTION_GENERAL_SHAPE,
|
||||
)
|
||||
assert (unknown.provider_id, unknown.stage) == (
|
||||
pcs.PROVIDER_UNKNOWN,
|
||||
pcs.RESOLUTION_UNKNOWN,
|
||||
)
|
||||
assert explicit.to_dict() == {
|
||||
"provider": "openrouter",
|
||||
"provider_source": pcs.PROVIDER_SOURCE_EXPLICIT,
|
||||
"shape": "fallback.models.envelope.v1",
|
||||
"fallback": True,
|
||||
}
|
||||
assert host.to_dict() == {
|
||||
"provider": "mistral",
|
||||
"provider_source": pcs.PROVIDER_SOURCE_HOST,
|
||||
"shape": "fallback.models.envelope.v1",
|
||||
"fallback": True,
|
||||
}
|
||||
assert native.to_dict() == {
|
||||
"provider": "google",
|
||||
"provider_source": pcs.PROVIDER_SOURCE_PAYLOAD,
|
||||
"shape": "google.generative-language.models.v1beta",
|
||||
"fallback": False,
|
||||
}
|
||||
assert fallback.to_dict() == {
|
||||
"provider": pcs.PROVIDER_UNKNOWN,
|
||||
"provider_source": pcs.PROVIDER_SOURCE_UNKNOWN,
|
||||
"shape": "fallback.models.list.v1",
|
||||
"fallback": True,
|
||||
}
|
||||
assert unknown.to_dict() == {
|
||||
"provider": pcs.PROVIDER_UNKNOWN,
|
||||
"provider_source": pcs.PROVIDER_SOURCE_UNKNOWN,
|
||||
"shape": "",
|
||||
"fallback": False,
|
||||
}
|
||||
|
||||
|
||||
def test_provider_host_matching_rejects_lookalikes_and_does_not_use_ports():
|
||||
|
|
@ -53,31 +71,57 @@ def test_provider_host_matching_rejects_lookalikes_and_does_not_use_ports():
|
|||
assert pcs.provider_from_host("http://127.0.0.1:30000") == pcs.PROVIDER_UNKNOWN
|
||||
|
||||
|
||||
def test_provider_aliases_collapse_runtime_names_without_url_path_guessing():
|
||||
def test_provider_aliases_only_normalize_explicit_identity():
|
||||
assert pcs.normalize_provider_id("opencode-go") == "opencode"
|
||||
assert pcs.normalize_provider_id("opencode-zen") == "opencode"
|
||||
assert pcs.normalize_provider_id("nvidia-nim") == "nvidia"
|
||||
assert pcs.normalize_provider_id("tgi") == "text_generation_inference"
|
||||
assert pcs.normalize_provider_id("llama.cpp") == "llamacpp"
|
||||
assert pcs.normalize_provider_id("Z.AI") == "zai"
|
||||
assert pcs.normalize_provider_id("future-provider") == "future_provider"
|
||||
|
||||
|
||||
def test_current_native_catalog_shapes_are_discriminating_and_versioned():
|
||||
def test_unregistered_explicit_provider_is_preserved_but_stays_on_fallback():
|
||||
resolution = pcs.resolve_provider(
|
||||
{"data": [{"id": "future-model", "capabilities": {"tools": True}}]},
|
||||
provider="future-provider",
|
||||
)
|
||||
records = records_from_payload(
|
||||
{"data": [{"id": "future-model", "capabilities": {"tools": True}}]},
|
||||
vendor="future-provider",
|
||||
)
|
||||
|
||||
assert resolution.to_dict() == {
|
||||
"provider": "future_provider",
|
||||
"provider_source": pcs.PROVIDER_SOURCE_EXPLICIT,
|
||||
"shape": "fallback.models.data.v1",
|
||||
"fallback": True,
|
||||
}
|
||||
assert records[0].vendor == "future_provider"
|
||||
assert records[0].capability.family == mc.FAMILY_UNKNOWN
|
||||
assert records[0].capability.capabilities == ()
|
||||
|
||||
|
||||
def test_current_native_catalog_shapes_are_discriminating():
|
||||
cases = (
|
||||
(
|
||||
{"models": [{"key": "local/model", "type": "llm", "capabilities": {"vision": True}}]},
|
||||
"lmstudio",
|
||||
"lmstudio.models.native.v1",
|
||||
),
|
||||
(
|
||||
{"data": [{"id": "legacy", "type": "vlm", "arch": "gemma"}]},
|
||||
"lmstudio",
|
||||
"lmstudio.models.native.v0",
|
||||
),
|
||||
(
|
||||
{"models": [{"name": "local", "digest": "abc", "details": {"family": "qwen3"}}]},
|
||||
"ollama",
|
||||
"ollama.tags.v1",
|
||||
),
|
||||
(
|
||||
{"capabilities": ["completion", "vision"], "model_info": {"x.context_length": 4096}},
|
||||
"ollama",
|
||||
"ollama.show.v1",
|
||||
),
|
||||
(
|
||||
|
|
@ -86,10 +130,12 @@ def test_current_native_catalog_shapes_are_discriminating_and_versioned():
|
|||
"default_generation_settings": {"n_ctx": 4096},
|
||||
"chat_template_caps": {"supports_tools": True},
|
||||
},
|
||||
"llamacpp",
|
||||
"llamacpp.props.v1",
|
||||
),
|
||||
(
|
||||
{"data": [{"id": "mistral", "capabilities": {"completion_chat": True, "vision": False}}]},
|
||||
"mistral",
|
||||
"mistral.models.rich.v1",
|
||||
),
|
||||
(
|
||||
|
|
@ -102,6 +148,7 @@ def test_current_native_catalog_shapes_are_discriminating_and_versioned():
|
|||
}
|
||||
]
|
||||
},
|
||||
"copilot",
|
||||
"github-copilot.models.v1",
|
||||
),
|
||||
(
|
||||
|
|
@ -111,6 +158,7 @@ def test_current_native_catalog_shapes_are_discriminating_and_versioned():
|
|||
"is_generation": True,
|
||||
"has_image_understanding": False,
|
||||
},
|
||||
"sglang",
|
||||
"sglang.model-info.v2",
|
||||
),
|
||||
(
|
||||
|
|
@ -127,46 +175,38 @@ def test_current_native_catalog_shapes_are_discriminating_and_versioned():
|
|||
}
|
||||
],
|
||||
},
|
||||
"vllm",
|
||||
"vllm.models.openai.v1",
|
||||
),
|
||||
(
|
||||
{"models": [{"slug": "gpt-example", "visibility": "list", "priority": 1}]},
|
||||
"chatgpt_subscription",
|
||||
"chatgpt-subscription.codex-models.v1",
|
||||
),
|
||||
(
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"name": "command-example",
|
||||
"endpoints": ["chat"],
|
||||
"context_length": 131072,
|
||||
}
|
||||
]
|
||||
},
|
||||
{"models": [{"name": "command-example", "endpoints": ["chat"], "context_length": 131072}]},
|
||||
"cohere",
|
||||
"cohere.models.rich.v1",
|
||||
),
|
||||
(
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "MiniMax-M2-example",
|
||||
"object": "model",
|
||||
"owned_by": "minimax",
|
||||
}
|
||||
],
|
||||
"data": [{"id": "MiniMax-M2", "object": "model", "owned_by": "minimax"}],
|
||||
},
|
||||
"minimax",
|
||||
"minimax.models.identity.v1",
|
||||
),
|
||||
)
|
||||
|
||||
for payload, expected_shape in cases:
|
||||
for payload, expected_provider, expected_shape in cases:
|
||||
resolution = pcs.resolve_provider(payload)
|
||||
assert resolution.stage == pcs.RESOLUTION_NATIVE_SHAPE
|
||||
assert resolution.catalog_shape.shape_id == expected_shape
|
||||
assert resolution.provider_id == expected_provider
|
||||
assert resolution.provider_source == pcs.PROVIDER_SOURCE_PAYLOAD
|
||||
assert resolution.shape_id == expected_shape
|
||||
assert resolution.fallback is False
|
||||
|
||||
|
||||
def test_native_shape_detection_rejects_wrong_field_types_before_general_fallback():
|
||||
def test_wrong_native_field_types_degrade_to_explicit_fallback_inventory():
|
||||
malformed_cohere = pcs.resolve_provider(
|
||||
{"models": [{"name": "future", "endpoints": "chat", "context_length": 4096}]}
|
||||
)
|
||||
|
|
@ -174,57 +214,69 @@ def test_native_shape_detection_rejects_wrong_field_types_before_general_fallbac
|
|||
{"data": [{"id": "future", "capabilities": ["completion_chat"]}]}
|
||||
)
|
||||
|
||||
assert (malformed_cohere.provider_id, malformed_cohere.stage) == (
|
||||
pcs.PROVIDER_GENERIC_OPENAI,
|
||||
pcs.RESOLUTION_GENERAL_SHAPE,
|
||||
)
|
||||
assert (malformed_mistral.provider_id, malformed_mistral.stage) == (
|
||||
pcs.PROVIDER_GENERIC_OPENAI,
|
||||
pcs.RESOLUTION_GENERAL_SHAPE,
|
||||
)
|
||||
assert malformed_cohere.provider_id == pcs.PROVIDER_UNKNOWN
|
||||
assert malformed_cohere.shape_id == "fallback.models.envelope.v1"
|
||||
assert malformed_cohere.fallback is True
|
||||
assert malformed_mistral.provider_id == pcs.PROVIDER_UNKNOWN
|
||||
assert malformed_mistral.shape_id == "fallback.models.data.v1"
|
||||
assert malformed_mistral.fallback is True
|
||||
|
||||
|
||||
def test_general_reader_promotes_only_explicit_structural_fields_and_accepts_bare_lists():
|
||||
records = generic_openai.records_from_payload(
|
||||
[
|
||||
{
|
||||
"id": "future-rich-model",
|
||||
"type": "chat",
|
||||
"architecture": {
|
||||
"input_modalities": ["text", "image"],
|
||||
"output_modalities": ["text"],
|
||||
},
|
||||
"supported_parameters": ["tools", "structured_outputs", "temperature"],
|
||||
"max_model_len": 131072,
|
||||
"future_capability": {"may_be_important_later": True},
|
||||
def test_fallback_reader_is_identity_only_even_for_dangerous_looking_fields():
|
||||
payload = [
|
||||
{
|
||||
"id": "future-rich-model",
|
||||
"type": "chat",
|
||||
"architecture": {
|
||||
"input_modalities": ["text", "image"],
|
||||
"output_modalities": ["text"],
|
||||
},
|
||||
{
|
||||
"id": "vision-reasoning-tools-in-the-name-only",
|
||||
"description": "Claims every capability in prose",
|
||||
"type": "image",
|
||||
"future_capability": True,
|
||||
},
|
||||
]
|
||||
)
|
||||
"capabilities": {"supports": {"tools": True, "reasoning": True}},
|
||||
"supported_parameters": ["tools", "structured_outputs", "temperature"],
|
||||
"max_model_len": 131072,
|
||||
},
|
||||
{"key": "key-only-model", "pipeline_tag": "text-to-image"},
|
||||
{"slug": "slug-only-model", "modality": "text_to_image"},
|
||||
]
|
||||
|
||||
rich, identity_only = records
|
||||
assert rich.capability.family == mc.FAMILY_CHAT
|
||||
assert rich.capability.modalities.input == (mc.MODALITY_TEXT, mc.MODALITY_IMAGE)
|
||||
assert rich.capability.capabilities == (
|
||||
mc.CAP_TOOL_CALL,
|
||||
mc.CAP_STRUCTURED_OUTPUT,
|
||||
mc.CAP_VISION,
|
||||
)
|
||||
assert dict(rich.capability.limits) == {"context_tokens": 131072}
|
||||
assert [control.control for control in rich.deterministic_controls] == [mc.CONTROL_TEMPERATURE]
|
||||
assert rich.raw["future_capability"] == {"may_be_important_later": True}
|
||||
direct = generic_openai.records_from_payload(payload)
|
||||
wrapped = records_from_payload(payload, vendor="together")
|
||||
|
||||
assert identity_only.capability.family == mc.FAMILY_UNKNOWN
|
||||
assert identity_only.capability.capabilities == ()
|
||||
assert identity_only.raw["future_capability"] is True
|
||||
assert [record.model_id for record in direct] == [
|
||||
"future-rich-model",
|
||||
"key-only-model",
|
||||
"slug-only-model",
|
||||
]
|
||||
for record in (*direct, *wrapped):
|
||||
assert record.capability.family == mc.FAMILY_UNKNOWN
|
||||
assert record.capability.capabilities == ()
|
||||
assert dict(record.capability.limits) == {}
|
||||
assert record.deterministic_controls == ()
|
||||
|
||||
lean = wrapped[0].to_dict()
|
||||
assert lean == {
|
||||
"schema_version": CANONICAL_MODEL_SHAPE_VERSION,
|
||||
"provider": "together",
|
||||
"model": "future-rich-model",
|
||||
"stable_id": "together|global|future-rich-model",
|
||||
"family": "unknown",
|
||||
"task": "unknown",
|
||||
"modalities": {"input": [], "output": []},
|
||||
"features": [],
|
||||
"limits": {},
|
||||
"controls": [],
|
||||
"evidence": {
|
||||
"source": "provider_reader",
|
||||
"confidence": "unknown",
|
||||
"provider_source": "explicit",
|
||||
"shape": "fallback.models.list.v1",
|
||||
"fallback": True,
|
||||
},
|
||||
}
|
||||
assert wrapped[0].to_dict(include_raw=True)["raw"] == payload[0]
|
||||
|
||||
|
||||
def test_general_reader_fails_soft_for_null_and_malformed_envelopes():
|
||||
def test_fallback_reader_fails_soft_for_null_and_malformed_envelopes():
|
||||
for payload in (
|
||||
{"data": None},
|
||||
{"models": None},
|
||||
|
|
@ -235,13 +287,12 @@ def test_general_reader_fails_soft_for_null_and_malformed_envelopes():
|
|||
assert generic_openai.records_from_payload(payload) == ()
|
||||
|
||||
|
||||
def test_mistral_reader_maps_per_model_capabilities_without_provider_wide_inheritance():
|
||||
def test_mistral_reader_maps_per_model_capabilities_without_provider_inheritance():
|
||||
records = mistral.records_from_payload(
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "vision-chat",
|
||||
"root": "mistral-small",
|
||||
"capabilities": {
|
||||
"completion_chat": True,
|
||||
"function_calling": True,
|
||||
|
|
@ -258,10 +309,7 @@ def test_mistral_reader_maps_per_model_capabilities_without_provider_wide_inheri
|
|||
"vision": False,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "future-card",
|
||||
"capabilities": {"future_only": True},
|
||||
},
|
||||
{"id": "future-card", "capabilities": {"future_only": True}},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
|
@ -270,14 +318,12 @@ def test_mistral_reader_maps_per_model_capabilities_without_provider_wide_inheri
|
|||
assert records[0].capability.modalities.input == (mc.MODALITY_TEXT, mc.MODALITY_IMAGE)
|
||||
assert records[0].capability.capabilities == (mc.CAP_VISION, mc.CAP_TOOL_CALL)
|
||||
assert dict(records[0].capability.limits) == {"context_tokens": 32768}
|
||||
assert records[0].model_family == "mistral-small"
|
||||
assert records[1].capability.family == mc.FAMILY_CLASSIFICATION
|
||||
assert records[2].capability.family == mc.FAMILY_UNKNOWN
|
||||
assert records[2].capability.capabilities == ()
|
||||
|
||||
|
||||
def test_copilot_reader_uses_picker_and_nested_supports_shape():
|
||||
records = copilot.records_from_payload(
|
||||
record = copilot.records_from_payload(
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
|
|
@ -285,23 +331,17 @@ def test_copilot_reader_uses_picker_and_nested_supports_shape():
|
|||
"model_picker_enabled": True,
|
||||
"capabilities": {"supports": {"tool_calls": True, "vision": True}},
|
||||
"limits": {"max_prompt_tokens": 64000, "max_output_tokens": 8192},
|
||||
},
|
||||
{
|
||||
"id": "utility-model",
|
||||
"model_picker_enabled": False,
|
||||
"capabilities": {"supports": {}},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
)[0]
|
||||
|
||||
assert records[0].capability.family == mc.FAMILY_CHAT
|
||||
assert records[0].capability.capabilities == (mc.CAP_TOOL_CALL, mc.CAP_VISION)
|
||||
assert dict(records[0].capability.limits) == {"input_tokens": 64000, "output_tokens": 8192}
|
||||
assert records[1].capability.family == mc.FAMILY_UNKNOWN
|
||||
assert record.capability.family == mc.FAMILY_CHAT
|
||||
assert record.capability.capabilities == (mc.CAP_TOOL_CALL, mc.CAP_VISION)
|
||||
assert dict(record.capability.limits) == {"input_tokens": 64000, "output_tokens": 8192}
|
||||
|
||||
|
||||
def test_sglang_model_info_is_structural_and_non_generation_stays_unknown():
|
||||
def test_sglang_model_info_maps_native_generation_flags_only():
|
||||
generation = sglang.records_from_payload(
|
||||
{
|
||||
"model_path": "org/vision-model",
|
||||
|
|
@ -309,7 +349,6 @@ def test_sglang_model_info_is_structural_and_non_generation_stays_unknown():
|
|||
"is_generation": True,
|
||||
"has_image_understanding": True,
|
||||
"has_audio_understanding": True,
|
||||
"model_type": "future_arch",
|
||||
"preferred_sampling_params": {"temperature": 0.2, "top_p": 0.9},
|
||||
}
|
||||
)[0]
|
||||
|
|
@ -333,11 +372,10 @@ def test_sglang_model_info_is_structural_and_non_generation_stays_unknown():
|
|||
mc.CONTROL_TEMPERATURE,
|
||||
mc.CONTROL_TOP_P,
|
||||
]
|
||||
assert generation.model_family == "future_arch"
|
||||
assert pooling.capability.family == mc.FAMILY_UNKNOWN
|
||||
|
||||
|
||||
def test_identity_only_catalogs_do_not_claim_model_capability():
|
||||
def test_identity_only_native_catalogs_remain_unknown():
|
||||
anthropic_record = anthropic.records_from_payload(
|
||||
{
|
||||
"data": [
|
||||
|
|
@ -356,13 +394,7 @@ def test_identity_only_catalogs_do_not_claim_model_capability():
|
|||
minimax_record = records_from_payload(
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "MiniMax-M2-example",
|
||||
"object": "model",
|
||||
"owned_by": "minimax",
|
||||
}
|
||||
],
|
||||
"data": [{"id": "MiniMax-M2", "object": "model", "owned_by": "minimax"}],
|
||||
}
|
||||
)[0]
|
||||
|
||||
|
|
@ -372,7 +404,7 @@ def test_identity_only_catalogs_do_not_claim_model_capability():
|
|||
assert minimax_record.capability.family == mc.FAMILY_UNKNOWN
|
||||
|
||||
|
||||
def test_huggingface_reader_maps_pipeline_tag_as_registry_evidence():
|
||||
def test_huggingface_reader_maps_provider_specific_pipeline_metadata():
|
||||
record = huggingface.records_from_payload(
|
||||
{
|
||||
"modelId": "org/vision-model",
|
||||
|
|
@ -387,7 +419,6 @@ def test_huggingface_reader_maps_pipeline_tag_as_registry_evidence():
|
|||
assert record.capability.capabilities == (mc.CAP_VISION,)
|
||||
assert record.capability.source == mc.SOURCE_COOKBOOK_HF
|
||||
assert record.capability.confidence == mc.CONFIDENCE_REGISTRY
|
||||
assert record.model_family == "future_vlm"
|
||||
|
||||
|
||||
def test_cohere_reader_maps_only_native_endpoint_and_limit_fields():
|
||||
|
|
@ -411,7 +442,6 @@ def test_cohere_reader_maps_only_native_endpoint_and_limit_fields():
|
|||
)
|
||||
|
||||
assert chat.capability.family == mc.FAMILY_CHAT
|
||||
assert chat.capability.modalities.input == (mc.MODALITY_TEXT,)
|
||||
assert dict(chat.capability.limits) == {"context_tokens": 131072}
|
||||
assert [control.control for control in chat.deterministic_controls] == [
|
||||
mc.CONTROL_TEMPERATURE,
|
||||
|
|
@ -422,8 +452,8 @@ def test_cohere_reader_maps_only_native_endpoint_and_limit_fields():
|
|||
assert ambiguous.capability.family == mc.FAMILY_UNKNOWN
|
||||
|
||||
|
||||
def test_registry_wrapper_records_resolution_and_preserves_compatible_provider_identity():
|
||||
mistral_records = records_from_payload(
|
||||
def test_reader_wrapper_adds_one_lean_evidence_object():
|
||||
record = records_from_payload(
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
|
|
@ -432,74 +462,20 @@ def test_registry_wrapper_records_resolution_and_preserves_compatible_provider_i
|
|||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
together_records = records_from_payload(
|
||||
[{"id": "served/model", "type": "chat", "supported_parameters": ["tools"]}],
|
||||
vendor="together",
|
||||
)
|
||||
)[0]
|
||||
serialized = record.to_dict()
|
||||
|
||||
assert mistral_records[0].vendor == "mistral"
|
||||
assert mistral_records[0].provider_schema_id == "mistral"
|
||||
assert mistral_records[0].catalog_shape_id == "mistral.models.rich.v1"
|
||||
assert mistral_records[0].provider_resolution == pcs.RESOLUTION_NATIVE_SHAPE
|
||||
|
||||
assert together_records[0].vendor == "together"
|
||||
assert together_records[0].capability.family == mc.FAMILY_CHAT
|
||||
assert together_records[0].provider_schema_id == "together"
|
||||
assert together_records[0].provider_resolution == pcs.RESOLUTION_EXPLICIT
|
||||
|
||||
|
||||
def test_reasoning_control_preserves_canonical_and_native_values():
|
||||
control = mc.ReasoningControl.build(
|
||||
mechanism="reasoning_effort",
|
||||
values=("enabled", "disabled"),
|
||||
native_values=("high", "medium", "low", "none"),
|
||||
request_path="reasoning_effort",
|
||||
response_paths=("choices[].delta.reasoning",),
|
||||
status="claimed",
|
||||
source="provider_docs_registry",
|
||||
confidence="registry",
|
||||
)
|
||||
|
||||
assert control.values == (mc.REASONING_CONTROL_VALUE_ON, mc.REASONING_CONTROL_VALUE_OFF)
|
||||
assert control.native_values == ("high", "medium", "low", "none")
|
||||
assert mc.ReasoningControl.from_dict(control.to_dict()) == control
|
||||
|
||||
|
||||
def test_model_quirks_require_structured_exact_identity_not_name_parsing():
|
||||
matching = quirks.matching_quirks(
|
||||
provider="moonshot",
|
||||
model_id="kimi-k2.5",
|
||||
model_family="",
|
||||
api_dialect=pcs.DIALECT_OPENAI_CHAT,
|
||||
capabilities=(mc.CAP_REASONING,),
|
||||
)
|
||||
lookalike = quirks.matching_quirks(
|
||||
provider="moonshot",
|
||||
model_id="proxy/kimi-k2.5-lookalike",
|
||||
model_family="",
|
||||
api_dialect=pcs.DIALECT_OPENAI_CHAT,
|
||||
capabilities=(mc.CAP_REASONING,),
|
||||
)
|
||||
opus_without_version = quirks.matching_quirks(
|
||||
provider="anthropic",
|
||||
model_family="claude-opus",
|
||||
model_id="claude-opus-4-8-in-name-only",
|
||||
api_dialect=pcs.DIALECT_ANTHROPIC_MESSAGES,
|
||||
)
|
||||
opus_structured = quirks.matching_quirks(
|
||||
provider="anthropic",
|
||||
model_family="claude-opus",
|
||||
model_version=(4, 8),
|
||||
api_dialect=pcs.DIALECT_ANTHROPIC_MESSAGES,
|
||||
)
|
||||
|
||||
assert {quirk.quirk_id for quirk in matching} == {
|
||||
"moonshot.kimi-k2.5-k2.6.provider-fixed-temperature",
|
||||
"moonshot.kimi-k2.5-k2.6.tool-history-reasoning-content",
|
||||
assert record.vendor == "mistral"
|
||||
assert serialized["schema_version"] == 1
|
||||
assert serialized["provider"] == "mistral"
|
||||
assert serialized["features"] == [mc.CAP_TOOL_CALL]
|
||||
assert serialized["evidence"] == {
|
||||
"source": mc.SOURCE_PROVIDER_READER,
|
||||
"confidence": mc.CONFIDENCE_PROVIDER_REPORTED,
|
||||
"provider_source": pcs.PROVIDER_SOURCE_PAYLOAD,
|
||||
"shape": "mistral.models.rich.v1",
|
||||
"fallback": False,
|
||||
}
|
||||
assert lookalike == ()
|
||||
assert opus_without_version == ()
|
||||
assert [quirk.quirk_id for quirk in opus_structured] == [
|
||||
"anthropic.claude-opus-4.7-plus.omit-sampling-controls"
|
||||
]
|
||||
assert "capability" not in serialized
|
||||
assert "capability_assertions" not in serialized
|
||||
assert "deterministic_controls" not in serialized
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue