refactor(models): simplify provider capability catalog

This commit is contained in:
RaresKeY 2026-07-17 12:42:41 +00:00
parent 2eb5e4d9f5
commit 60cf5dd4aa
16 changed files with 520 additions and 1368 deletions

View file

@ -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",
]

View file

@ -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

View file

@ -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",

View file

@ -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

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)
)

View file

@ -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

View file

@ -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()

View file

@ -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