a1c00l commited on
Commit
4ccc7b2
·
verified ·
1 Parent(s): 4f363fe

Update src/aibom_generator/api.py

Browse files
Files changed (1) hide show
  1. src/aibom_generator/api.py +423 -416
src/aibom_generator/api.py CHANGED
@@ -1,416 +1,423 @@
1
- from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks, Query, File, UploadFile, Form
2
- from fastapi.responses import JSONResponse, FileResponse
3
- from fastapi.middleware.cors import CORSMiddleware
4
- from fastapi.staticfiles import StaticFiles
5
- from pydantic import BaseModel, Field
6
- from typing import Optional, Dict, Any, List
7
- import uvicorn
8
- import json
9
- import os
10
- import sys
11
- import uuid
12
- import shutil
13
- from datetime import datetime
14
-
15
- # Add parent directory to path to import generator module
16
- sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
-
18
- # Import the AIBOM generator
19
- try:
20
- from aibom_fix.final_generator import AIBOMGenerator
21
- except ImportError:
22
- # If not found, try the mapping directory
23
- try:
24
- from aibom_mapping.final_generator import AIBOMGenerator
25
- except ImportError:
26
- # If still not found, use the original generator
27
- try:
28
- from aibom_fix.generator import AIBOMGenerator
29
- except ImportError:
30
- try:
31
- from generator import AIBOMGenerator
32
- except ImportError:
33
- # Last resort: try to import from the aibom_generator module
34
- try:
35
- from aibom_generator.generator import AIBOMGenerator
36
- except ImportError:
37
- raise ImportError("Could not import AIBOMGenerator from any known location")
38
-
39
- # Create FastAPI app
40
- app = FastAPI(
41
- title="Aetheris AI SBOM Generator API",
42
- description="API for generating CycloneDX JSON AI SBOMs for machine learning models",
43
- version="1.0.0",
44
- )
45
-
46
- # Add CORS middleware
47
- app.add_middleware(
48
- CORSMiddleware,
49
- allow_origins=["*"], # Allow all origins in development
50
- allow_credentials=True,
51
- allow_methods=["*"],
52
- allow_headers=["*"],
53
- )
54
-
55
- # Create output directory for AIBOMs
56
- output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output")
57
- os.makedirs(output_dir, exist_ok=True)
58
- app.mount("/output", StaticFiles(directory=output_dir), name="output")
59
-
60
- # Create a global generator instance
61
- generator = AIBOMGenerator(use_best_practices=True)
62
-
63
- # Define request models
64
- class GenerateAIBOMRequest(BaseModel):
65
- model_id: str = Field(..., description="The Hugging Face model ID (e.g., 'meta-llama/Llama-4-Scout-17B-16E-Instruct')")
66
- hf_token: Optional[str] = Field(None, description="Optional Hugging Face API token for accessing private models")
67
- include_inference: Optional[bool] = Field(True, description="Whether to use AI inference to enhance the AIBOM")
68
- use_best_practices: Optional[bool] = Field(True, description="Whether to use industry best practices for scoring")
69
-
70
- class AIBOMResponse(BaseModel):
71
- aibom: Dict[str, Any] = Field(..., description="The generated AIBOM in CycloneDX JSON format")
72
- model_id: str = Field(..., description="The model ID for which the AIBOM was generated")
73
- generated_at: str = Field(..., description="Timestamp when the AIBOM was generated")
74
- request_id: str = Field(..., description="Unique ID for this request")
75
- download_url: Optional[str] = Field(None, description="URL to download the AIBOM JSON file")
76
-
77
- class EnhancementReport(BaseModel):
78
- ai_enhanced: bool = Field(..., description="Whether AI enhancement was applied")
79
- ai_model: Optional[str] = Field(None, description="The AI model used for enhancement, if any")
80
- original_score: Dict[str, Any] = Field(..., description="Original completeness score before enhancement")
81
- final_score: Dict[str, Any] = Field(..., description="Final completeness score after enhancement")
82
- improvement: float = Field(..., description="Score improvement from enhancement")
83
-
84
- class AIBOMWithReportResponse(AIBOMResponse):
85
- enhancement_report: Optional[EnhancementReport] = Field(None, description="Report on AI enhancement results")
86
-
87
- class StatusResponse(BaseModel):
88
- status: str = Field(..., description="API status")
89
- version: str = Field(..., description="API version")
90
- generator_version: str = Field(..., description="AIBOM generator version")
91
-
92
- class BatchGenerateRequest(BaseModel):
93
- model_ids: List[str] = Field(..., description="List of Hugging Face model IDs to generate AIBOMs for")
94
- hf_token: Optional[str] = Field(None, description="Optional Hugging Face API token for accessing private models")
95
- include_inference: Optional[bool] = Field(True, description="Whether to use AI inference to enhance the AIBOM")
96
- use_best_practices: Optional[bool] = Field(True, description="Whether to use industry best practices for scoring")
97
-
98
- class BatchJobResponse(BaseModel):
99
- job_id: str = Field(..., description="Unique ID for the batch job")
100
- status: str = Field(..., description="Job status (e.g., 'queued', 'processing', 'completed')")
101
- model_ids: List[str] = Field(..., description="List of model IDs in the batch")
102
- created_at: str = Field(..., description="Timestamp when the job was created")
103
-
104
- # In-memory storage for batch jobs
105
- batch_jobs = {}
106
-
107
- # Define API endpoints
108
- @app.get("/", response_model=StatusResponse)
109
- async def get_status():
110
- """Get the API status and version information."""
111
- return {
112
- "status": "operational",
113
- "version": "1.0.0",
114
- "generator_version": "0.1.0",
115
- }
116
-
117
- @app.post("/generate", response_model=AIBOMResponse)
118
- async def generate_aibom(request: GenerateAIBOMRequest):
119
- """
120
- Generate a CycloneDX JSON AI SBOM for a Hugging Face model.
121
-
122
- This endpoint takes a model ID and optional parameters to generate
123
- a comprehensive AI SBOM in CycloneDX format.
124
- """
125
- try:
126
- # Create a new generator instance with the provided token if available
127
- gen = AIBOMGenerator(
128
- hf_token=request.hf_token,
129
- use_inference=request.include_inference,
130
- use_best_practices=request.use_best_practices
131
- )
132
-
133
- # Generate a request ID
134
- request_id = str(uuid.uuid4())
135
-
136
- # Create output file path
137
- safe_model_id = request.model_id.replace("/", "_")
138
- output_file = os.path.join(output_dir, f"{safe_model_id}_{request_id}.json")
139
-
140
- # Generate the AIBOM
141
- aibom = gen.generate_aibom(
142
- model_id=request.model_id,
143
- include_inference=request.include_inference,
144
- use_best_practices=request.use_best_practices,
145
- output_file=output_file
146
- )
147
-
148
- # Create download URL
149
- download_url = f"/output/{os.path.basename(output_file)}"
150
-
151
- # Create response
152
- response = {
153
- "aibom": aibom,
154
- "model_id": request.model_id,
155
- "generated_at": datetime.utcnow().isoformat() + "Z",
156
- "request_id": request_id,
157
- "download_url": download_url
158
- }
159
-
160
- return response
161
- except Exception as e:
162
- raise HTTPException(status_code=500, detail=f"Error generating AIBOM: {str(e)}")
163
-
164
- @app.post("/generate-with-report", response_model=AIBOMWithReportResponse)
165
- async def generate_aibom_with_report(request: GenerateAIBOMRequest):
166
- """
167
- Generate a CycloneDX JSON AI SBOM with an enhancement report.
168
-
169
- This endpoint is similar to /generate but also includes a report
170
- on the AI enhancement results, including before/after scores.
171
- """
172
- try:
173
- # Create a new generator instance with the provided token if available
174
- gen = AIBOMGenerator(
175
- hf_token=request.hf_token,
176
- use_inference=request.include_inference,
177
- use_best_practices=request.use_best_practices
178
- )
179
-
180
- # Generate a request ID
181
- request_id = str(uuid.uuid4())
182
-
183
- # Create output file path
184
- safe_model_id = request.model_id.replace("/", "_")
185
- output_file = os.path.join(output_dir, f"{safe_model_id}_{request_id}.json")
186
-
187
- # Generate the AIBOM
188
- aibom = gen.generate_aibom(
189
- model_id=request.model_id,
190
- include_inference=request.include_inference,
191
- use_best_practices=request.use_best_practices,
192
- output_file=output_file
193
- )
194
-
195
- # Get the enhancement report
196
- enhancement_report = gen.get_enhancement_report()
197
-
198
- # Create download URL
199
- download_url = f"/output/{os.path.basename(output_file)}"
200
-
201
- # Create response
202
- response = {
203
- "aibom": aibom,
204
- "model_id": request.model_id,
205
- "generated_at": datetime.utcnow().isoformat() + "Z",
206
- "request_id": request_id,
207
- "download_url": download_url,
208
- "enhancement_report": enhancement_report
209
- }
210
-
211
- return response
212
- except Exception as e:
213
- raise HTTPException(status_code=500, detail=f"Error generating AIBOM: {str(e)}")
214
-
215
- @app.get("/models/{model_id}/score", response_model=Dict[str, Any])
216
- async def get_model_score(
217
- model_id: str,
218
- hf_token: Optional[str] = Query(None, description="Optional Hugging Face API token for accessing private models"),
219
- use_best_practices: bool = Query(True, description="Whether to use industry best practices for scoring")
220
- ):
221
- """
222
- Get the completeness score for a model without generating a full AIBOM.
223
-
224
- This is a lightweight endpoint that only returns the scoring information.
225
- """
226
- try:
227
- # Create a new generator instance with the provided token if available
228
- gen = AIBOMGenerator(
229
- hf_token=hf_token,
230
- use_inference=False, # Don't use inference for scoring only
231
- use_best_practices=use_best_practices
232
- )
233
-
234
- # Generate the AIBOM (needed to calculate score)
235
- aibom = gen.generate_aibom(
236
- model_id=model_id,
237
- include_inference=False, # Don't use inference for scoring only
238
- use_best_practices=use_best_practices
239
- )
240
-
241
- # Get the enhancement report for the score
242
- enhancement_report = gen.get_enhancement_report()
243
-
244
- if enhancement_report and "final_score" in enhancement_report:
245
- return enhancement_report["final_score"]
246
- else:
247
- raise HTTPException(status_code=500, detail="Failed to calculate score")
248
- except Exception as e:
249
- raise HTTPException(status_code=500, detail=f"Error calculating score: {str(e)}")
250
-
251
- @app.post("/batch", response_model=BatchJobResponse)
252
- async def batch_generate(request: BatchGenerateRequest, background_tasks: BackgroundTasks):
253
- """
254
- Start a batch job to generate AIBOMs for multiple models.
255
-
256
- This endpoint queues a background task to generate AIBOMs for all the
257
- specified model IDs and returns a job ID that can be used to check status.
258
- """
259
- try:
260
- # Generate a job ID
261
- job_id = str(uuid.uuid4())
262
-
263
- # Create job record
264
- job = {
265
- "job_id": job_id,
266
- "status": "queued",
267
- "model_ids": request.model_ids,
268
- "created_at": datetime.utcnow().isoformat() + "Z",
269
- "completed": 0,
270
- "total": len(request.model_ids),
271
- "results": {}
272
- }
273
-
274
- # Store job in memory
275
- batch_jobs[job_id] = job
276
-
277
- # Add background task to process the batch
278
- background_tasks.add_task(
279
- process_batch_job,
280
- job_id=job_id,
281
- model_ids=request.model_ids,
282
- hf_token=request.hf_token,
283
- include_inference=request.include_inference,
284
- use_best_practices=request.use_best_practices
285
- )
286
-
287
- # Return job info
288
- return {
289
- "job_id": job_id,
290
- "status": "queued",
291
- "model_ids": request.model_ids,
292
- "created_at": job["created_at"]
293
- }
294
- except Exception as e:
295
- raise HTTPException(status_code=500, detail=f"Error starting batch job: {str(e)}")
296
-
297
- @app.get("/batch/{job_id}", response_model=Dict[str, Any])
298
- async def get_batch_status(job_id: str):
299
- """
300
- Get the status of a batch job.
301
-
302
- This endpoint returns the current status of a batch job, including
303
- progress information and results for completed models.
304
- """
305
- if job_id not in batch_jobs:
306
- raise HTTPException(status_code=404, detail="Batch job not found")
307
-
308
- return batch_jobs[job_id]
309
-
310
- @app.post("/upload-model-card")
311
- async def upload_model_card(
312
- model_id: str = Form(...),
313
- model_card: UploadFile = File(...),
314
- include_inference: bool = Form(True),
315
- use_best_practices: bool = Form(True)
316
- ):
317
- """
318
- Generate an AIBOM from an uploaded model card file.
319
-
320
- This endpoint allows users to upload a model card file directly
321
- instead of requiring the model to be on Hugging Face.
322
- """
323
- try:
324
- # Create a temporary directory to store the uploaded file
325
- temp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp")
326
- os.makedirs(temp_dir, exist_ok=True)
327
-
328
- # Save the uploaded file
329
- file_path = os.path.join(temp_dir, model_card.filename)
330
- with open(file_path, "wb") as f:
331
- shutil.copyfileobj(model_card.file, f)
332
-
333
- # TODO: Implement custom model card processing
334
- # This would require modifying the AIBOMGenerator to accept a file path
335
- # instead of a model ID, which is beyond the scope of this example
336
-
337
- # For now, return a placeholder response
338
- return {
339
- "status": "not_implemented",
340
- "message": "Custom model card processing is not yet implemented"
341
- }
342
- except Exception as e:
343
- raise HTTPException(status_code=500, detail=f"Error processing uploaded model card: {str(e)}")
344
-
345
- @app.get("/download/{filename}")
346
- async def download_aibom(filename: str):
347
- """
348
- Download a previously generated AIBOM file.
349
-
350
- This endpoint allows downloading AIBOM files by filename.
351
- """
352
- file_path = os.path.join(output_dir, filename)
353
- if not os.path.exists(file_path):
354
- raise HTTPException(status_code=404, detail="File not found")
355
-
356
- return FileResponse(file_path, media_type="application/json", filename=filename)
357
-
358
- # Background task function for batch processing
359
- async def process_batch_job(job_id: str, model_ids: List[str], hf_token: Optional[str], include_inference: bool, use_best_practices: bool):
360
- """Process a batch job in the background."""
361
- # Update job status
362
- batch_jobs[job_id]["status"] = "processing"
363
-
364
- # Create output directory
365
- batch_output_dir = os.path.join(output_dir, job_id)
366
- os.makedirs(batch_output_dir, exist_ok=True)
367
-
368
- # Process each model
369
- for model_id in model_ids:
370
- try:
371
- # Create a new generator instance
372
- gen = AIBOMGenerator(
373
- hf_token=hf_token,
374
- use_inference=include_inference,
375
- use_best_practices=use_best_practices
376
- )
377
-
378
- # Create output file path
379
- safe_model_id = model_id.replace("/", "_")
380
- output_file = os.path.join(batch_output_dir, f"{safe_model_id}.json")
381
-
382
- # Generate the AIBOM
383
- aibom = gen.generate_aibom(
384
- model_id=model_id,
385
- include_inference=include_inference,
386
- use_best_practices=use_best_practices,
387
- output_file=output_file
388
- )
389
-
390
- # Get the enhancement report
391
- enhancement_report = gen.get_enhancement_report()
392
-
393
- # Create download URL
394
- download_url = f"/output/{job_id}/{safe_model_id}.json"
395
-
396
- # Store result
397
- batch_jobs[job_id]["results"][model_id] = {
398
- "status": "completed",
399
- "download_url": download_url,
400
- "enhancement_report": enhancement_report
401
- }
402
- except Exception as e:
403
- # Store error
404
- batch_jobs[job_id]["results"][model_id] = {
405
- "status": "error",
406
- "error": str(e)
407
- }
408
-
409
- # Update progress
410
- batch_jobs[job_id]["completed"] += 1
411
-
412
- # Update job status
413
- batch_jobs[job_id]["status"] = "completed"
414
-
415
- if __name__ == "__main__":
416
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks, Query, File, UploadFile, Form
2
+ from fastapi.responses import JSONResponse, FileResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.staticfiles import StaticFiles
5
+ from pydantic import BaseModel, Field
6
+ from typing import Optional, Dict, Any, List
7
+ import uvicorn
8
+ import json
9
+ import os
10
+ import sys
11
+ import uuid
12
+ import shutil
13
+ from datetime import datetime
14
+
15
+ # Add parent directory to path to import generator module
16
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
+
18
+ # Import the AIBOM generator
19
+ try:
20
+ from aibom_fix.final_generator import AIBOMGenerator
21
+ except ImportError:
22
+ # If not found, try the mapping directory
23
+ try:
24
+ from aibom_mapping.final_generator import AIBOMGenerator
25
+ except ImportError:
26
+ # If still not found, use the original generator
27
+ try:
28
+ from aibom_fix.generator import AIBOMGenerator
29
+ except ImportError:
30
+ try:
31
+ from generator import AIBOMGenerator
32
+ except ImportError:
33
+ # Last resort: try to import from the aibom_generator module
34
+ try:
35
+ from aibom_generator.generator import AIBOMGenerator
36
+ except ImportError:
37
+ raise ImportError("Could not import AIBOMGenerator from any known location")
38
+
39
+ # Create FastAPI app
40
+ app = FastAPI(
41
+ title="Aetheris AI SBOM Generator API",
42
+ description="API for generating CycloneDX JSON AI SBOMs for machine learning models",
43
+ version="1.0.0",
44
+ )
45
+
46
+ # Add CORS middleware
47
+ app.add_middleware(
48
+ CORSMiddleware,
49
+ allow_origins=["*"], # Allow all origins in development
50
+ allow_credentials=True,
51
+ allow_methods=["*"],
52
+ allow_headers=["*"],
53
+ )
54
+
55
+ # Define output directory - use a directory that will have proper permissions
56
+ # Instead of creating it at the module level, we'll create it in the startup event
57
+ output_dir = "/tmp/aibom_output" # Using /tmp which should be writable
58
+
59
+ # Create a global generator instance
60
+ generator = AIBOMGenerator(use_best_practices=True)
61
+
62
+ # Define request models
63
+ class GenerateAIBOMRequest(BaseModel):
64
+ model_id: str = Field(..., description="The Hugging Face model ID (e.g., 'meta-llama/Llama-4-Scout-17B-16E-Instruct')")
65
+ hf_token: Optional[str] = Field(None, description="Optional Hugging Face API token for accessing private models")
66
+ include_inference: Optional[bool] = Field(True, description="Whether to use AI inference to enhance the AIBOM")
67
+ use_best_practices: Optional[bool] = Field(True, description="Whether to use industry best practices for scoring")
68
+
69
+ class AIBOMResponse(BaseModel):
70
+ aibom: Dict[str, Any] = Field(..., description="The generated AIBOM in CycloneDX JSON format")
71
+ model_id: str = Field(..., description="The model ID for which the AIBOM was generated")
72
+ generated_at: str = Field(..., description="Timestamp when the AIBOM was generated")
73
+ request_id: str = Field(..., description="Unique ID for this request")
74
+ download_url: Optional[str] = Field(None, description="URL to download the AIBOM JSON file")
75
+
76
+ class EnhancementReport(BaseModel):
77
+ ai_enhanced: bool = Field(..., description="Whether AI enhancement was applied")
78
+ ai_model: Optional[str] = Field(None, description="The AI model used for enhancement, if any")
79
+ original_score: Dict[str, Any] = Field(..., description="Original completeness score before enhancement")
80
+ final_score: Dict[str, Any] = Field(..., description="Final completeness score after enhancement")
81
+ improvement: float = Field(..., description="Score improvement from enhancement")
82
+
83
+ class AIBOMWithReportResponse(AIBOMResponse):
84
+ enhancement_report: Optional[EnhancementReport] = Field(None, description="Report on AI enhancement results")
85
+
86
+ class StatusResponse(BaseModel):
87
+ status: str = Field(..., description="API status")
88
+ version: str = Field(..., description="API version")
89
+ generator_version: str = Field(..., description="AIBOM generator version")
90
+
91
+ class BatchGenerateRequest(BaseModel):
92
+ model_ids: List[str] = Field(..., description="List of Hugging Face model IDs to generate AIBOMs for")
93
+ hf_token: Optional[str] = Field(None, description="Optional Hugging Face API token for accessing private models")
94
+ include_inference: Optional[bool] = Field(True, description="Whether to use AI inference to enhance the AIBOM")
95
+ use_best_practices: Optional[bool] = Field(True, description="Whether to use industry best practices for scoring")
96
+
97
+ class BatchJobResponse(BaseModel):
98
+ job_id: str = Field(..., description="Unique ID for the batch job")
99
+ status: str = Field(..., description="Job status (e.g., 'queued', 'processing', 'completed')")
100
+ model_ids: List[str] = Field(..., description="List of model IDs in the batch")
101
+ created_at: str = Field(..., description="Timestamp when the job was created")
102
+
103
+ # In-memory storage for batch jobs
104
+ batch_jobs = {}
105
+
106
+ # Startup event to create output directory
107
+ @app.on_event("startup")
108
+ async def startup_event():
109
+ """Create necessary directories on startup."""
110
+ os.makedirs(output_dir, exist_ok=True)
111
+ # Mount the static files directory after it's created
112
+ app.mount("/output", StaticFiles(directory=output_dir), name="output")
113
+
114
+ # Define API endpoints
115
+ @app.get("/", response_model=StatusResponse)
116
+ async def get_status():
117
+ """Get the API status and version information."""
118
+ return {
119
+ "status": "operational",
120
+ "version": "1.0.0",
121
+ "generator_version": "0.1.0",
122
+ }
123
+
124
+ @app.post("/generate", response_model=AIBOMResponse)
125
+ async def generate_aibom(request: GenerateAIBOMRequest):
126
+ """
127
+ Generate a CycloneDX JSON AI SBOM for a Hugging Face model.
128
+
129
+ This endpoint takes a model ID and optional parameters to generate
130
+ a comprehensive AI SBOM in CycloneDX format.
131
+ """
132
+ try:
133
+ # Create a new generator instance with the provided token if available
134
+ gen = AIBOMGenerator(
135
+ hf_token=request.hf_token,
136
+ use_inference=request.include_inference,
137
+ use_best_practices=request.use_best_practices
138
+ )
139
+
140
+ # Generate a request ID
141
+ request_id = str(uuid.uuid4())
142
+
143
+ # Create output file path
144
+ safe_model_id = request.model_id.replace("/", "_")
145
+ output_file = os.path.join(output_dir, f"{safe_model_id}_{request_id}.json")
146
+
147
+ # Generate the AIBOM
148
+ aibom = gen.generate_aibom(
149
+ model_id=request.model_id,
150
+ include_inference=request.include_inference,
151
+ use_best_practices=request.use_best_practices,
152
+ output_file=output_file
153
+ )
154
+
155
+ # Create download URL
156
+ download_url = f"/output/{os.path.basename(output_file)}"
157
+
158
+ # Create response
159
+ response = {
160
+ "aibom": aibom,
161
+ "model_id": request.model_id,
162
+ "generated_at": datetime.utcnow().isoformat() + "Z",
163
+ "request_id": request_id,
164
+ "download_url": download_url
165
+ }
166
+
167
+ return response
168
+ except Exception as e:
169
+ raise HTTPException(status_code=500, detail=f"Error generating AIBOM: {str(e)}")
170
+
171
+ @app.post("/generate-with-report", response_model=AIBOMWithReportResponse)
172
+ async def generate_aibom_with_report(request: GenerateAIBOMRequest):
173
+ """
174
+ Generate a CycloneDX JSON AI SBOM with an enhancement report.
175
+
176
+ This endpoint is similar to /generate but also includes a report
177
+ on the AI enhancement results, including before/after scores.
178
+ """
179
+ try:
180
+ # Create a new generator instance with the provided token if available
181
+ gen = AIBOMGenerator(
182
+ hf_token=request.hf_token,
183
+ use_inference=request.include_inference,
184
+ use_best_practices=request.use_best_practices
185
+ )
186
+
187
+ # Generate a request ID
188
+ request_id = str(uuid.uuid4())
189
+
190
+ # Create output file path
191
+ safe_model_id = request.model_id.replace("/", "_")
192
+ output_file = os.path.join(output_dir, f"{safe_model_id}_{request_id}.json")
193
+
194
+ # Generate the AIBOM
195
+ aibom = gen.generate_aibom(
196
+ model_id=request.model_id,
197
+ include_inference=request.include_inference,
198
+ use_best_practices=request.use_best_practices,
199
+ output_file=output_file
200
+ )
201
+
202
+ # Get the enhancement report
203
+ enhancement_report = gen.get_enhancement_report()
204
+
205
+ # Create download URL
206
+ download_url = f"/output/{os.path.basename(output_file)}"
207
+
208
+ # Create response
209
+ response = {
210
+ "aibom": aibom,
211
+ "model_id": request.model_id,
212
+ "generated_at": datetime.utcnow().isoformat() + "Z",
213
+ "request_id": request_id,
214
+ "download_url": download_url,
215
+ "enhancement_report": enhancement_report
216
+ }
217
+
218
+ return response
219
+ except Exception as e:
220
+ raise HTTPException(status_code=500, detail=f"Error generating AIBOM: {str(e)}")
221
+
222
+ @app.get("/models/{model_id}/score", response_model=Dict[str, Any])
223
+ async def get_model_score(
224
+ model_id: str,
225
+ hf_token: Optional[str] = Query(None, description="Optional Hugging Face API token for accessing private models"),
226
+ use_best_practices: bool = Query(True, description="Whether to use industry best practices for scoring")
227
+ ):
228
+ """
229
+ Get the completeness score for a model without generating a full AIBOM.
230
+
231
+ This is a lightweight endpoint that only returns the scoring information.
232
+ """
233
+ try:
234
+ # Create a new generator instance with the provided token if available
235
+ gen = AIBOMGenerator(
236
+ hf_token=hf_token,
237
+ use_inference=False, # Don't use inference for scoring only
238
+ use_best_practices=use_best_practices
239
+ )
240
+
241
+ # Generate the AIBOM (needed to calculate score)
242
+ aibom = gen.generate_aibom(
243
+ model_id=model_id,
244
+ include_inference=False, # Don't use inference for scoring only
245
+ use_best_practices=use_best_practices
246
+ )
247
+
248
+ # Get the enhancement report for the score
249
+ enhancement_report = gen.get_enhancement_report()
250
+
251
+ if enhancement_report and "final_score" in enhancement_report:
252
+ return enhancement_report["final_score"]
253
+ else:
254
+ raise HTTPException(status_code=500, detail="Failed to calculate score")
255
+ except Exception as e:
256
+ raise HTTPException(status_code=500, detail=f"Error calculating score: {str(e)}")
257
+
258
+ @app.post("/batch", response_model=BatchJobResponse)
259
+ async def batch_generate(request: BatchGenerateRequest, background_tasks: BackgroundTasks):
260
+ """
261
+ Start a batch job to generate AIBOMs for multiple models.
262
+
263
+ This endpoint queues a background task to generate AIBOMs for all the
264
+ specified model IDs and returns a job ID that can be used to check status.
265
+ """
266
+ try:
267
+ # Generate a job ID
268
+ job_id = str(uuid.uuid4())
269
+
270
+ # Create job record
271
+ job = {
272
+ "job_id": job_id,
273
+ "status": "queued",
274
+ "model_ids": request.model_ids,
275
+ "created_at": datetime.utcnow().isoformat() + "Z",
276
+ "completed": 0,
277
+ "total": len(request.model_ids),
278
+ "results": {}
279
+ }
280
+
281
+ # Store job in memory
282
+ batch_jobs[job_id] = job
283
+
284
+ # Add background task to process the batch
285
+ background_tasks.add_task(
286
+ process_batch_job,
287
+ job_id=job_id,
288
+ model_ids=request.model_ids,
289
+ hf_token=request.hf_token,
290
+ include_inference=request.include_inference,
291
+ use_best_practices=request.use_best_practices
292
+ )
293
+
294
+ # Return job info
295
+ return {
296
+ "job_id": job_id,
297
+ "status": "queued",
298
+ "model_ids": request.model_ids,
299
+ "created_at": job["created_at"]
300
+ }
301
+ except Exception as e:
302
+ raise HTTPException(status_code=500, detail=f"Error starting batch job: {str(e)}")
303
+
304
+ @app.get("/batch/{job_id}", response_model=Dict[str, Any])
305
+ async def get_batch_status(job_id: str):
306
+ """
307
+ Get the status of a batch job.
308
+
309
+ This endpoint returns the current status of a batch job, including
310
+ progress information and results for completed models.
311
+ """
312
+ if job_id not in batch_jobs:
313
+ raise HTTPException(status_code=404, detail="Batch job not found")
314
+
315
+ return batch_jobs[job_id]
316
+
317
+ @app.post("/upload-model-card")
318
+ async def upload_model_card(
319
+ model_id: str = Form(...),
320
+ model_card: UploadFile = File(...),
321
+ include_inference: bool = Form(True),
322
+ use_best_practices: bool = Form(True)
323
+ ):
324
+ """
325
+ Generate an AIBOM from an uploaded model card file.
326
+
327
+ This endpoint allows users to upload a model card file directly
328
+ instead of requiring the model to be on Hugging Face.
329
+ """
330
+ try:
331
+ # Create a temporary directory to store the uploaded file
332
+ temp_dir = os.path.join("/tmp", "aibom_temp")
333
+ os.makedirs(temp_dir, exist_ok=True)
334
+
335
+ # Save the uploaded file
336
+ file_path = os.path.join(temp_dir, model_card.filename)
337
+ with open(file_path, "wb") as f:
338
+ shutil.copyfileobj(model_card.file, f)
339
+
340
+ # TODO: Implement custom model card processing
341
+ # This would require modifying the AIBOMGenerator to accept a file path
342
+ # instead of a model ID, which is beyond the scope of this example
343
+
344
+ # For now, return a placeholder response
345
+ return {
346
+ "status": "not_implemented",
347
+ "message": "Custom model card processing is not yet implemented"
348
+ }
349
+ except Exception as e:
350
+ raise HTTPException(status_code=500, detail=f"Error processing uploaded model card: {str(e)}")
351
+
352
+ @app.get("/download/{filename}")
353
+ async def download_aibom(filename: str):
354
+ """
355
+ Download a previously generated AIBOM file.
356
+
357
+ This endpoint allows downloading AIBOM files by filename.
358
+ """
359
+ file_path = os.path.join(output_dir, filename)
360
+ if not os.path.exists(file_path):
361
+ raise HTTPException(status_code=404, detail="File not found")
362
+
363
+ return FileResponse(file_path, media_type="application/json", filename=filename)
364
+
365
+ # Background task function for batch processing
366
+ async def process_batch_job(job_id: str, model_ids: List[str], hf_token: Optional[str], include_inference: bool, use_best_practices: bool):
367
+ """Process a batch job in the background."""
368
+ # Update job status
369
+ batch_jobs[job_id]["status"] = "processing"
370
+
371
+ # Create output directory
372
+ batch_output_dir = os.path.join(output_dir, job_id)
373
+ os.makedirs(batch_output_dir, exist_ok=True)
374
+
375
+ # Process each model
376
+ for model_id in model_ids:
377
+ try:
378
+ # Create a new generator instance
379
+ gen = AIBOMGenerator(
380
+ hf_token=hf_token,
381
+ use_inference=include_inference,
382
+ use_best_practices=use_best_practices
383
+ )
384
+
385
+ # Create output file path
386
+ safe_model_id = model_id.replace("/", "_")
387
+ output_file = os.path.join(batch_output_dir, f"{safe_model_id}.json")
388
+
389
+ # Generate the AIBOM
390
+ aibom = gen.generate_aibom(
391
+ model_id=model_id,
392
+ include_inference=include_inference,
393
+ use_best_practices=use_best_practices,
394
+ output_file=output_file
395
+ )
396
+
397
+ # Get the enhancement report
398
+ enhancement_report = gen.get_enhancement_report()
399
+
400
+ # Create download URL
401
+ download_url = f"/output/{job_id}/{safe_model_id}.json"
402
+
403
+ # Store result
404
+ batch_jobs[job_id]["results"][model_id] = {
405
+ "status": "completed",
406
+ "download_url": download_url,
407
+ "enhancement_report": enhancement_report
408
+ }
409
+ except Exception as e:
410
+ # Store error
411
+ batch_jobs[job_id]["results"][model_id] = {
412
+ "status": "error",
413
+ "error": str(e)
414
+ }
415
+
416
+ # Update progress
417
+ batch_jobs[job_id]["completed"] += 1
418
+
419
+ # Update job status
420
+ batch_jobs[job_id]["status"] = "completed"
421
+
422
+ if __name__ == "__main__":
423
+ uvicorn.run(app, host="0.0.0.0", port=8000)