Skip to content

Commit d1d5720

Browse files
feat: Enhance enrichment command with interactive and file strategies, add logging, and implement user-provided value handling
1 parent e9bf143 commit d1d5720

File tree

7 files changed

+896
-19
lines changed

7 files changed

+896
-19
lines changed

cmd/enrich.go

Lines changed: 151 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,177 @@ package cmd
22

33
import (
44
"fmt"
5+
"strings"
56

7+
"github.com/idlab-discover/AIBoMGen-cli/internal/completeness"
8+
"github.com/idlab-discover/AIBoMGen-cli/internal/enricher"
9+
"github.com/idlab-discover/AIBoMGen-cli/internal/fetcher"
10+
bomio "github.com/idlab-discover/AIBoMGen-cli/internal/io"
11+
"github.com/idlab-discover/AIBoMGen-cli/internal/metadata"
612
"github.com/spf13/cobra"
13+
"github.com/spf13/viper"
714
)
815

916
// enrichCmd represents the enrich command
1017
var enrichCmd = &cobra.Command{
1118
Use: "enrich",
1219
Short: "Enrich an existing AIBOM with additional metadata",
13-
Long: `Enrich an existing AIBOM with additional metadata by prompting the user for inputs on empty or missing fields.`,
20+
Long: `Enrich an existing AIBOM with additional metadata through interactive prompts
21+
or by loading values from a configuration file. Optionally refetch model metadata
22+
from Hugging Face API and README before enrichment.`,
1423
RunE: func(cmd *cobra.Command, args []string) error {
15-
fmt.Println("Not implemented")
24+
// Validate strategy
25+
strategy := strings.ToLower(strings.TrimSpace(enrichStrategy))
26+
if strategy == "" {
27+
strategy = "interactive"
28+
}
29+
switch strategy {
30+
case "interactive", "file":
31+
// ok
32+
default:
33+
return fmt.Errorf("invalid --strategy %q (expected interactive|file)", enrichStrategy)
34+
}
35+
36+
// Validate log level
37+
level := strings.ToLower(strings.TrimSpace(enrichLogLevel))
38+
if level == "" {
39+
level = "standard"
40+
}
41+
switch level {
42+
case "quiet", "standard", "debug":
43+
// ok
44+
default:
45+
return fmt.Errorf("invalid --log-level %q (expected quiet|standard|debug)", enrichLogLevel)
46+
}
47+
48+
// Wire internal package logging
49+
if level != "quiet" {
50+
lw := cmd.ErrOrStderr()
51+
enricher.SetLogger(lw)
52+
completeness.SetLogger(lw)
53+
fetcher.SetLogger(lw)
54+
if level == "debug" {
55+
metadata.SetLogger(lw)
56+
}
57+
}
58+
59+
// Read existing BOM
60+
bom, err := bomio.ReadBOM(enrichInput, enrichInputFormat)
61+
if err != nil {
62+
return fmt.Errorf("failed to read input BOM: %w", err)
63+
}
64+
65+
// Determine output path
66+
outPath := enrichOutput
67+
if outPath == "" {
68+
outPath = enrichInput // overwrite by default
69+
}
70+
71+
// Determine spec version (default to same version as input)
72+
// Note: if not specified, WriteBOM will use the BOM's existing spec version
73+
specVersion := strings.TrimSpace(enrichSpecVersion)
74+
75+
// Build enricher configuration
76+
cfg := enricher.Config{
77+
Strategy: strategy,
78+
ConfigFile: enrichConfigFile,
79+
RequiredOnly: enrichRequiredOnly,
80+
MinWeight: enrichMinWeight,
81+
Refetch: enrichRefetch,
82+
NoPreview: enrichNoPreview,
83+
SpecVersion: specVersion,
84+
HFToken: enrichHFToken,
85+
HFBaseURL: enrichHFBaseURL,
86+
HFTimeout: enrichHFTimeout,
87+
}
88+
89+
// Load config file values if using file strategy
90+
var fileValues map[string]interface{}
91+
if strategy == "file" {
92+
if enrichConfigFile == "" {
93+
return fmt.Errorf("--file is required when using --strategy file")
94+
}
95+
fileValues, err = loadEnrichmentConfig(enrichConfigFile)
96+
if err != nil {
97+
return fmt.Errorf("failed to load config file: %w", err)
98+
}
99+
}
100+
101+
// Create enricher
102+
e := enricher.New(enricher.Options{
103+
Reader: cmd.InOrStdin(),
104+
Writer: cmd.OutOrStdout(),
105+
Config: cfg,
106+
})
107+
108+
// Run enrichment
109+
enriched, err := e.Enrich(bom, fileValues)
110+
if err != nil {
111+
return fmt.Errorf("enrichment failed: %w", err)
112+
}
113+
114+
// Write output
115+
if err := bomio.WriteBOM(enriched, outPath, enrichOutputFormat, specVersion); err != nil {
116+
return fmt.Errorf("failed to write output: %w", err)
117+
}
118+
119+
if level != "quiet" {
120+
fmt.Fprintf(cmd.OutOrStdout(), "\n✓ Enriched BOM saved to %s\n", outPath)
121+
}
122+
16123
return nil
17124
},
18125
}
19126

