From b91b0b3c3a018dfd5c8306a3a0380326f4b76f0b Mon Sep 17 00:00:00 2001 From: RaresKeY <158580472+RaresKeY@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:41:07 +0000 Subject: [PATCH] fix(models): preserve Hugging Face identity-only cards --- src/model_capability_readers/huggingface.py | 2 +- src/provider_capability_schemas.py | 8 ++-- tests/test_provider_capability_schemas.py | 42 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/model_capability_readers/huggingface.py b/src/model_capability_readers/huggingface.py index f6f5ec6fc..bdbfb45ad 100644 --- a/src/model_capability_readers/huggingface.py +++ b/src/model_capability_readers/huggingface.py @@ -136,7 +136,7 @@ def records_from_payload( endpoint_id: Any = "", base_url: Any = "", ) -> tuple[ModelCapabilityRecord, ...]: - if isinstance(payload, Mapping) and "pipeline_tag" in payload: + if isinstance(payload, Mapping): record = record_from_model(payload, endpoint_id=endpoint_id, base_url=base_url) return (record,) if record else () if not isinstance(payload, (list, tuple)): diff --git a/src/provider_capability_schemas.py b/src/provider_capability_schemas.py index 71b86162c..faffb917e 100644 --- a/src/provider_capability_schemas.py +++ b/src/provider_capability_schemas.py @@ -394,8 +394,10 @@ HUGGINGFACE_MODEL_SHAPE = ProviderCatalogShape( provider_id="huggingface", envelope=ENVELOPE_SINGLE, identity_paths=("modelId", "id"), - required_item_paths=("pipeline_tag",), - item_types=(("pipeline_tag", (str,)),), + # Hub ModelInfo exposes pipeline_tag as optional metadata. Provider/host + # context is still required because this shape has priority zero, so an + # identity-only card can stay native without making generic ``id`` payloads + # look like Hugging Face catalogs. detection_priority=0, ) HUGGINGFACE_MODELS_LIST_SHAPE = ProviderCatalogShape( @@ -403,8 +405,6 @@ HUGGINGFACE_MODELS_LIST_SHAPE = ProviderCatalogShape( provider_id="huggingface", envelope=ENVELOPE_BARE_LIST, identity_paths=("modelId", "id"), - required_item_paths=("pipeline_tag",), - item_types=(("pipeline_tag", (str,)),), detection_priority=0, ) COHERE_MODELS_SHAPE = ProviderCatalogShape( diff --git a/tests/test_provider_capability_schemas.py b/tests/test_provider_capability_schemas.py index bf814d678..228f5120f 100644 --- a/tests/test_provider_capability_schemas.py +++ b/tests/test_provider_capability_schemas.py @@ -1001,6 +1001,48 @@ def test_huggingface_reader_maps_provider_specific_pipeline_metadata(): assert record.capability.confidence == mc.CONFIDENCE_REGISTRY +def test_huggingface_optional_pipeline_tag_preserves_identity_only_records(): + cases = ( + ( + {"modelId": "org/no-pipeline-tag"}, + "huggingface.hub.model-info.v1", + ), + ( + {"modelId": "org/null-pipeline-tag", "pipeline_tag": None}, + "huggingface.hub.model-info.v1", + ), + ( + [{"modelId": "org/list-no-pipeline-tag"}], + "huggingface.hub.model-info-list.v1", + ), + ( + [{"modelId": "org/list-null-pipeline-tag", "pipeline_tag": None}], + "huggingface.hub.model-info-list.v1", + ), + ) + + for payload, shape_id in cases: + resolution = pcs.resolve_provider(payload, provider="huggingface") + direct = huggingface.records_from_payload(payload) + wrapped = records_from_payload(payload, vendor="huggingface") + + assert resolution.shape_id == shape_id + assert resolution.fallback is False + assert len(direct) == 1 + assert len(wrapped) == 1 + assert wrapped[0].model_id == direct[0].model_id + assert wrapped[0].capability.family == mc.FAMILY_UNKNOWN + assert wrapped[0].capability.capabilities == () + assert wrapped[0].catalog_shape_id == shape_id + assert wrapped[0].fallback is False + + # An identity-only singleton remains insufficient to infer Hugging Face + # without configured provider or host context. + unscoped = {"modelId": "org/unscoped"} + assert pcs.resolve_provider(unscoped).provider_id == pcs.PROVIDER_UNKNOWN + assert records_from_payload(unscoped) == () + + def test_cohere_reader_maps_only_native_endpoint_and_limit_fields(): chat, ambiguous = cohere.records_from_payload( {