CoolFace
Apppublic

TReqs/hf-aibom-scorer

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes
bom_builder.py170 linesDownload Raw Back to src
1"""HF Parser — converts Hugging Face model metadata into a partial CycloneDX BOM.2 3Conservative by design: unknown values stay absent rather than guessed.4Mirrors external-bom-builder.ts from the design doc. In particular,5`formulation`, `dependencies`, and the per-task lineage fields are never6populated here — no pipeline was observed, only a description of a result.7"""8 9from __future__ import annotations10 11import copy12import uuid13from datetime import datetime, timezone14from typing import Any15 16from src.hf_client import HfModelMetadata17 18TOOL_NAME = "GLaaS External Scorer (demo)"19TOOL_VERSION = "0.1.0"20MANUFACTURER_NAME = "GLaaS"21 22 23def _extract_description(readme_text: str | None) -> str | None:24    if not readme_text:25        return None26    text = readme_text27    if text.startswith("---"):28        end = text.find("---", 3)29        if end != -1:30            text = text[end + 3:]31    for paragraph in text.split("\n\n"):32        stripped = paragraph.strip().lstrip("#").strip()33        if stripped and not stripped.startswith("!["):34            return stripped[:300]35    return None36 37 38def _primary_weight_file(siblings: list[dict[str, Any]]) -> dict[str, Any] | None:39    return siblings[0] if siblings else None40 41 42def _license_ids(card_data: dict[str, Any]) -> list[str]:43    license_value = card_data.get("license")44    if not license_value:45        return []46    if isinstance(license_value, list):47        return [str(v) for v in license_value]48    return [str(license_value)]49 50 51def _package_url(model_id: str, sha: str) -> str:52    # No official purl-spec type exists for Hugging Face; "huggingface" here53    # mirrors the same ad-hoc convention the OWASP AIBOM Generator uses54    # (pkg:huggingface/{org}/{name}@{commit}), not a real IANA/purl-spec type.55    ref = f"@{sha[:12]}" if sha else ""56    return f"pkg:huggingface/{model_id}{ref}"57 58 59def build_partial_bom(metadata: HfModelMetadata) -> dict[str, Any]:60    now_iso = datetime.now(timezone.utc).isoformat()61    model_id = metadata.model_id62 63    hashes = []64    for sibling in metadata.siblings:65        lfs = sibling.get("lfs") or {}66        sha256 = lfs.get("sha256")67        if sha256:68            hashes.append({"alg": "SHA-256", "content": sha256})69 70    properties: list[dict[str, str]] = [71        {"name": "glaas:git:commit", "value": metadata.sha},72        {"name": "glaas:git:repo", "value": f"https://huggingface.co/{model_id}"},73        {"name": "glaas:git:branch", "value": "main"},74    ]75    if metadata.pipeline_tag:76        properties.append({"name": "glaas:label:pipeline_tag", "value": metadata.pipeline_tag})77    if metadata.library_name:78        properties.append({"name": "glaas:label:library_name", "value": metadata.library_name})79    for tag in metadata.tags[:20]:80        properties.append({"name": "glaas:label:tag", "value": tag})81 82    external_references = [83        {"type": "vcs", "url": f"https://huggingface.co/{model_id}"},84        {"type": "documentation", "url": f"https://huggingface.co/{model_id}"},85    ]86    primary_weight = _primary_weight_file(metadata.siblings)87    if primary_weight:88        filename = primary_weight.get("rfilename")89        if filename:90            external_references.append({91                "type": "distribution",92                "url": f"https://huggingface.co/{model_id}/resolve/main/{filename}",93            })94 95    component: dict[str, Any] = {96        "type": "machine-learning-model",97        "name": model_id,98        "version": metadata.sha,99        "purl": _package_url(model_id, metadata.sha),100        "properties": properties,101        "externalReferences": external_references,102    }103    if hashes:104        component["hashes"] = hashes105    description = _extract_description(metadata.readme_text)106    if description:107        component["description"] = description108    license_ids = _license_ids(metadata.card_data)109    if license_ids:110        component["licenses"] = [{"license": {"id": lic}} for lic in license_ids]111 112    metadata_block: dict[str, Any] = {113        "timestamp": now_iso,114        "tools": {"components": [{"type": "application", "name": TOOL_NAME, "version": TOOL_VERSION}]},115        "manufacturer": {"name": MANUFACTURER_NAME},116        "component": {"type": "machine-learning-model", "name": model_id},117    }118    if metadata.namespace:119        metadata_block["supplier"] = {"name": metadata.namespace}120 121    bom: dict[str, Any] = {122        "bomFormat": "CycloneDX",123        "specVersion": "1.6",124        "serialNumber": f"urn:uuid:{uuid.uuid4()}",125        "version": 1,126        "metadata": metadata_block,127        "components": [component],128        # Deliberately absent: "dependencies", "formulation" — not observable129        # from Hugging Face metadata alone (see design doc's field table).130    }131    return bom132 133 134def build_illustrative_roar_bom(bom: dict[str, Any]) -> dict[str, Any]:135    """Layer a synthetic roar-instrumented pipeline onto an already-scored BOM.136 137    Illustrates what GLaaS + roar would have captured automatically during138    training — a typical two-task pipeline (prepare_data -> train_model),139    NOT a measurement of the entered model's actual training process (no140    pipeline was observed for it). Mirrors the "illustrative projection"141    approach in design-docs/20260709 HF-ai-bom-space.md: same scorer, same142    component, only a synthetic formulation/dependencies layered on top —143    every caller must show this alongside the real score, clearly labeled144    as illustrative, never as a claim about the entered model.145    """146    illustrative = copy.deepcopy(bom)147    illustrative["formulation"] = [{148        "bom-ref": "formulation-1",149        "workflows": [{150            "bom-ref": "workflow-1",151            "tasks": [152                {153                    "bom-ref": "task-prepare-data",154                    "name": "prepare_data",155                    "outputs": [{"source": {"ref": "artifact-training-data"}}],156                },157                {158                    "bom-ref": "task-train-model",159                    "name": "train_model",160                    "inputs": [{"source": {"ref": "artifact-training-data"}}],161                    "outputs": [{"source": {"ref": "component-model"}}],162                },163            ],164        }],165    }]166    illustrative["dependencies"] = [167        {"ref": "task-train-model", "dependsOn": ["task-prepare-data"]},168    ]169    return illustrative170