20127
var (
21128
enrichInput string
129+
enrichInputFormat string
22130
enrichOutput string
23131
enrichOutputFormat string
24132
enrichSpecVersion string
25-
enrichQuiet bool
133+
enrichStrategy string
134+
enrichConfigFile string
135+
enrichRequiredOnly bool
136+
enrichMinWeight float64
137+
enrichRefetch bool
138+
enrichNoPreview bool
139+
enrichLogLevel string
140+
enrichHFToken string
141+
enrichHFBaseURL string
142+
enrichHFTimeout int
26143
)
27144

28145
func init() {
29-
enrichCmd.Flags().StringVarP(&enrichInput, "input", "i", "", "Path to existing AIBOM JSON (required)")
146+
enrichCmd.Flags().StringVarP(&enrichInput, "input", "i", "", "Path to existing AIBOM (required)")
30147
enrichCmd.Flags().StringVarP(&enrichOutput, "output", "o", "", "Output file path (default: overwrite input)")
31-
enrichCmd.Flags().StringVarP(&enrichOutputFormat, "format", "f", "auto", "Output BOM format: json|xml|auto (default: auto)")
32-
enrichCmd.Flags().StringVar(&enrichSpecVersion, "spec", "", "CycloneDX spec version for output (e.g., 1.3, 1.4, 1.5, 1.6)")
148+
enrichCmd.Flags().StringVarP(&enrichInputFormat, "format", "f", "auto", "Input BOM format: json|xml|auto")
149+
enrichCmd.Flags().StringVar(&enrichOutputFormat, "output-format", "auto", "Output BOM format: json|xml|auto")
150+
enrichCmd.Flags().StringVar(&enrichSpecVersion, "spec", "", "CycloneDX spec version for output (default: same as input)")
151+
152+
enrichCmd.Flags().StringVar(&enrichStrategy, "strategy", "interactive", "Enrichment strategy: interactive|file")
153+
enrichCmd.Flags().StringVar(&enrichConfigFile, "file", "", "Path to enrichment config file (YAML)")
154+
enrichCmd.Flags().BoolVar(&enrichRequiredOnly, "required-only", false, "Only prompt for required fields")
155+
enrichCmd.Flags().Float64Var(&enrichMinWeight, "min-weight", 0.0, "Only prompt for fields with weight >= this value")
156+
enrichCmd.Flags().BoolVar(&enrichRefetch, "refetch", false, "Refetch model metadata from Hugging Face before enrichment")
157+
enrichCmd.Flags().BoolVar(&enrichNoPreview, "no-preview", false, "Skip preview before saving")
158+
159+
enrichCmd.Flags().StringVar(&enrichLogLevel, "log-level", "standard", "Log level: quiet|standard|debug")
160+
enrichCmd.Flags().StringVar(&enrichHFToken, "hf-token", "", "Hugging Face API token (for refetch)")
161+
enrichCmd.Flags().StringVar(&enrichHFBaseURL, "hf-base-url", "", "Hugging Face base URL (for refetch)")
162+
enrichCmd.Flags().IntVar(&enrichHFTimeout, "hf-timeout", 30, "Hugging Face API timeout in seconds (for refetch)")
163+
164+
_ = enrichCmd.MarkFlagRequired("input")
165+
}
166+
167+
// loadEnrichmentConfig loads enrichment values from a YAML config file
168+
func loadEnrichmentConfig(path string) (map[string]interface{}, error) {
169+
v := viper.New()
170+
v.SetConfigFile(path)
171+
v.SetConfigType("yaml")
172+
173+
if err := v.ReadInConfig(); err != nil {
174+
return nil, err
175+
}
176+
177+
return v.AllSettings(), nil
33178
}

