Spaces:
Running
Running
Upload api.py
Browse files- src/aibom-generator/api.py +136 -151
src/aibom-generator/api.py
CHANGED
@@ -19,11 +19,28 @@ from starlette.middleware.base import BaseHTTPMiddleware
|
|
19 |
from huggingface_hub import HfApi
|
20 |
from huggingface_hub.utils import RepositoryNotFoundError # For specific error handling
|
21 |
|
22 |
-
|
23 |
# Configure logging
|
24 |
logging.basicConfig(level=logging.INFO)
|
25 |
logger = logging.getLogger(__name__)
|
26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
# Define directories and constants
|
28 |
templates_dir = "templates"
|
29 |
OUTPUT_DIR = "/tmp/aibom_output"
|
@@ -418,7 +435,7 @@ def import_utils():
|
|
418 |
|
419 |
# Try from src
|
420 |
try:
|
421 |
-
from src.aibom_generator.utils import calculate_completeness_score
|
422 |
logger.info("Imported src.aibom_generator.utils.calculate_completeness_score")
|
423 |
return calculate_completeness_score
|
424 |
except ImportError:
|
@@ -442,25 +459,60 @@ def import_utils():
|
|
442 |
# Try to import the calculate_completeness_score function
|
443 |
calculate_completeness_score = import_utils()
|
444 |
|
445 |
-
#
|
446 |
-
|
447 |
-
""
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
462 |
return {
|
463 |
-
"total_score": 0,
|
464 |
"section_scores": {
|
465 |
"required_fields": 0,
|
466 |
"metadata": 0,
|
@@ -476,141 +528,38 @@ def create_comprehensive_completeness_score(aibom=None):
|
|
476 |
"external_references": 10
|
477 |
},
|
478 |
"field_checklist": {
|
479 |
-
# Required fields
|
480 |
"bomFormat": "n/a ★★★",
|
481 |
"specVersion": "n/a ★★★",
|
482 |
"serialNumber": "n/a ★★★",
|
483 |
"version": "n/a ★★★",
|
484 |
-
"metadata.timestamp": "n/a ★★",
|
485 |
-
"metadata.tools": "n/a ★★",
|
486 |
-
"metadata.authors": "n/a ★★",
|
487 |
-
"metadata.component": "n/a ★★",
|
488 |
-
|
489 |
-
# Component basic info
|
490 |
-
"component.type": "n/a ★★",
|
491 |
-
"component.name": "n/a ★★★",
|
492 |
-
"component.bom-ref": "n/a ★★",
|
493 |
-
"component.purl": "n/a ★★",
|
494 |
-
"component.description": "n/a ★★",
|
495 |
-
"component.licenses": "n/a ★★",
|
496 |
-
|
497 |
-
# Model card
|
498 |
-
"modelCard.modelParameters": "n/a ★★",
|
499 |
-
"modelCard.quantitativeAnalysis": "n/a ★★",
|
500 |
-
"modelCard.considerations": "n/a ★★",
|
501 |
-
|
502 |
-
# External references
|
503 |
-
"externalReferences": "n/a ★",
|
504 |
-
|
505 |
-
# Additional fields from FIELD_CLASSIFICATION
|
506 |
"name": "n/a ★★★",
|
507 |
-
"downloadLocation": "n/a ★★★"
|
508 |
-
|
509 |
-
"suppliedBy": "n/a ★★★",
|
510 |
-
"energyConsumption": "n/a ★★",
|
511 |
-
"hyperparameter": "n/a ★★",
|
512 |
-
"limitation": "n/a ★★",
|
513 |
-
"safetyRiskAssessment": "n/a ★★",
|
514 |
-
"typeOfModel": "n/a ★★",
|
515 |
-
"modelExplainability": "n/a ★",
|
516 |
-
"standardCompliance": "n/a ★",
|
517 |
-
"domain": "n/a ★",
|
518 |
-
"energyQuantity": "n/a ★",
|
519 |
-
"energyUnit": "n/a ★",
|
520 |
-
"informationAboutTraining": "n/a ★",
|
521 |
-
"informationAboutApplication": "n/a ★",
|
522 |
-
"metric": "n/a ★",
|
523 |
-
"metricDecisionThreshold": "n/a ★",
|
524 |
-
"modelDataPreprocessing": "n/a ★",
|
525 |
-
"autonomyType": "n/a ★",
|
526 |
-
"useSensitivePersonalInformation": "n/a ★"
|
527 |
-
},
|
528 |
-
"field_tiers": {
|
529 |
-
# Required fields
|
530 |
-
"bomFormat": "critical",
|
531 |
-
"specVersion": "critical",
|
532 |
-
"serialNumber": "critical",
|
533 |
-
"version": "critical",
|
534 |
-
"metadata.timestamp": "important",
|
535 |
-
"metadata.tools": "important",
|
536 |
-
"metadata.authors": "important",
|
537 |
-
"metadata.component": "important",
|
538 |
-
|
539 |
-
# Component basic info
|
540 |
-
"component.type": "important",
|
541 |
-
"component.name": "critical",
|
542 |
-
"component.bom-ref": "important",
|
543 |
-
"component.purl": "important",
|
544 |
-
"component.description": "important",
|
545 |
-
"component.licenses": "important",
|
546 |
-
|
547 |
-
# Model card
|
548 |
-
"modelCard.modelParameters": "important",
|
549 |
-
"modelCard.quantitativeAnalysis": "important",
|
550 |
-
"modelCard.considerations": "important",
|
551 |
-
|
552 |
-
# External references
|
553 |
-
"externalReferences": "supplementary",
|
554 |
-
|
555 |
-
# Additional fields from FIELD_CLASSIFICATION
|
556 |
-
"name": "critical",
|
557 |
-
"downloadLocation": "critical",
|
558 |
-
"primaryPurpose": "critical",
|
559 |
-
"suppliedBy": "critical",
|
560 |
-
"energyConsumption": "important",
|
561 |
-
"hyperparameter": "important",
|
562 |
-
"limitation": "important",
|
563 |
-
"safetyRiskAssessment": "important",
|
564 |
-
"typeOfModel": "important",
|
565 |
-
"modelExplainability": "supplementary",
|
566 |
-
"standardCompliance": "supplementary",
|
567 |
-
"domain": "supplementary",
|
568 |
-
"energyQuantity": "supplementary",
|
569 |
-
"energyUnit": "supplementary",
|
570 |
-
"informationAboutTraining": "supplementary",
|
571 |
-
"informationAboutApplication": "supplementary",
|
572 |
-
"metric": "supplementary",
|
573 |
-
"metricDecisionThreshold": "supplementary",
|
574 |
-
"modelDataPreprocessing": "supplementary",
|
575 |
-
"autonomyType": "supplementary",
|
576 |
-
"useSensitivePersonalInformation": "supplementary"
|
577 |
-
},
|
578 |
-
"missing_fields": {
|
579 |
-
"critical": [],
|
580 |
-
"important": ["modelCard.quantitativeAnalysis", "energyConsumption", "safetyRiskAssessment"],
|
581 |
-
"supplementary": ["modelExplainability", "standardCompliance", "energyQuantity", "energyUnit",
|
582 |
-
"metric", "metricDecisionThreshold", "modelDataPreprocessing",
|
583 |
-
"autonomyType", "useSensitivePersonalInformation"]
|
584 |
-
},
|
585 |
-
"completeness_profile": {
|
586 |
-
"name": "standard",
|
587 |
-
"description": "Comprehensive fields for proper documentation",
|
588 |
-
"satisfied": True
|
589 |
-
},
|
590 |
-
"penalty_applied": False,
|
591 |
-
"penalty_reason": None,
|
592 |
-
"recommendations": [
|
593 |
-
{
|
594 |
-
"priority": "medium",
|
595 |
-
"field": "modelCard.quantitativeAnalysis",
|
596 |
-
"message": "Missing important field: modelCard.quantitativeAnalysis",
|
597 |
-
"recommendation": "Add quantitative analysis information to the model card"
|
598 |
-
},
|
599 |
-
{
|
600 |
-
"priority": "medium",
|
601 |
-
"field": "energyConsumption",
|
602 |
-
"message": "Missing important field: energyConsumption - helpful for environmental impact assessment",
|
603 |
-
"recommendation": "Consider documenting energy consumption metrics for better transparency"
|
604 |
-
},
|
605 |
-
{
|
606 |
-
"priority": "medium",
|
607 |
-
"field": "safetyRiskAssessment",
|
608 |
-
"message": "Missing important field: safetyRiskAssessment",
|
609 |
-
"recommendation": "Add safety risk assessment information to improve documentation"
|
610 |
-
}
|
611 |
-
]
|
612 |
}
|
613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
614 |
@app.post("/generate", response_class=HTMLResponse)
|
615 |
async def generate_form(
|
616 |
request: Request,
|
@@ -834,8 +783,13 @@ async def generate_form(
|
|
834 |
print(f" completeness_score keys: {list(completeness_score.keys())}")
|
835 |
if 'category_details' in completeness_score:
|
836 |
print(f" category_details exists: {list(completeness_score['category_details'].keys())}")
|
837 |
-
#
|
838 |
-
|
|
|
|
|
|
|
|
|
|
|
839 |
if category in completeness_score['category_details']:
|
840 |
details = completeness_score['category_details'][category]
|
841 |
print(f" {category}: present={details.get('present_fields')}, total={details.get('total_fields')}, percentage={details.get('percentage')}")
|
@@ -1022,7 +976,11 @@ async def api_generate_with_report(request: GenerateRequest):
|
|
1022 |
)
|
1023 |
|
1024 |
# Calculate completeness score
|
1025 |
-
|
|
|
|
|
|
|
|
|
1026 |
|
1027 |
# Round only section_scores that aren't already rounded
|
1028 |
for section, score in completeness_score["section_scores"].items():
|
@@ -1230,3 +1188,30 @@ if __name__ == "__main__":
|
|
1230 |
if not HF_TOKEN:
|
1231 |
print("Warning: HF_TOKEN environment variable not set. SBOM count will show N/A and logging will be skipped.")
|
1232 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
from huggingface_hub import HfApi
|
20 |
from huggingface_hub.utils import RepositoryNotFoundError # For specific error handling
|
21 |
|
|
|
22 |
# Configure logging
|
23 |
logging.basicConfig(level=logging.INFO)
|
24 |
logger = logging.getLogger(__name__)
|
25 |
|
26 |
+
# Registry-driven field classification imports
|
27 |
+
try:
|
28 |
+
from src.aibom_generator.field_registry_manager import (
|
29 |
+
get_field_registry_manager,
|
30 |
+
generate_field_classification,
|
31 |
+
get_configurable_scoring_weights
|
32 |
+
)
|
33 |
+
REGISTRY_MANAGER = get_field_registry_manager()
|
34 |
+
FIELD_CLASSIFICATION = generate_field_classification()
|
35 |
+
SCORING_WEIGHTS = get_configurable_scoring_weights()
|
36 |
+
REGISTRY_AVAILABLE = True
|
37 |
+
logger.info(f"✅ Registry-driven API: {len(FIELD_CLASSIFICATION)} fields loaded")
|
38 |
+
except ImportError as e:
|
39 |
+
REGISTRY_AVAILABLE = False
|
40 |
+
FIELD_CLASSIFICATION = {}
|
41 |
+
SCORING_WEIGHTS = {}
|
42 |
+
logger.warning(f"⚠️ Registry not available for API: {e}")
|
43 |
+
|
44 |
# Define directories and constants
|
45 |
templates_dir = "templates"
|
46 |
OUTPUT_DIR = "/tmp/aibom_output"
|
|
|
435 |
|
436 |
# Try from src
|
437 |
try:
|
438 |
+
from src.aibom_generator.utils import calculate_completeness_score
|
439 |
logger.info("Imported src.aibom_generator.utils.calculate_completeness_score")
|
440 |
return calculate_completeness_score
|
441 |
except ImportError:
|
|
|
459 |
# Try to import the calculate_completeness_score function
|
460 |
calculate_completeness_score = import_utils()
|
461 |
|
462 |
+
# Verify registry integration status
|
463 |
+
if REGISTRY_AVAILABLE:
|
464 |
+
logger.info("✅ API fully integrated with registry system")
|
465 |
+
else:
|
466 |
+
logger.warning("⚠️ API using fallback mode - registry not available")
|
467 |
+
|
468 |
+
|
469 |
+
def get_tier_points(tier):
|
470 |
+
"""Get points for a field tier."""
|
471 |
+
tier_points = {
|
472 |
+
"critical": 4.0,
|
473 |
+
"important": 2.0,
|
474 |
+
"supplementary": 1.0
|
475 |
+
}
|
476 |
+
return tier_points.get(tier, 1.0)
|
477 |
+
|
478 |
+
def create_registry_driven_fallback():
|
479 |
+
"""Create fallback score using registry configuration."""
|
480 |
+
if not REGISTRY_AVAILABLE:
|
481 |
+
return create_hardcoded_fallback()
|
482 |
+
|
483 |
+
categories = {}
|
484 |
+
field_checklist = {}
|
485 |
+
max_scores = {}
|
486 |
+
|
487 |
+
# Get categories and scores from registry
|
488 |
+
for field_name, classification in FIELD_CLASSIFICATION.items():
|
489 |
+
category = classification["category"]
|
490 |
+
tier = classification["tier"]
|
491 |
+
|
492 |
+
# Initialize category if not exists
|
493 |
+
if category not in categories:
|
494 |
+
categories[category] = {"total": 0, "present": 0}
|
495 |
+
max_scores[category] = 0
|
496 |
+
|
497 |
+
categories[category]["total"] += 1
|
498 |
+
max_scores[category] += get_tier_points(tier)
|
499 |
+
|
500 |
+
# Add to field checklist with registry-based tier
|
501 |
+
tier_stars = {"critical": "★★★", "important": "★★", "supplementary": "★"}
|
502 |
+
field_checklist[field_name] = f"n/a {tier_stars.get(tier, '★')}"
|
503 |
+
|
504 |
+
return {
|
505 |
+
"total_score": 0,
|
506 |
+
"section_scores": {cat: 0 for cat in categories.keys()},
|
507 |
+
"max_scores": max_scores,
|
508 |
+
"field_checklist": field_checklist,
|
509 |
+
"category_details": categories
|
510 |
+
}
|
511 |
+
|
512 |
+
def create_hardcoded_fallback():
|
513 |
+
"""Fallback to original hardcoded structure when registry unavailable."""
|
514 |
return {
|
515 |
+
"total_score": 0,
|
516 |
"section_scores": {
|
517 |
"required_fields": 0,
|
518 |
"metadata": 0,
|
|
|
528 |
"external_references": 10
|
529 |
},
|
530 |
"field_checklist": {
|
|
|
531 |
"bomFormat": "n/a ★★★",
|
532 |
"specVersion": "n/a ★★★",
|
533 |
"serialNumber": "n/a ★★★",
|
534 |
"version": "n/a ★★★",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
535 |
"name": "n/a ★★★",
|
536 |
+
"downloadLocation": "n/a ★★★"
|
537 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
538 |
}
|
539 |
|
540 |
+
# Helper function to create a comprehensive completeness_score with field_checklist
|
541 |
+
def create_comprehensive_completeness_score(aibom=None):
|
542 |
+
"""
|
543 |
+
Create a comprehensive completeness_score object with all required attributes.
|
544 |
+
Uses registry-driven field classification when available.
|
545 |
+
"""
|
546 |
+
# If we have the calculate_completeness_score function and an AIBOM, use it
|
547 |
+
if calculate_completeness_score and aibom:
|
548 |
+
try:
|
549 |
+
return calculate_completeness_score(aibom, validate=True, use_best_practices=True)
|
550 |
+
except Exception as e:
|
551 |
+
logger.error(f"Error calculating completeness score: {str(e)}")
|
552 |
+
# Fall through to registry-driven fallback
|
553 |
+
|
554 |
+
# Use registry-driven fallback
|
555 |
+
if REGISTRY_AVAILABLE:
|
556 |
+
logger.info("Using registry-driven completeness score fallback")
|
557 |
+
return create_registry_driven_fallback()
|
558 |
+
else:
|
559 |
+
logger.warning("Using hardcoded completeness score fallback")
|
560 |
+
return create_hardcoded_fallback()
|
561 |
+
|
562 |
+
|
563 |
@app.post("/generate", response_class=HTMLResponse)
|
564 |
async def generate_form(
|
565 |
request: Request,
|
|
|
783 |
print(f" completeness_score keys: {list(completeness_score.keys())}")
|
784 |
if 'category_details' in completeness_score:
|
785 |
print(f" category_details exists: {list(completeness_score['category_details'].keys())}")
|
786 |
+
# Use registry-driven categories when available
|
787 |
+
if REGISTRY_AVAILABLE:
|
788 |
+
categories = set(classification["category"] for classification in FIELD_CLASSIFICATION.values())
|
789 |
+
else:
|
790 |
+
categories = ['required_fields', 'metadata', 'component_basic', 'component_model_card', 'external_references']
|
791 |
+
|
792 |
+
for category in categories:
|
793 |
if category in completeness_score['category_details']:
|
794 |
details = completeness_score['category_details'][category]
|
795 |
print(f" {category}: present={details.get('present_fields')}, total={details.get('total_fields')}, percentage={details.get('percentage')}")
|
|
|
976 |
)
|
977 |
|
978 |
# Calculate completeness score
|
979 |
+
try:
|
980 |
+
completeness_score = calculate_completeness_score(aibom, validate=True, use_best_practices=True)
|
981 |
+
except Exception as e:
|
982 |
+
logger.error(f"Failed completeness scoring for {normalized_model_id}: {str(e)}")
|
983 |
+
raise HTTPException(status_code=500, detail=f"Error calculating score: {str(e)}")
|
984 |
|
985 |
# Round only section_scores that aren't already rounded
|
986 |
for section, score in completeness_score["section_scores"].items():
|
|
|
1188 |
if not HF_TOKEN:
|
1189 |
print("Warning: HF_TOKEN environment variable not set. SBOM count will show N/A and logging will be skipped.")
|
1190 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
1191 |
+
|
1192 |
+
|
1193 |
+
@app.get("/api/registry/status")
|
1194 |
+
async def get_registry_status():
|
1195 |
+
"""Get current registry configuration status for debugging."""
|
1196 |
+
if REGISTRY_AVAILABLE:
|
1197 |
+
categories = {}
|
1198 |
+
for field_name, classification in FIELD_CLASSIFICATION.items():
|
1199 |
+
category = classification["category"]
|
1200 |
+
if category not in categories:
|
1201 |
+
categories[category] = 0
|
1202 |
+
categories[category] += 1
|
1203 |
+
|
1204 |
+
return {
|
1205 |
+
"registry_available": True,
|
1206 |
+
"total_fields": len(FIELD_CLASSIFICATION),
|
1207 |
+
"categories": list(categories.keys()),
|
1208 |
+
"field_count_by_category": categories,
|
1209 |
+
"registry_manager_loaded": REGISTRY_MANAGER is not None
|
1210 |
+
}
|
1211 |
+
else:
|
1212 |
+
return {
|
1213 |
+
"registry_available": False,
|
1214 |
+
"fallback_mode": True,
|
1215 |
+
"message": "Using hardcoded field definitions",
|
1216 |
+
"total_fields": 6 # Hardcoded fallback count
|
1217 |
+
}
|