Skip to content

Commit 95b2bb3

Browse files
committed
feat: Add training data transparency flags with HF Hub verification
1 parent 14eb00b commit 95b2bb3

2 files changed

Lines changed: 154 additions & 1 deletion

File tree

src/models/service.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,14 +777,39 @@ def _create_model_card_section(self, metadata: Dict[str, Any]) -> Dict[str, Any]
777777
props.append({"name": "genai:aibom:modelcard:quantizationFileType", "value": str(q_dict["file_type"])})
778778
taxonomy_mapped_keys.append("quantization")
779779

780+
# Training Data Completeness Check
781+
has_training_data = self._verify_datasets_available(metadata)
782+
props.append({"name": "genai:aibom:trainingDataAvailable", "value": "true" if has_training_data else "false"})
783+
784+
# Add status note about dataset verification
785+
if has_training_data:
786+
props.append({
787+
"name": "genai:aibom:trainingDataStatus",
788+
"value": "Training datasets verified: Dataset(s) exist and are accessible on Hugging Face Hub."
789+
})
790+
else:
791+
# Dataset referenced but not found/verified
792+
if "datasets" in metadata and metadata.get("datasets"):
793+
props.append({
794+
"name": "genai:aibom:trainingDataWarning",
795+
"value": "Training datasets were referenced but could not be verified on Hugging Face Hub. Dataset may not exist, be disabled, or be inaccessible."
796+
})
797+
else:
798+
# No dataset info at all
799+
props.append({
800+
"name": "genai:aibom:trainingDataWarning",
801+
"value": "Training data information is missing or not documented. This limits transparency and auditability of the model."
802+
})
803+
780804
# Basic Fields we've already mapped to structured homes
781805
mapped_fields = [
782806
"primaryPurpose", "typeOfModel", "suppliedBy", "intendedUse",
783807
"technicalLimitations", "ethicalConsiderations", "datasets", "eval_results",
784808
"pipeline_tag", "name", "author", "license", "description",
785809
"commit", "bomFormat", "specVersion", "version", "licenses",
786810
"external_references", "tags", "library_name", "paper", "downloadLocation",
787-
"gguf_filename", "gguf_license", "model_type", "architectures"
811+
"gguf_filename", "gguf_license", "model_type", "architectures",
812+
"trainingDataAvailable", "trainingDataWarning"
788813
] + taxonomy_mapped_keys
789814

790815
for k, v in metadata.items():
@@ -835,3 +860,28 @@ def _infer_io_formats(self, task: str) -> tuple:
835860
return (["csv", "json"], ["string", "number"])
836861

837862
return ([], [])
863+
864+
def _verify_datasets_available(self, metadata: Dict[str, Any]) -> bool:
865+
"""Verify if training datasets exist on Hugging Face Hub."""
866+
datasets = metadata.get("datasets")
867+
if not datasets:
868+
return False
869+
870+
# Normalize to list
871+
if isinstance(datasets, str):
872+
datasets = [datasets]
873+
elif isinstance(datasets, dict):
874+
datasets = [datasets.get("name", "")]
875+
876+
# Filter out empty/placeholder values
877+
valid = [d for d in datasets if isinstance(d, str) and d.strip() and d.lower() != "unknown"]
878+
879+
return any(self._verify_dataset_exists_on_hf(d) for d in valid) if valid else False
880+
881+
def _verify_dataset_exists_on_hf(self, dataset_id: str) -> bool:
882+
"""Check if dataset exists and is accessible on HF Hub."""
883+
try:
884+
info = self.hf_api.dataset_info(repo_id=dataset_id)
885+
return info is not None and not getattr(info, 'disabled', False)
886+
except Exception:
887+
return False

tests/test_service.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,5 +227,108 @@ def test_custom_name_with_known_url_includes_url(self):
227227
self.assertIn("nvidia.com", lic["url"])
228228

229229