cmd/generate.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/spf13/cobra"
1212

1313
"github.com/idlab-discover/AIBoMGen-cli/internal/builder"
14-
"github.com/idlab-discover/AIBoMGen-cli/internal/enricher"
1514
"github.com/idlab-discover/AIBoMGen-cli/internal/fetcher"
1615
"github.com/idlab-discover/AIBoMGen-cli/internal/generator"
1716
bomio "github.com/idlab-discover/AIBoMGen-cli/internal/io"
@@ -126,13 +125,9 @@ var generateCmd = &cobra.Command{
126125
}
127126

128127
// Optional enrichment: only run when requested.
129-
// Enricher is currently stubbed; do not fail generate if it errors.
128+
// The enricher is now a separate command; this flag is deprecated.
130129
if enrich {
131-
for _, d := range discoveredBOMs {
132-
if err := enricher.InteractiveCompleteBOM(d.BOM, true, cmd.InOrStdin(), cmd.OutOrStdout()); err != nil && level == "debug" {
133-
fmt.Fprintf(cmd.ErrOrStderr(), "[enrich] %v\n", err)
134-
}
135-
}
130+
fmt.Fprintf(cmd.ErrOrStderr(), "[warn] --enrich flag is deprecated. Use 'aibomgen-cli enrich' command instead.\n")
136131
}
137132

138133
output := generateOutput

config/enrichment.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Component-level fields
2+
licenses: Apache-2.0
3+
tags:
4+
- nlp
5+
- transformer
6+
- bert
7+
manufacturer: Google
8+
group: google
9+
10+
# Model card fields
11+
task: text-classification
12+
architectureFamily: transformer
13+
modelArchitecture: bert-base
14+
15+
# Hugging Face properties
16+
language: en
17+
libraryName: transformers
18+
19+
# Model card sections
20+
useCases: "Sentiment analysis, text classification"
21+
technicalLimitations: "Limited to English text, max 512 tokens"
22+
ethicalConsiderations: "May reflect biases present in training data"
23+
24+
# External references
25+
paperUrl: https://arxiv.org/abs/1810.04805
26+
demoUrl: https://huggingface.co/spaces/demo
27+
28+
# Environmental impact
29+
hardwareType: "NVIDIA V100"
30+
hoursUsed: "96"
31+
cloudProvider: "Google Cloud"
32+
computeRegion: "us-central1"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/CycloneDX/cyclonedx-go v0.9.3
77
github.com/spf13/cobra v1.10.2
88
github.com/spf13/viper v1.21.0
9+
go.yaml.in/yaml/v3 v3.0.4
910
)
1011

1112
require (
@@ -18,7 +19,6 @@ require (
1819
github.com/spf13/cast v1.10.0 // indirect
1920
github.com/spf13/pflag v1.0.10 // indirect
2021
github.com/subosito/gotenv v1.6.0 // indirect
21-
go.yaml.in/yaml/v3 v3.0.4 // indirect
2222
golang.org/x/sys v0.39.0 // indirect
2323
golang.org/x/text v0.32.0 // indirect
2424
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect

0 commit comments

Comments
 (0)