230+
@patch("src.models.service.calculate_completeness_score")
231+
@patch("src.models.service.EnhancedExtractor")
232+
def test_training_data_flag_with_datasets(self, mock_extractor_cls, mock_score):
233+
"""Test that trainingDataAvailable flag is set to true when datasets are present"""
234+
# Setup
235+
mock_extractor = mock_extractor_cls.return_value
236+
metadata_with_data = {
237+
"name": "test-model",
238+
"datasets": ["dataset1", "dataset2"],
239+
"commit": "123456"
240+
}
241+
mock_extractor.extract_metadata.return_value = metadata_with_data
242+
mock_extractor.extraction_results = {}
243+
mock_score.return_value = {"total_score": 50}
244+
245+
self.service.hf_api.model_info.return_value = MagicMock(sha="123456")
246+
247+
# Mock dataset verification
248+
with patch.object(self.service, '_verify_dataset_exists_on_hf', return_value=True):
249+
# Action
250+
aibom = self.service.generate_aibom("owner/model")
251+
252+
# Verify
253+
model_card = aibom["components"][0].get("modelCard", {})
254+
properties = model_card.get("properties", [])
255+
256+
# Find the trainingDataAvailable property
257+
training_flag = next((p for p in properties if p["name"] == "genai:aibom:trainingDataAvailable"), None)
258+
self.assertIsNotNone(training_flag)
259+
self.assertEqual(training_flag["value"], "true")
260+
261+
# Verify no warning
262+
warning = next((p for p in properties if p["name"] == "genai:aibom:trainingDataWarning"), None)
263+
self.assertIsNone(warning)
264+
265+
@patch("src.models.service.calculate_completeness_score")
266+
@patch("src.models.service.EnhancedExtractor")
267+
def test_training_data_flag_without_datasets(self, mock_extractor_cls, mock_score):
268+
"""Test that trainingDataAvailable flag is set to false and warning is added when datasets are missing"""
269+
# Setup
270+
mock_extractor = mock_extractor_cls.return_value
271+
metadata_no_data = {
272+
"name": "test-model",
273+
"commit": "123456"
274+
# No datasets key
275+
}
276+
mock_extractor.extract_metadata.return_value = metadata_no_data
277+
mock_extractor.extraction_results = {}
278+
mock_score.return_value = {"total_score": 50}
279+
280+
self.service.hf_api.model_info.return_value = MagicMock(sha="123456")
281+
282+
# Action
283+
aibom = self.service.generate_aibom("owner/model")
284+
285+
# Verify
286+
model_card = aibom["components"][0].get("modelCard", {})
287+
properties = model_card.get("properties", [])
288+
289+
# Find the trainingDataAvailable property
290+
training_flag = next((p for p in properties if p["name"] == "genai:aibom:trainingDataAvailable"), None)
291+
self.assertIsNotNone(training_flag)
292+
self.assertEqual(training_flag["value"], "false")
293+
294+
# Verify warning is present
295+
warning = next((p for p in properties if p["name"] == "genai:aibom:trainingDataWarning"), None)
296+
self.assertIsNotNone(warning)
297+
self.assertIn("Training data information is missing", warning["value"])
298+
299+
def test_verify_datasets_available_with_valid_datasets(self):
300+
"""Test dataset verification with valid datasets"""
301+
# Mock the HF API call
302+
with patch.object(self.service, '_verify_dataset_exists_on_hf', return_value=True):
303+
# List of valid datasets
304+
metadata = {"datasets": ["dataset1", "dataset2"]}
305+
self.assertTrue(self.service._verify_datasets_available(metadata))
306+
307+
# Single string dataset
308+
metadata = {"datasets": "valid_dataset"}
309+
self.assertTrue(self.service._verify_datasets_available(metadata))
310+
311+
# Dict format with name
312+
metadata = {"datasets": {"name": "my_dataset", "url": "https://example.com"}}
313+
self.assertTrue(self.service._verify_datasets_available(metadata))
314+
315+
def test_verify_datasets_available_with_empty_datasets(self):
316+
"""Test dataset verification with empty or invalid datasets"""
317+
# Empty list
318+
metadata = {"datasets": []}
319+
self.assertFalse(self.service._verify_datasets_available(metadata))
320+
321+
# List with empty strings
322+
metadata = {"datasets": ["", " ", ""]}
323+
self.assertFalse(self.service._verify_datasets_available(metadata))
324+
325+
# Unknown placeholder
326+
metadata = {"datasets": ["unknown"]}
327+
self.assertFalse(self.service._verify_datasets_available(metadata))
328+
329+
# No datasets key
330+
metadata = {"name": "test-model"}
331+
self.assertFalse(self.service._verify_datasets_available(metadata))
332+
230333
if __name__ == '__main__':
231334
unittest.main()

0 commit comments

Comments
 (0)