From 1c75a9f536c2f81827a4ec6e88e26352f1b1117e Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Tue, 30 Dec 2025 14:30:31 +0530 Subject: [PATCH 01/13] initial changes --- .../internal/cmd/operations.go | 55 +++++++++------ .../internal/providers/azure/provider.go | 4 +- .../internal/providers/interface.go | 4 +- .../internal/providers/openai/conversions.go | 68 +++++++++++++++++++ .../internal/providers/openai/provider.go | 37 +++++++--- .../internal/services/finetune_service.go | 27 +++++--- .../internal/services/interface.go | 4 +- .../azure.ai.finetune/pkg/models/finetune.go | 13 +++- 8 files changed, 170 insertions(+), 42 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index 9c2ce3d6894..6bdd61f7981 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -144,7 +144,7 @@ func newOperationShowCommand() *cobra.Command { cmd := &cobra.Command{ Use: "show", - Short: "Show the fine tuning job details", + Short: "show the fine tuning job details", RunE: func(cmd *cobra.Command, args []string) error { ctx := azdext.WithAccessToken(cmd.Context()) azdClient, err := azdext.NewAzdClient() @@ -152,30 +152,45 @@ func newOperationShowCommand() *cobra.Command { return fmt.Errorf("failed to create azd client: %w", err) } defer azdClient.Close() + // Show spinner while fetching jobs spinner := ux.NewSpinner(&ux.SpinnerOptions{ - Text: fmt.Sprintf("Fetching fine-tuning job %s...", jobID), + Text: fmt.Sprintf("fetching fine-tuning job %s...", jobID), }) if err := spinner.Start(ctx); err != nil { - fmt.Printf("Failed to start spinner: %v\n", err) + fmt.Printf("failed to start spinner: %v\n", err) } - // Fetch fine-tuning job details using job wrapper - job, err := JobWrapper.GetJobDetails(ctx, azdClient, jobID) - _ = spinner.Stop(ctx) + fineTuneSvc, err := services.NewFineTuningService(ctx, azdClient, nil) + if err != nil { + _ = spinner.Stop(ctx) + fmt.Println() + return err + } + job, err := fineTuneSvc.GetFineTuningJobDetails(ctx, jobID) + _ = spinner.Stop(ctx) if err != nil { - return fmt.Errorf("failed to get fine-tuning job details: %w", err) + fmt.Println() + return err } + + // // Fetch fine-tuning job details using job wrapper + // job, err := JobWrapper.GetJobDetails(ctx, azdClient, jobID) + // _ = spinner.Stop(ctx) + + // if err != nil { + // return fmt.Errorf("failed to get fine-tuning job details: %w", err) + // } // Print job details - color.Green("\nFine-Tuning Job Details\n") - fmt.Printf("Job ID: %s\n", job.Id) - fmt.Printf("Status: %s %s\n", getStatusSymbol(job.Status), job.Status) + color.Green("\nfine-tuning job details\n") + fmt.Printf("Job ID: %s\n", job.ID) + fmt.Printf("Status: %s %s\n", getStatusSymbol(string(job.Status)), job.Status) fmt.Printf("Model: %s\n", job.Model) fmt.Printf("Fine-tuned Model: %s\n", formatFineTunedModel(job.FineTunedModel)) fmt.Printf("Created At: %s\n", job.CreatedAt) - if job.FinishedAt != "" { + if !job.FinishedAt.IsZero() { fmt.Printf("Finished At: %s\n", job.FinishedAt) } fmt.Printf("Method: %s\n", job.Method) @@ -194,17 +209,18 @@ func newOperationShowCommand() *cobra.Command { // Fetch and print events eventsSpinner := ux.NewSpinner(&ux.SpinnerOptions{ - Text: "Fetching job events...", + Text: "fetching job events...", }) if err := eventsSpinner.Start(ctx); err != nil { - fmt.Printf("Failed to start spinner: %v\n", err) + fmt.Printf("failed to start spinner: %v\n", err) } - events, err := JobWrapper.GetJobEvents(ctx, azdClient, jobID) + events, err := fineTuneSvc.GetJobEvents(ctx, jobID) + // events, err := JobWrapper.GetJobEvents(ctx, azdClient, jobID) _ = eventsSpinner.Stop(ctx) if err != nil { - fmt.Printf("Warning: failed to fetch job events: %v\n", err) + fmt.Printf("WARNING: failed to fetch job events: %v\n", err) } else if events != nil && len(events.Data) > 0 { fmt.Println("\nJob Events:") for i, event := range events.Data { @@ -218,17 +234,18 @@ func newOperationShowCommand() *cobra.Command { // Fetch and print checkpoints if job is completed if job.Status == "succeeded" { checkpointsSpinner := ux.NewSpinner(&ux.SpinnerOptions{ - Text: "Fetching job checkpoints...", + Text: "fetching job checkpoints...", }) if err := checkpointsSpinner.Start(ctx); err != nil { - fmt.Printf("Failed to start spinner: %v\n", err) + fmt.Printf("failed to start spinner: %v\n", err) } - checkpoints, err := JobWrapper.GetJobCheckPoints(ctx, azdClient, jobID) + checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID) + // checkpoints, err := JobWrapper.GetJobCheckPoints(ctx, azdClient, jobID) _ = checkpointsSpinner.Stop(ctx) if err != nil { - fmt.Printf("Warning: failed to fetch job checkpoints: %v\n", err) + fmt.Printf("warning: failed to fetch job checkpoints: %v\n", err) } else if checkpoints != nil && len(checkpoints.Data) > 0 { fmt.Println("\nJob Checkpoints:") for i, checkpoint := range checkpoints.Data { diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go index 2d67a6f2e7e..430da9b704e 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go @@ -50,13 +50,13 @@ func (p *AzureProvider) GetFineTuningJobDetails(ctx context.Context, jobID strin } // GetJobEvents retrieves events for a fine-tuning job -func (p *AzureProvider) GetJobEvents(ctx context.Context, jobID string, limit int, after string) ([]*models.JobEvent, error) { +func (p *AzureProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) { // TODO: Implement return nil, nil } // GetJobCheckpoints retrieves checkpoints for a fine-tuning job -func (p *AzureProvider) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) ([]*models.JobCheckpoint, error) { +func (p *AzureProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) { // TODO: Implement return nil, nil } diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go index e0f935d88b0..8cecc614a28 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go @@ -25,10 +25,10 @@ type FineTuningProvider interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a fine-tuning job - GetJobEvents(ctx context.Context, jobID string, limit int, after string) ([]*models.JobEvent, error) + GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) // GetJobCheckpoints retrieves checkpoints for a fine-tuning job - GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) ([]*models.JobCheckpoint, error) + GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) // PauseJob pauses a fine-tuning job PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go index bcf0ccdd9e7..f236b42d234 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go @@ -7,6 +7,7 @@ import ( "azure.ai.finetune/internal/utils" "azure.ai.finetune/pkg/models" "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/packages/pagination" ) // OpenAI Status Constants - matches OpenAI SDK values @@ -47,3 +48,70 @@ func convertOpenAIJobToModel(openaiJob openai.FineTuningJob) *models.FineTuningJ CreatedAt: utils.UnixTimestampToUTC(openaiJob.CreatedAt), } } + +func convertOpenAIJobToDetailModel(openaiJob *openai.FineTuningJob) *models.FineTuningJobDetail { + // Extract hyperparameters based on method type + hyperparameters := &models.Hyperparameters{} + hyperparameters.BatchSize = openaiJob.Hyperparameters.BatchSize.OfInt + hyperparameters.LearningRateMultiplier = openaiJob.Hyperparameters.LearningRateMultiplier.OfFloat + hyperparameters.NEpochs = openaiJob.Hyperparameters.NEpochs.OfInt + + jobDetail := &models.FineTuningJobDetail{ + ID: openaiJob.ID, + Status: mapOpenAIStatusToJobStatus(openaiJob.Status), + Model: openaiJob.Model, + FineTunedModel: openaiJob.FineTunedModel, + CreatedAt: utils.UnixTimestampToUTC(openaiJob.CreatedAt), + FinishedAt: utils.UnixTimestampToUTC(openaiJob.FinishedAt), + Method: openaiJob.Method.Type, + TrainingFile: openaiJob.TrainingFile, + ValidationFile: openaiJob.ValidationFile, + Hyperparameters: hyperparameters, + } + return jobDetail +} + +func convertOpenAIJobEventsToModel(eventsList *pagination.CursorPage[openai.FineTuningJobEvent]) *models.JobEventsListContract { + var events []models.JobEvent + for _, event := range eventsList.Data { + eventContract := models.JobEvent{ + ID: event.ID, + CreatedAt: utils.UnixTimestampToUTC(event.CreatedAt), + Level: string(event.Level), + Message: event.Message, + Data: event.Data, + Type: string(event.Type), + } + events = append(events, eventContract) + } + + return &models.JobEventsListContract{ + Data: events, + HasMore: eventsList.HasMore, + } +} + +func convertOpenAIJobCheckpointsToModel(openaiJobCheckpointsList *pagination.CursorPage[openai.FineTuningJobCheckpoint]) *models.JobCheckpointsListContract { + var checkpoints []models.JobCheckpoint + + for _, checkpoint := range openaiJobCheckpointsList.Data { + metrics := &models.CheckpointMetrics{ + FullValidLoss: checkpoint.Metrics.FullValidLoss, + FullValidMeanTokenAccuracy: checkpoint.Metrics.FullValidMeanTokenAccuracy, + } + + jobCheckpoint := models.JobCheckpoint{ + ID: checkpoint.ID, + CreatedAt: utils.UnixTimestampToUTC(checkpoint.CreatedAt), + FineTunedModelCheckpoint: checkpoint.FineTunedModelCheckpoint, + Metrics: metrics, + FineTuningJobID: checkpoint.FineTuningJobID, + StepNumber: checkpoint.StepNumber, + } + checkpoints = append(checkpoints, jobCheckpoint) + } + return &models.JobCheckpointsListContract{ + Data: checkpoints, + HasMore: openaiJobCheckpointsList.HasMore, + } +} diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go index 5de45c00feb..551e2ddf942 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go @@ -59,20 +59,41 @@ func (p *OpenAIProvider) ListFineTuningJobs(ctx context.Context, limit int, afte // GetFineTuningJobDetails retrieves detailed information about a job func (p *OpenAIProvider) GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) { - // TODO: Implement - return nil, nil + job, err := p.client.FineTuning.Jobs.Get(ctx, jobID) + if err != nil { + return nil, err + } + finetuningJobDetail := convertOpenAIJobToDetailModel(job) + return finetuningJobDetail, nil } // GetJobEvents retrieves events for a fine-tuning job -func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string, limit int, after string) ([]*models.JobEvent, error) { - // TODO: Implement - return nil, nil +func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) { + eventsList, err := p.client.FineTuning.Jobs.ListEvents( + ctx, + jobID, + openai.FineTuningJobListEventsParams{}, + ) + if err != nil { + return nil, err + } + + events := convertOpenAIJobEventsToModel(eventsList) + return events, nil } // GetJobCheckpoints retrieves checkpoints for a fine-tuning job -func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) ([]*models.JobCheckpoint, error) { - // TODO: Implement - return nil, nil +func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) { + checkpointList, err := p.client.FineTuning.Jobs.Checkpoints.List( + ctx, + jobID, + openai.FineTuningJobCheckpointListParams{}, + ) + if err != nil { + return nil, err + } + checkpoints := convertOpenAIJobCheckpointsToModel(checkpointList) + return checkpoints, nil } // PauseJob pauses a fine-tuning job diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go index 03ae59d508f..59fb91257b2 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go @@ -75,20 +75,31 @@ func (s *fineTuningServiceImpl) ListFineTuningJobs(ctx context.Context, limit in // GetFineTuningJobDetails retrieves detailed information about a job func (s *fineTuningServiceImpl) GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) { - // TODO: Implement - return nil, nil + jobDetail, err := s.provider.GetFineTuningJobDetails(ctx, jobID) + if err != nil { + return nil, fmt.Errorf("failed to get job details: %w", err) + } + + return jobDetail, nil } // GetJobEvents retrieves events for a job with filtering and pagination -func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string, limit int, after string) ([]*models.JobEvent, error) { - // TODO: Implement - return nil, nil +func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) { + eventsList, err := s.provider.GetJobEvents(ctx, jobID) + if err != nil { + return nil, fmt.Errorf("failed to get job events: %w", err) + } + + return eventsList, nil } // GetJobCheckpoints retrieves checkpoints for a job with pagination -func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) ([]*models.JobCheckpoint, error) { - // TODO: Implement - return nil, nil +func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) { + checkpointList, err := s.provider.GetJobCheckpoints(ctx, jobID) + if err != nil { + return nil, fmt.Errorf("failed to get job checkpoints: %w", err) + } + return checkpointList, nil } // PauseJob pauses a running job (if applicable) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go index e2b0d63c7e5..726de3e1521 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go @@ -24,10 +24,10 @@ type FineTuningService interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a job with filtering and pagination - GetJobEvents(ctx context.Context, jobID string, limit int, after string) ([]*models.JobEvent, error) + GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) // GetJobCheckpoints retrieves checkpoints for a job with pagination - GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) ([]*models.JobCheckpoint, error) + GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) // PauseJob pauses a running job (if applicable) PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error) diff --git a/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go b/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go index a8b6b11b237..9b01413f224 100644 --- a/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go +++ b/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go @@ -70,7 +70,7 @@ type FineTuningJobDetail struct { Model string FineTunedModel string CreatedAt time.Time - FinishedAt *time.Time + FinishedAt time.Time Method string TrainingFile string ValidationFile string @@ -88,6 +88,12 @@ type JobEvent struct { Type string } +type JobEventsListContract struct { + Data []JobEvent + HasMore bool +} + + // JobCheckpoint represents a checkpoint of a fine-tuning job type JobCheckpoint struct { ID string @@ -98,6 +104,11 @@ type JobCheckpoint struct { StepNumber int64 } +type JobCheckpointsListContract struct { + Data []JobCheckpoint + HasMore bool +} + // CheckpointMetrics represents metrics for a checkpoint type CheckpointMetrics struct { FullValidLoss float64 From fbe538934b8ec1417d303547977939cce59b773e Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Tue, 30 Dec 2025 14:37:53 +0530 Subject: [PATCH 02/13] clean-up --- .../internal/cmd/operations.go | 10 --- .../internal/tools/job_wrapper.go | 85 ------------------- 2 files changed, 95 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index 6bdd61f7981..74a7cca5d66 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -175,14 +175,6 @@ func newOperationShowCommand() *cobra.Command { return err } - // // Fetch fine-tuning job details using job wrapper - // job, err := JobWrapper.GetJobDetails(ctx, azdClient, jobID) - // _ = spinner.Stop(ctx) - - // if err != nil { - // return fmt.Errorf("failed to get fine-tuning job details: %w", err) - // } - // Print job details color.Green("\nfine-tuning job details\n") fmt.Printf("Job ID: %s\n", job.ID) @@ -216,7 +208,6 @@ func newOperationShowCommand() *cobra.Command { } events, err := fineTuneSvc.GetJobEvents(ctx, jobID) - // events, err := JobWrapper.GetJobEvents(ctx, azdClient, jobID) _ = eventsSpinner.Stop(ctx) if err != nil { @@ -241,7 +232,6 @@ func newOperationShowCommand() *cobra.Command { } checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID) - // checkpoints, err := JobWrapper.GetJobCheckPoints(ctx, azdClient, jobID) _ = checkpointsSpinner.Stop(ctx) if err != nil { diff --git a/cli/azd/extensions/azure.ai.finetune/internal/tools/job_wrapper.go b/cli/azd/extensions/azure.ai.finetune/internal/tools/job_wrapper.go index 7aa35e223c8..9b760df9cb8 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/tools/job_wrapper.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/tools/job_wrapper.go @@ -196,91 +196,6 @@ func GetJobDetails(ctx context.Context, azdClient *azdext.AzdClient, jobId strin return jobDetail, nil } -func GetJobEvents( - ctx context.Context, - azdClient *azdext.AzdClient, - jobId string, -) (*EventsListContract, error) { - client, err := GetOpenAIClientFromAzdClient(ctx, azdClient) - if err != nil { - return nil, fmt.Errorf("failed to create OpenAI client: %w", err) - } - - eventsList, err := client.FineTuning.Jobs.ListEvents( - ctx, - jobId, - openai.FineTuningJobListEventsParams{}, - ) - if err != nil { - return nil, fmt.Errorf("failed to get job events: %w", err) - } - - // Convert events to EventContract slice - var events []EventContract - for _, event := range eventsList.Data { - eventContract := EventContract{ - ID: event.ID, - CreatedAt: formatUnixTimestampToUTC(event.CreatedAt), - Level: string(event.Level), - Message: event.Message, - Data: event.Data, - Type: string(event.Type), - } - events = append(events, eventContract) - } - - // Return EventsListContract - return &EventsListContract{ - Data: events, - HasMore: eventsList.HasMore, - }, nil -} - -func GetJobCheckPoints( - ctx context.Context, - azdClient *azdext.AzdClient, - jobId string, -) (*CheckpointsListContract, error) { - client, err := GetOpenAIClientFromAzdClient(ctx, azdClient) - if err != nil { - return nil, fmt.Errorf("failed to create OpenAI client: %w", err) - } - - checkpointList, err := client.FineTuning.Jobs.Checkpoints.List( - ctx, - jobId, - openai.FineTuningJobCheckpointListParams{}, - ) - if err != nil { - return nil, fmt.Errorf("failed to get job checkpoints: %w", err) - } - - // Convert checkpoints to CheckpointContract slice - var checkpoints []CheckpointContract - for _, checkpoint := range checkpointList.Data { - metrics := &CheckpointMetrics{ - FullValidLoss: checkpoint.Metrics.FullValidLoss, - FullValidMeanTokenAccuracy: checkpoint.Metrics.FullValidMeanTokenAccuracy, - } - - checkpointContract := CheckpointContract{ - ID: checkpoint.ID, - CreatedAt: formatUnixTimestampToUTC(checkpoint.CreatedAt), - FineTunedModelCheckpoint: checkpoint.FineTunedModelCheckpoint, - Metrics: metrics, - FineTuningJobID: checkpoint.FineTuningJobID, - StepNumber: checkpoint.StepNumber, - } - checkpoints = append(checkpoints, checkpointContract) - } - - // Return CheckpointsListContract - return &CheckpointsListContract{ - Data: checkpoints, - HasMore: checkpointList.HasMore, - }, nil -} - // GetOpenAIClientFromAzdClient creates an OpenAI client from AzdClient context func GetOpenAIClientFromAzdClient(ctx context.Context, azdClient *azdext.AzdClient) (*openai.Client, error) { envValueMap := make(map[string]string) From b6a8a0ee0cbbdee4e50a52a8d59a59bb14d075fe Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Tue, 30 Dec 2025 15:14:44 +0530 Subject: [PATCH 03/13] Format time --- .../azure.ai.finetune/internal/cmd/operations.go | 11 ++++++----- .../azure.ai.finetune/internal/utils/time.go | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index 74a7cca5d66..2c0f040b8a6 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -15,6 +15,7 @@ import ( FTYaml "azure.ai.finetune/internal/fine_tuning_yaml" "azure.ai.finetune/internal/services" JobWrapper "azure.ai.finetune/internal/tools" + "azure.ai.finetune/internal/utils" ) func newOperationCommand() *cobra.Command { @@ -181,9 +182,9 @@ func newOperationShowCommand() *cobra.Command { fmt.Printf("Status: %s %s\n", getStatusSymbol(string(job.Status)), job.Status) fmt.Printf("Model: %s\n", job.Model) fmt.Printf("Fine-tuned Model: %s\n", formatFineTunedModel(job.FineTunedModel)) - fmt.Printf("Created At: %s\n", job.CreatedAt) + fmt.Printf("Created At: %s\n", utils.FormatTime(job.CreatedAt)) if !job.FinishedAt.IsZero() { - fmt.Printf("Finished At: %s\n", job.FinishedAt) + fmt.Printf("Finished At: %s\n", utils.FormatTime(job.FinishedAt)) } fmt.Printf("Method: %s\n", job.Method) fmt.Printf("Training File: %s\n", job.TrainingFile) @@ -215,7 +216,7 @@ func newOperationShowCommand() *cobra.Command { } else if events != nil && len(events.Data) > 0 { fmt.Println("\nJob Events:") for i, event := range events.Data { - fmt.Printf(" %d. [%s] %s - %s\n", i+1, event.Level, event.CreatedAt, event.Message) + fmt.Printf(" %d. [%s] %s - %s\n", i+1, event.Level, utils.FormatTime(event.CreatedAt), event.Message) } if events.HasMore { fmt.Println(" ... (more events available)") @@ -241,7 +242,7 @@ func newOperationShowCommand() *cobra.Command { for i, checkpoint := range checkpoints.Data { fmt.Printf(" %d. Checkpoint ID: %s\n", i+1, checkpoint.ID) fmt.Printf(" Checkpoint Name: %s\n", checkpoint.FineTunedModelCheckpoint) - fmt.Printf(" Created On: %s\n", checkpoint.CreatedAt) + fmt.Printf(" Created On: %s\n", utils.FormatTime(checkpoint.CreatedAt)) fmt.Printf(" Step Number: %d\n", checkpoint.StepNumber) if checkpoint.Metrics != nil { fmt.Printf(" Full Validation Loss: %.6f\n", checkpoint.Metrics.FullValidLoss) @@ -302,7 +303,7 @@ func newOperationListCommand() *cobra.Command { for i, job := range jobs { fmt.Printf("\n%d. Job ID: %s | Status: %s %s | Model: %s | Fine-tuned: %s | Created: %s", - i+1, job.ID, getStatusSymbol(string(job.Status)), job.Status, job.BaseModel, formatFineTunedModel(job.FineTunedModel), job.CreatedAt) + i+1, job.ID, getStatusSymbol(string(job.Status)), job.Status, job.BaseModel, formatFineTunedModel(job.FineTunedModel), utils.FormatTime(job.CreatedAt)) } fmt.Printf("\ntotal jobs: %d\n", len(jobs)) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go b/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go index 05af770fafb..f10b3cfecf7 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go @@ -7,6 +7,8 @@ import ( "time" ) +const TimeFormat = "2006-01-02 15:04:05 UTC" + // UnixTimestampToUTC converts a Unix timestamp (seconds since epoch) to a UTC time.Time. // Returns zero time.Time if timestamp is 0. func UnixTimestampToUTC(timestamp int64) time.Time { @@ -15,3 +17,10 @@ func UnixTimestampToUTC(timestamp int64) time.Time { } return time.Unix(timestamp, 0).UTC() } + +func FormatTime(t time.Time) string { + if t.IsZero() { + return "" + } + return t.Format(TimeFormat) +} \ No newline at end of file From be5376eb27a9f314b83dc5a28cd1ea382b65c828 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Tue, 30 Dec 2025 17:34:15 +0530 Subject: [PATCH 04/13] Formatting, refactoring, logging --- .../internal/cmd/operations.go | 66 +++++++++++++------ .../internal/providers/azure/provider.go | 4 +- .../internal/providers/interface.go | 4 +- .../internal/providers/openai/conversions.go | 46 +++++++------ .../internal/providers/openai/provider.go | 27 ++++---- .../internal/services/finetune_service.go | 9 +-- .../internal/services/interface.go | 4 +- .../azure.ai.finetune/internal/utils/time.go | 5 +- .../azure.ai.finetune/pkg/models/finetune.go | 9 +-- 9 files changed, 108 insertions(+), 66 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index 2c0f040b8a6..ad057eeaee1 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -7,15 +7,17 @@ import ( "fmt" "strings" - "github.com/azure/azure-dev/cli/azd/pkg/azdext" - "github.com/azure/azure-dev/cli/azd/pkg/ux" "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "github.com/azure/azure-dev/cli/azd/pkg/ux" + FTYaml "azure.ai.finetune/internal/fine_tuning_yaml" "azure.ai.finetune/internal/services" JobWrapper "azure.ai.finetune/internal/tools" "azure.ai.finetune/internal/utils" + "azure.ai.finetune/pkg/models" ) func newOperationCommand() *cobra.Command { @@ -37,7 +39,27 @@ func newOperationCommand() *cobra.Command { } // getStatusSymbol returns a symbol representation for job status -func getStatusSymbol(status string) string { +func getStatusSymbol(status models.JobStatus) string { + switch status { + case models.StatusPending: + return "⌛" + case models.StatusQueued: + return "📚" + case models.StatusRunning: + return "🔄" + case models.StatusSucceeded: + return "✅" + case models.StatusFailed: + return "💥" + case models.StatusCancelled: + return "❌" + default: + return "❓" + } +} + +// getStatusSymbolFromString returns a symbol representation for job status +func getStatusSymbolFromString(status string) string { switch status { case "pending": return "⌛" @@ -140,12 +162,13 @@ func newOperationSubmitCommand() *cobra.Command { return cmd } +// newOperationShowCommand creates a command to show the fine-tuning job details func newOperationShowCommand() *cobra.Command { var jobID string cmd := &cobra.Command{ Use: "show", - Short: "show the fine tuning job details", + Short: "Show fine-tuning job details.", RunE: func(cmd *cobra.Command, args []string) error { ctx := azdext.WithAccessToken(cmd.Context()) azdClient, err := azdext.NewAzdClient() @@ -154,9 +177,9 @@ func newOperationShowCommand() *cobra.Command { } defer azdClient.Close() - // Show spinner while fetching jobs + // Show spinner while fetching job spinner := ux.NewSpinner(&ux.SpinnerOptions{ - Text: fmt.Sprintf("fetching fine-tuning job %s...", jobID), + Text: fmt.Sprintf("Fetching fine-tuning job %s...", jobID), }) if err := spinner.Start(ctx); err != nil { fmt.Printf("failed to start spinner: %v\n", err) @@ -176,10 +199,10 @@ func newOperationShowCommand() *cobra.Command { return err } - // Print job details - color.Green("\nfine-tuning job details\n") + // Display job details + color.Green("\nFine-tuning Job Details\n") fmt.Printf("Job ID: %s\n", job.ID) - fmt.Printf("Status: %s %s\n", getStatusSymbol(string(job.Status)), job.Status) + fmt.Printf("Status: %s %s\n", getStatusSymbol(job.Status), job.Status) fmt.Printf("Model: %s\n", job.Model) fmt.Printf("Fine-tuned Model: %s\n", formatFineTunedModel(job.FineTunedModel)) fmt.Printf("Created At: %s\n", utils.FormatTime(job.CreatedAt)) @@ -202,7 +225,7 @@ func newOperationShowCommand() *cobra.Command { // Fetch and print events eventsSpinner := ux.NewSpinner(&ux.SpinnerOptions{ - Text: "fetching job events...", + Text: "Fetching job events...", }) if err := eventsSpinner.Start(ctx); err != nil { fmt.Printf("failed to start spinner: %v\n", err) @@ -224,9 +247,9 @@ func newOperationShowCommand() *cobra.Command { } // Fetch and print checkpoints if job is completed - if job.Status == "succeeded" { + if job.Status == models.StatusSucceeded { checkpointsSpinner := ux.NewSpinner(&ux.SpinnerOptions{ - Text: "fetching job checkpoints...", + Text: "Fetching job checkpoints...", }) if err := checkpointsSpinner.Start(ctx); err != nil { fmt.Printf("failed to start spinner: %v\n", err) @@ -236,7 +259,7 @@ func newOperationShowCommand() *cobra.Command { _ = checkpointsSpinner.Stop(ctx) if err != nil { - fmt.Printf("warning: failed to fetch job checkpoints: %v\n", err) + fmt.Printf("WARNING: failed to fetch job checkpoints: %v\n", err) } else if checkpoints != nil && len(checkpoints.Data) > 0 { fmt.Println("\nJob Checkpoints:") for i, checkpoint := range checkpoints.Data { @@ -270,7 +293,7 @@ func newOperationListCommand() *cobra.Command { var after string cmd := &cobra.Command{ Use: "list", - Short: "list the fine tuning jobs", + Short: "List fine-tuning jobs.", RunE: func(cmd *cobra.Command, args []string) error { ctx := azdext.WithAccessToken(cmd.Context()) azdClient, err := azdext.NewAzdClient() @@ -281,7 +304,7 @@ func newOperationListCommand() *cobra.Command { // Show spinner while fetching jobs spinner := ux.NewSpinner(&ux.SpinnerOptions{ - Text: "fetching fine-tuning jobs...", + Text: "Fetching fine-tuning jobs...", }) if err := spinner.Start(ctx); err != nil { fmt.Printf("failed to start spinner: %v\n", err) @@ -301,18 +324,21 @@ func newOperationListCommand() *cobra.Command { return err } + // Display job list for i, job := range jobs { fmt.Printf("\n%d. Job ID: %s | Status: %s %s | Model: %s | Fine-tuned: %s | Created: %s", - i+1, job.ID, getStatusSymbol(string(job.Status)), job.Status, job.BaseModel, formatFineTunedModel(job.FineTunedModel), utils.FormatTime(job.CreatedAt)) + i+1, job.ID, getStatusSymbol(job.Status), job.Status, job.BaseModel, + formatFineTunedModel(job.FineTunedModel), utils.FormatTime(job.CreatedAt)) } - fmt.Printf("\ntotal jobs: %d\n", len(jobs)) + fmt.Printf("\nTotal jobs: %d\n", len(jobs)) return nil }, } - cmd.Flags().IntVarP(&limit, "top", "t", 50, "number of fine-tuning jobs to list") - cmd.Flags().StringVarP(&after, "after", "a", "", "cursor for pagination") + + cmd.Flags().IntVarP(&limit, "top", "t", 50, "Number of fine-tuning jobs to list") + cmd.Flags().StringVarP(&after, "after", "a", "", "Cursor for pagination") return cmd } @@ -369,7 +395,7 @@ func newOperationActionCommand() *cobra.Command { color.Green(fmt.Sprintf("\nSuccessfully %sd fine-tuning Job!\n", action)) fmt.Printf("Job ID: %s\n", job.Id) fmt.Printf("Model: %s\n", job.Model) - fmt.Printf("Status: %s %s\n", getStatusSymbol(job.Status), job.Status) + fmt.Printf("Status: %s %s\n", getStatusSymbolFromString(job.Status), job.Status) fmt.Printf("Created: %s\n", job.CreatedAt) if job.FineTunedModel != "" { fmt.Printf("Fine-tuned: %s\n", job.FineTunedModel) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go index 430da9b704e..fd5557a113d 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go @@ -50,13 +50,13 @@ func (p *AzureProvider) GetFineTuningJobDetails(ctx context.Context, jobID strin } // GetJobEvents retrieves events for a fine-tuning job -func (p *AzureProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) { +func (p *AzureProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { // TODO: Implement return nil, nil } // GetJobCheckpoints retrieves checkpoints for a fine-tuning job -func (p *AzureProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) { +func (p *AzureProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { // TODO: Implement return nil, nil } diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go index 8cecc614a28..d2aeb2df163 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go @@ -25,10 +25,10 @@ type FineTuningProvider interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a fine-tuning job - GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) + GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) // GetJobCheckpoints retrieves checkpoints for a fine-tuning job - GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) + GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) // PauseJob pauses a fine-tuning job PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go index f236b42d234..914524f0069 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/conversions.go @@ -4,10 +4,11 @@ package openai import ( - "azure.ai.finetune/internal/utils" - "azure.ai.finetune/pkg/models" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/packages/pagination" + + "azure.ai.finetune/internal/utils" + "azure.ai.finetune/pkg/models" ) // OpenAI Status Constants - matches OpenAI SDK values @@ -49,8 +50,9 @@ func convertOpenAIJobToModel(openaiJob openai.FineTuningJob) *models.FineTuningJ } } +// convertOpenAIJobToDetailModel converts OpenAI SDK job to detailed domain model func convertOpenAIJobToDetailModel(openaiJob *openai.FineTuningJob) *models.FineTuningJobDetail { - // Extract hyperparameters based on method type + // Extract hyperparameters from OpenAI job hyperparameters := &models.Hyperparameters{} hyperparameters.BatchSize = openaiJob.Hyperparameters.BatchSize.OfInt hyperparameters.LearningRateMultiplier = openaiJob.Hyperparameters.LearningRateMultiplier.OfFloat @@ -68,13 +70,15 @@ func convertOpenAIJobToDetailModel(openaiJob *openai.FineTuningJob) *models.Fine ValidationFile: openaiJob.ValidationFile, Hyperparameters: hyperparameters, } + return jobDetail } -func convertOpenAIJobEventsToModel(eventsList *pagination.CursorPage[openai.FineTuningJobEvent]) *models.JobEventsListContract { +// convertOpenAIJobEventsToModel converts OpenAI SDK job events to domain model +func convertOpenAIJobEventsToModel(eventsPage *pagination.CursorPage[openai.FineTuningJobEvent]) *models.JobEventsList { var events []models.JobEvent - for _, event := range eventsList.Data { - eventContract := models.JobEvent{ + for _, event := range eventsPage.Data { + jobEvent := models.JobEvent{ ID: event.ID, CreatedAt: utils.UnixTimestampToUTC(event.CreatedAt), Level: string(event.Level), @@ -82,36 +86,38 @@ func convertOpenAIJobEventsToModel(eventsList *pagination.CursorPage[openai.Fine Data: event.Data, Type: string(event.Type), } - events = append(events, eventContract) + events = append(events, jobEvent) } - return &models.JobEventsListContract{ + return &models.JobEventsList{ Data: events, - HasMore: eventsList.HasMore, + HasMore: eventsPage.HasMore, } } -func convertOpenAIJobCheckpointsToModel(openaiJobCheckpointsList *pagination.CursorPage[openai.FineTuningJobCheckpoint]) *models.JobCheckpointsListContract { +// convertOpenAIJobCheckpointsToModel converts OpenAI SDK job checkpoints to domain model +func convertOpenAIJobCheckpointsToModel(checkpointsPage *pagination.CursorPage[openai.FineTuningJobCheckpoint]) *models.JobCheckpointsList { var checkpoints []models.JobCheckpoint - for _, checkpoint := range openaiJobCheckpointsList.Data { + for _, checkpoint := range checkpointsPage.Data { metrics := &models.CheckpointMetrics{ - FullValidLoss: checkpoint.Metrics.FullValidLoss, + FullValidLoss: checkpoint.Metrics.FullValidLoss, FullValidMeanTokenAccuracy: checkpoint.Metrics.FullValidMeanTokenAccuracy, } jobCheckpoint := models.JobCheckpoint{ - ID: checkpoint.ID, - CreatedAt: utils.UnixTimestampToUTC(checkpoint.CreatedAt), + ID: checkpoint.ID, + CreatedAt: utils.UnixTimestampToUTC(checkpoint.CreatedAt), FineTunedModelCheckpoint: checkpoint.FineTunedModelCheckpoint, - Metrics: metrics, - FineTuningJobID: checkpoint.FineTuningJobID, - StepNumber: checkpoint.StepNumber, + Metrics: metrics, + FineTuningJobID: checkpoint.FineTuningJobID, + StepNumber: checkpoint.StepNumber, } checkpoints = append(checkpoints, jobCheckpoint) } - return &models.JobCheckpointsListContract{ - Data: checkpoints, - HasMore: openaiJobCheckpointsList.HasMore, + + return &models.JobCheckpointsList{ + Data: checkpoints, + HasMore: checkpointsPage.HasMore, } } diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go index 551e2ddf942..f0e99e0a19a 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go @@ -6,8 +6,9 @@ package openai import ( "context" - "azure.ai.finetune/pkg/models" "github.com/openai/openai-go/v3" + + "azure.ai.finetune/pkg/models" ) // OpenAIProvider implements the provider interface for OpenAI APIs @@ -54,6 +55,7 @@ func (p *OpenAIProvider) ListFineTuningJobs(ctx context.Context, limit int, afte finetuningJob := convertOpenAIJobToModel(job) jobs = append(jobs, finetuningJob) } + return jobs, nil } @@ -64,35 +66,38 @@ func (p *OpenAIProvider) GetFineTuningJobDetails(ctx context.Context, jobID stri return nil, err } finetuningJobDetail := convertOpenAIJobToDetailModel(job) + return finetuningJobDetail, nil } // GetJobEvents retrieves events for a fine-tuning job -func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) { - eventsList, err := p.client.FineTuning.Jobs.ListEvents( - ctx, - jobID, +func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { + eventsPage, err := p.client.FineTuning.Jobs.ListEvents( + ctx, + jobID, openai.FineTuningJobListEventsParams{}, ) if err != nil { return nil, err } - events := convertOpenAIJobEventsToModel(eventsList) + events := convertOpenAIJobEventsToModel(eventsPage) + return events, nil } // GetJobCheckpoints retrieves checkpoints for a fine-tuning job -func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) { - checkpointList, err := p.client.FineTuning.Jobs.Checkpoints.List( - ctx, - jobID, +func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { + checkpointsPage, err := p.client.FineTuning.Jobs.Checkpoints.List( + ctx, + jobID, openai.FineTuningJobCheckpointListParams{}, ) if err != nil { return nil, err } - checkpoints := convertOpenAIJobCheckpointsToModel(checkpointList) + checkpoints := convertOpenAIJobCheckpointsToModel(checkpointsPage) + return checkpoints, nil } diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go index 59fb91257b2..c6298f2b5ba 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go @@ -7,11 +7,12 @@ import ( "context" "fmt" + "github.com/azure/azure-dev/cli/azd/pkg/azdext" + "azure.ai.finetune/internal/providers" "azure.ai.finetune/internal/providers/factory" "azure.ai.finetune/internal/utils" "azure.ai.finetune/pkg/models" - "github.com/azure/azure-dev/cli/azd/pkg/azdext" ) // Ensure fineTuningServiceImpl implements FineTuningService interface @@ -84,17 +85,17 @@ func (s *fineTuningServiceImpl) GetFineTuningJobDetails(ctx context.Context, job } // GetJobEvents retrieves events for a job with filtering and pagination -func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) { +func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { eventsList, err := s.provider.GetJobEvents(ctx, jobID) if err != nil { return nil, fmt.Errorf("failed to get job events: %w", err) } - + return eventsList, nil } // GetJobCheckpoints retrieves checkpoints for a job with pagination -func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) { +func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { checkpointList, err := s.provider.GetJobCheckpoints(ctx, jobID) if err != nil { return nil, fmt.Errorf("failed to get job checkpoints: %w", err) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go index 726de3e1521..c4d20d13c9d 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go @@ -24,10 +24,10 @@ type FineTuningService interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a job with filtering and pagination - GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsListContract, error) + GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) // GetJobCheckpoints retrieves checkpoints for a job with pagination - GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsListContract, error) + GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) // PauseJob pauses a running job (if applicable) PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go b/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go index f10b3cfecf7..52b9d378333 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/utils/time.go @@ -7,6 +7,7 @@ import ( "time" ) +// TimeFormat defines the standard time format used for display output const TimeFormat = "2006-01-02 15:04:05 UTC" // UnixTimestampToUTC converts a Unix timestamp (seconds since epoch) to a UTC time.Time. @@ -18,9 +19,11 @@ func UnixTimestampToUTC(timestamp int64) time.Time { return time.Unix(timestamp, 0).UTC() } +// FormatTime formats a time.Time to the standard display format. +// Returns empty string if time is zero. func FormatTime(t time.Time) string { if t.IsZero() { return "" } return t.Format(TimeFormat) -} \ No newline at end of file +} diff --git a/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go b/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go index 9b01413f224..9a943580c52 100644 --- a/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go +++ b/cli/azd/extensions/azure.ai.finetune/pkg/models/finetune.go @@ -88,12 +88,12 @@ type JobEvent struct { Type string } -type JobEventsListContract struct { +// JobEventsList represents a paginated list of job events +type JobEventsList struct { Data []JobEvent HasMore bool } - // JobCheckpoint represents a checkpoint of a fine-tuning job type JobCheckpoint struct { ID string @@ -104,8 +104,9 @@ type JobCheckpoint struct { StepNumber int64 } -type JobCheckpointsListContract struct { - Data []JobCheckpoint +// JobCheckpointsList represents a list of job checkpoints +type JobCheckpointsList struct { + Data []JobCheckpoint HasMore bool } From 9051276b8e655ff61693dcc9747447cc02590e5e Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Tue, 30 Dec 2025 18:08:43 +0530 Subject: [PATCH 05/13] Add retry --- .../internal/services/finetune_service.go | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go index c6298f2b5ba..054583eee5c 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go @@ -76,7 +76,15 @@ func (s *fineTuningServiceImpl) ListFineTuningJobs(ctx context.Context, limit in // GetFineTuningJobDetails retrieves detailed information about a job func (s *fineTuningServiceImpl) GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) { - jobDetail, err := s.provider.GetFineTuningJobDetails(ctx, jobID) + var jobDetail *models.FineTuningJobDetail + + // Use retry utility for job detail operation + err := utils.RetryOperation(ctx, utils.DefaultRetryConfig(), func() error { + var err error + jobDetail, err = s.provider.GetFineTuningJobDetails(ctx, jobID) + return err + }) + if err != nil { return nil, fmt.Errorf("failed to get job details: %w", err) } @@ -86,7 +94,15 @@ func (s *fineTuningServiceImpl) GetFineTuningJobDetails(ctx context.Context, job // GetJobEvents retrieves events for a job with filtering and pagination func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { - eventsList, err := s.provider.GetJobEvents(ctx, jobID) + var eventsList *models.JobEventsList + + // Use retry utility for job events operation + err := utils.RetryOperation(ctx, utils.DefaultRetryConfig(), func() error { + var err error + eventsList, err = s.provider.GetJobEvents(ctx, jobID) + return err + }) + if err != nil { return nil, fmt.Errorf("failed to get job events: %w", err) } @@ -96,10 +112,19 @@ func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) // GetJobCheckpoints retrieves checkpoints for a job with pagination func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { - checkpointList, err := s.provider.GetJobCheckpoints(ctx, jobID) + var checkpointList *models.JobCheckpointsList + + // Use retry utility for job checkpoints operation + err := utils.RetryOperation(ctx, utils.DefaultRetryConfig(), func() error { + var err error + checkpointList, err = s.provider.GetJobCheckpoints(ctx, jobID) + return err + }) + if err != nil { return nil, fmt.Errorf("failed to get job checkpoints: %w", err) } + return checkpointList, nil } From deba7ce42b4f5ff8e02562de65ddaaacab032b87 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Tue, 30 Dec 2025 18:19:02 +0530 Subject: [PATCH 06/13] Nit --- .../extensions/azure.ai.finetune/internal/cmd/operations.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index ad057eeaee1..d24e75468ca 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -235,7 +235,8 @@ func newOperationShowCommand() *cobra.Command { _ = eventsSpinner.Stop(ctx) if err != nil { - fmt.Printf("WARNING: failed to fetch job events: %v\n", err) + fmt.Println() + return err } else if events != nil && len(events.Data) > 0 { fmt.Println("\nJob Events:") for i, event := range events.Data { @@ -259,7 +260,8 @@ func newOperationShowCommand() *cobra.Command { _ = checkpointsSpinner.Stop(ctx) if err != nil { - fmt.Printf("WARNING: failed to fetch job checkpoints: %v\n", err) + fmt.Println() + return err } else if checkpoints != nil && len(checkpoints.Data) > 0 { fmt.Println("\nJob Checkpoints:") for i, checkpoint := range checkpoints.Data { From d073a6a1150bd94755208f146c31dc192f95d35c Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 31 Dec 2025 09:29:09 +0530 Subject: [PATCH 07/13] Create status util --- .../internal/cmd/operations.go | 24 ++--------------- .../internal/utils/status.go | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 cli/azd/extensions/azure.ai.finetune/internal/utils/status.go diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index d24e75468ca..43b3a912028 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -38,26 +38,6 @@ func newOperationCommand() *cobra.Command { return cmd } -// getStatusSymbol returns a symbol representation for job status -func getStatusSymbol(status models.JobStatus) string { - switch status { - case models.StatusPending: - return "⌛" - case models.StatusQueued: - return "📚" - case models.StatusRunning: - return "🔄" - case models.StatusSucceeded: - return "✅" - case models.StatusFailed: - return "💥" - case models.StatusCancelled: - return "❌" - default: - return "❓" - } -} - // getStatusSymbolFromString returns a symbol representation for job status func getStatusSymbolFromString(status string) string { switch status { @@ -202,7 +182,7 @@ func newOperationShowCommand() *cobra.Command { // Display job details color.Green("\nFine-tuning Job Details\n") fmt.Printf("Job ID: %s\n", job.ID) - fmt.Printf("Status: %s %s\n", getStatusSymbol(job.Status), job.Status) + fmt.Printf("Status: %s %s\n", utils.GetStatusSymbol(job.Status), job.Status) fmt.Printf("Model: %s\n", job.Model) fmt.Printf("Fine-tuned Model: %s\n", formatFineTunedModel(job.FineTunedModel)) fmt.Printf("Created At: %s\n", utils.FormatTime(job.CreatedAt)) @@ -329,7 +309,7 @@ func newOperationListCommand() *cobra.Command { // Display job list for i, job := range jobs { fmt.Printf("\n%d. Job ID: %s | Status: %s %s | Model: %s | Fine-tuned: %s | Created: %s", - i+1, job.ID, getStatusSymbol(job.Status), job.Status, job.BaseModel, + i+1, job.ID, utils.GetStatusSymbol(job.Status), job.Status, job.BaseModel, formatFineTunedModel(job.FineTunedModel), utils.FormatTime(job.CreatedAt)) } diff --git a/cli/azd/extensions/azure.ai.finetune/internal/utils/status.go b/cli/azd/extensions/azure.ai.finetune/internal/utils/status.go new file mode 100644 index 00000000000..25e961dbcde --- /dev/null +++ b/cli/azd/extensions/azure.ai.finetune/internal/utils/status.go @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package utils + +import "azure.ai.finetune/pkg/models" + +// getStatusSymbol returns a symbol representation for job status +func GetStatusSymbol(status models.JobStatus) string { + switch status { + case models.StatusPending: + return "⌛" + case models.StatusQueued: + return "📚" + case models.StatusRunning: + return "🔄" + case models.StatusSucceeded: + return "✅" + case models.StatusFailed: + return "💥" + case models.StatusCancelled: + return "❌" + default: + return "❓" + } +} From 58ea45c5d1210d661855319bcf6621c8efd903da Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 31 Dec 2025 09:41:02 +0530 Subject: [PATCH 08/13] Add limit and after flags for job events and checkpoints --- .../azure.ai.finetune/internal/cmd/operations.go | 9 +++++++-- .../internal/providers/azure/provider.go | 4 ++-- .../internal/providers/interface.go | 4 ++-- .../internal/providers/openai/provider.go | 14 ++++++++++---- .../internal/services/finetune_service.go | 8 ++++---- .../internal/services/interface.go | 4 ++-- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index 43b3a912028..973b99caa72 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -145,6 +145,8 @@ func newOperationSubmitCommand() *cobra.Command { // newOperationShowCommand creates a command to show the fine-tuning job details func newOperationShowCommand() *cobra.Command { var jobID string + var limit int + var after string cmd := &cobra.Command{ Use: "show", @@ -211,7 +213,7 @@ func newOperationShowCommand() *cobra.Command { fmt.Printf("failed to start spinner: %v\n", err) } - events, err := fineTuneSvc.GetJobEvents(ctx, jobID) + events, err := fineTuneSvc.GetJobEvents(ctx, jobID, limit, after) _ = eventsSpinner.Stop(ctx) if err != nil { @@ -236,7 +238,7 @@ func newOperationShowCommand() *cobra.Command { fmt.Printf("failed to start spinner: %v\n", err) } - checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID) + checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID, limit, after) _ = checkpointsSpinner.Stop(ctx) if err != nil { @@ -264,6 +266,9 @@ func newOperationShowCommand() *cobra.Command { return nil }, } + + cmd.Flags().IntVarP(&limit, "top", "t", 50, "Number of fine-tuning jobs to list") + cmd.Flags().StringVarP(&after, "after", "a", "", "Cursor for pagination") cmd.Flags().StringVarP(&jobID, "job-id", "i", "", "Fine-tuning job ID") cmd.MarkFlagRequired("job-id") return cmd diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go index fd5557a113d..46fdd92e358 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/azure/provider.go @@ -50,13 +50,13 @@ func (p *AzureProvider) GetFineTuningJobDetails(ctx context.Context, jobID strin } // GetJobEvents retrieves events for a fine-tuning job -func (p *AzureProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { +func (p *AzureProvider) GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) { // TODO: Implement return nil, nil } // GetJobCheckpoints retrieves checkpoints for a fine-tuning job -func (p *AzureProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { +func (p *AzureProvider) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) { // TODO: Implement return nil, nil } diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go index d2aeb2df163..f7894daba8e 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go @@ -25,10 +25,10 @@ type FineTuningProvider interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a fine-tuning job - GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) + GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) // GetJobCheckpoints retrieves checkpoints for a fine-tuning job - GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) + GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) // PauseJob pauses a fine-tuning job PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go index f0e99e0a19a..5d080008a62 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go @@ -71,11 +71,14 @@ func (p *OpenAIProvider) GetFineTuningJobDetails(ctx context.Context, jobID stri } // GetJobEvents retrieves events for a fine-tuning job -func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { +func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) { eventsPage, err := p.client.FineTuning.Jobs.ListEvents( ctx, jobID, - openai.FineTuningJobListEventsParams{}, + openai.FineTuningJobListEventsParams{ + Limit: openai.Int(int64(limit)), // optional pagination control + After: openai.String(after), + }, ) if err != nil { return nil, err @@ -87,11 +90,14 @@ func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string) (*model } // GetJobCheckpoints retrieves checkpoints for a fine-tuning job -func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { +func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) { checkpointsPage, err := p.client.FineTuning.Jobs.Checkpoints.List( ctx, jobID, - openai.FineTuningJobCheckpointListParams{}, + openai.FineTuningJobCheckpointListParams{ + Limit: openai.Int(int64(limit)), // optional pagination control + After: openai.String(after), + }, ) if err != nil { return nil, err diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go index 054583eee5c..350c6a08e4b 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go @@ -93,13 +93,13 @@ func (s *fineTuningServiceImpl) GetFineTuningJobDetails(ctx context.Context, job } // GetJobEvents retrieves events for a job with filtering and pagination -func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { +func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) { var eventsList *models.JobEventsList // Use retry utility for job events operation err := utils.RetryOperation(ctx, utils.DefaultRetryConfig(), func() error { var err error - eventsList, err = s.provider.GetJobEvents(ctx, jobID) + eventsList, err = s.provider.GetJobEvents(ctx, jobID, limit, after) return err }) @@ -111,13 +111,13 @@ func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) } // GetJobCheckpoints retrieves checkpoints for a job with pagination -func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { +func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) { var checkpointList *models.JobCheckpointsList // Use retry utility for job checkpoints operation err := utils.RetryOperation(ctx, utils.DefaultRetryConfig(), func() error { var err error - checkpointList, err = s.provider.GetJobCheckpoints(ctx, jobID) + checkpointList, err = s.provider.GetJobCheckpoints(ctx, jobID, limit, after) return err }) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go index c4d20d13c9d..114ca0bb914 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go @@ -24,10 +24,10 @@ type FineTuningService interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a job with filtering and pagination - GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) + GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) // GetJobCheckpoints retrieves checkpoints for a job with pagination - GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) + GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) // PauseJob pauses a running job (if applicable) PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error) From a7932348ee6f19e51f370953ba8afe1de398a6d3 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 31 Dec 2025 09:49:21 +0530 Subject: [PATCH 09/13] Add distinct flags for checkpoints and events --- .../internal/cmd/operations.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index 973b99caa72..aee1526b7c4 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -145,8 +145,10 @@ func newOperationSubmitCommand() *cobra.Command { // newOperationShowCommand creates a command to show the fine-tuning job details func newOperationShowCommand() *cobra.Command { var jobID string - var limit int - var after string + var eventsLimit int + var eventsAfter string + var checkpointsLimit int + var checkpointsAfter string cmd := &cobra.Command{ Use: "show", @@ -213,7 +215,7 @@ func newOperationShowCommand() *cobra.Command { fmt.Printf("failed to start spinner: %v\n", err) } - events, err := fineTuneSvc.GetJobEvents(ctx, jobID, limit, after) + events, err := fineTuneSvc.GetJobEvents(ctx, jobID, eventsLimit, eventsAfter) _ = eventsSpinner.Stop(ctx) if err != nil { @@ -238,7 +240,7 @@ func newOperationShowCommand() *cobra.Command { fmt.Printf("failed to start spinner: %v\n", err) } - checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID, limit, after) + checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID, checkpointsLimit, checkpointsAfter) _ = checkpointsSpinner.Stop(ctx) if err != nil { @@ -267,8 +269,12 @@ func newOperationShowCommand() *cobra.Command { }, } - cmd.Flags().IntVarP(&limit, "top", "t", 50, "Number of fine-tuning jobs to list") - cmd.Flags().StringVarP(&after, "after", "a", "", "Cursor for pagination") + cmd.Flags().IntVarP(&eventsLimit, "top", "t", 50, "Number of events to retrieve") + cmd.Flags().StringVarP(&eventsAfter, "after", "a", "", "Identifier for the last event from the previous pagination request") + + cmd.Flags().IntVarP(&checkpointsLimit, "top", "t", 50, "Number of checkpoints to retrieve") + cmd.Flags().StringVarP(&checkpointsAfter, "after", "a", "", "Identifier for the last checkpoint ID from the previous pagination request") + cmd.Flags().StringVarP(&jobID, "job-id", "i", "", "Fine-tuning job ID") cmd.MarkFlagRequired("job-id") return cmd From e758380bff868503b9854d668dc719fd0e346fd8 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 31 Dec 2025 09:54:41 +0530 Subject: [PATCH 10/13] Fix runtime error due to duplicate flags --- .../azure.ai.finetune/internal/cmd/operations.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index aee1526b7c4..8fe1bed810e 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -269,14 +269,15 @@ func newOperationShowCommand() *cobra.Command { }, } - cmd.Flags().IntVarP(&eventsLimit, "top", "t", 50, "Number of events to retrieve") - cmd.Flags().StringVarP(&eventsAfter, "after", "a", "", "Identifier for the last event from the previous pagination request") - - cmd.Flags().IntVarP(&checkpointsLimit, "top", "t", 50, "Number of checkpoints to retrieve") - cmd.Flags().StringVarP(&checkpointsAfter, "after", "a", "", "Identifier for the last checkpoint ID from the previous pagination request") + cmd.Flags().IntVar(&eventsLimit, "events-top", 50, "Number of events to retrieve") + cmd.Flags().StringVar(&eventsAfter, "events-after", "", "Identifier for the last event from the previous pagination request") + cmd.Flags().IntVar(&checkpointsLimit, "checkpoints-top", 50, "Number of checkpoints to retrieve") + cmd.Flags().StringVar(&checkpointsAfter, "checkpoints-after", "", "Identifier for the last checkpoint ID from the previous pagination request") + cmd.Flags().StringVarP(&jobID, "job-id", "i", "", "Fine-tuning job ID") cmd.MarkFlagRequired("job-id") + return cmd } From 92f2b7e187c8c3dfad8d89ac346fe75d47a36421 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 31 Dec 2025 10:11:56 +0530 Subject: [PATCH 11/13] Add eventId in display --- .../azure.ai.finetune/internal/cmd/operations.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index 8fe1bed810e..e73d4ce723f 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -182,7 +182,7 @@ func newOperationShowCommand() *cobra.Command { fmt.Println() return err } - + // Display job details color.Green("\nFine-tuning Job Details\n") fmt.Printf("Job ID: %s\n", job.ID) @@ -224,7 +224,8 @@ func newOperationShowCommand() *cobra.Command { } else if events != nil && len(events.Data) > 0 { fmt.Println("\nJob Events:") for i, event := range events.Data { - fmt.Printf(" %d. [%s] %s - %s\n", i+1, event.Level, utils.FormatTime(event.CreatedAt), event.Message) + fmt.Printf(" %d. Event ID: %s\n", i+1, event.ID) + fmt.Printf(" [%s] %s - %s\n", event.Level, utils.FormatTime(event.CreatedAt), event.Message) } if events.HasMore { fmt.Println(" ... (more events available)") @@ -268,16 +269,16 @@ func newOperationShowCommand() *cobra.Command { return nil }, } - + cmd.Flags().IntVar(&eventsLimit, "events-top", 50, "Number of events to retrieve") cmd.Flags().StringVar(&eventsAfter, "events-after", "", "Identifier for the last event from the previous pagination request") cmd.Flags().IntVar(&checkpointsLimit, "checkpoints-top", 50, "Number of checkpoints to retrieve") cmd.Flags().StringVar(&checkpointsAfter, "checkpoints-after", "", "Identifier for the last checkpoint ID from the previous pagination request") - + cmd.Flags().StringVarP(&jobID, "job-id", "i", "", "Fine-tuning job ID") cmd.MarkFlagRequired("job-id") - + return cmd } @@ -314,14 +315,14 @@ func newOperationListCommand() *cobra.Command { jobs, err := fineTuneSvc.ListFineTuningJobs(ctx, limit, after) _ = spinner.Stop(ctx) if err != nil { - fmt.Println() + fmt.Println() return err } // Display job list for i, job := range jobs { fmt.Printf("\n%d. Job ID: %s | Status: %s %s | Model: %s | Fine-tuned: %s | Created: %s", - i+1, job.ID, utils.GetStatusSymbol(job.Status), job.Status, job.BaseModel, + i+1, job.ID, utils.GetStatusSymbol(job.Status), job.Status, job.BaseModel, formatFineTunedModel(job.FineTunedModel), utils.FormatTime(job.CreatedAt)) } From 10a100bbbbfbac8a2b14e4a7b7978bea0a591245 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Wed, 31 Dec 2025 10:31:59 +0530 Subject: [PATCH 12/13] Handle edge case of no events/checkpoints after the given identifier --- .../extensions/azure.ai.finetune/internal/cmd/operations.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index e73d4ce723f..e6a9cddb7a3 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -230,6 +230,8 @@ func newOperationShowCommand() *cobra.Command { if events.HasMore { fmt.Println(" ... (more events available)") } + } else if eventsAfter != "" { + fmt.Println("\nNo events found after the specified identifier") } // Fetch and print checkpoints if job is completed @@ -261,6 +263,8 @@ func newOperationShowCommand() *cobra.Command { if checkpoints.HasMore { fmt.Println(" ... (more checkpoints available)") } + } else if checkpointsAfter != "" { + fmt.Println("\nNo checkpoints found after the specified identifier") } } From 9e14a468d12aadf295d02512e1e3c673aaba50d1 Mon Sep 17 00:00:00 2001 From: Saanika Gupta Date: Fri, 2 Jan 2026 12:04:17 +0530 Subject: [PATCH 13/13] Remove limit and after flags for events and checkpoints --- .../internal/cmd/operations.go | 18 ++---------------- .../internal/providers/interface.go | 4 ++-- .../internal/providers/openai/provider.go | 14 ++++---------- .../internal/services/finetune_service.go | 8 ++++---- .../internal/services/interface.go | 4 ++-- 5 files changed, 14 insertions(+), 34 deletions(-) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go index e6a9cddb7a3..bfb574eb7db 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/operations.go @@ -145,10 +145,6 @@ func newOperationSubmitCommand() *cobra.Command { // newOperationShowCommand creates a command to show the fine-tuning job details func newOperationShowCommand() *cobra.Command { var jobID string - var eventsLimit int - var eventsAfter string - var checkpointsLimit int - var checkpointsAfter string cmd := &cobra.Command{ Use: "show", @@ -215,7 +211,7 @@ func newOperationShowCommand() *cobra.Command { fmt.Printf("failed to start spinner: %v\n", err) } - events, err := fineTuneSvc.GetJobEvents(ctx, jobID, eventsLimit, eventsAfter) + events, err := fineTuneSvc.GetJobEvents(ctx, jobID) _ = eventsSpinner.Stop(ctx) if err != nil { @@ -230,8 +226,6 @@ func newOperationShowCommand() *cobra.Command { if events.HasMore { fmt.Println(" ... (more events available)") } - } else if eventsAfter != "" { - fmt.Println("\nNo events found after the specified identifier") } // Fetch and print checkpoints if job is completed @@ -243,7 +237,7 @@ func newOperationShowCommand() *cobra.Command { fmt.Printf("failed to start spinner: %v\n", err) } - checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID, checkpointsLimit, checkpointsAfter) + checkpoints, err := fineTuneSvc.GetJobCheckpoints(ctx, jobID) _ = checkpointsSpinner.Stop(ctx) if err != nil { @@ -263,8 +257,6 @@ func newOperationShowCommand() *cobra.Command { if checkpoints.HasMore { fmt.Println(" ... (more checkpoints available)") } - } else if checkpointsAfter != "" { - fmt.Println("\nNo checkpoints found after the specified identifier") } } @@ -274,12 +266,6 @@ func newOperationShowCommand() *cobra.Command { }, } - cmd.Flags().IntVar(&eventsLimit, "events-top", 50, "Number of events to retrieve") - cmd.Flags().StringVar(&eventsAfter, "events-after", "", "Identifier for the last event from the previous pagination request") - - cmd.Flags().IntVar(&checkpointsLimit, "checkpoints-top", 50, "Number of checkpoints to retrieve") - cmd.Flags().StringVar(&checkpointsAfter, "checkpoints-after", "", "Identifier for the last checkpoint ID from the previous pagination request") - cmd.Flags().StringVarP(&jobID, "job-id", "i", "", "Fine-tuning job ID") cmd.MarkFlagRequired("job-id") diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go index f7894daba8e..d2aeb2df163 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/interface.go @@ -25,10 +25,10 @@ type FineTuningProvider interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a fine-tuning job - GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) + GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) // GetJobCheckpoints retrieves checkpoints for a fine-tuning job - GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) + GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) // PauseJob pauses a fine-tuning job PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go index 5d080008a62..f0e99e0a19a 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/openai/provider.go @@ -71,14 +71,11 @@ func (p *OpenAIProvider) GetFineTuningJobDetails(ctx context.Context, jobID stri } // GetJobEvents retrieves events for a fine-tuning job -func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) { +func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { eventsPage, err := p.client.FineTuning.Jobs.ListEvents( ctx, jobID, - openai.FineTuningJobListEventsParams{ - Limit: openai.Int(int64(limit)), // optional pagination control - After: openai.String(after), - }, + openai.FineTuningJobListEventsParams{}, ) if err != nil { return nil, err @@ -90,14 +87,11 @@ func (p *OpenAIProvider) GetJobEvents(ctx context.Context, jobID string, limit i } // GetJobCheckpoints retrieves checkpoints for a fine-tuning job -func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) { +func (p *OpenAIProvider) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { checkpointsPage, err := p.client.FineTuning.Jobs.Checkpoints.List( ctx, jobID, - openai.FineTuningJobCheckpointListParams{ - Limit: openai.Int(int64(limit)), // optional pagination control - After: openai.String(after), - }, + openai.FineTuningJobCheckpointListParams{}, ) if err != nil { return nil, err diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go index 350c6a08e4b..054583eee5c 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/finetune_service.go @@ -93,13 +93,13 @@ func (s *fineTuningServiceImpl) GetFineTuningJobDetails(ctx context.Context, job } // GetJobEvents retrieves events for a job with filtering and pagination -func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) { +func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) { var eventsList *models.JobEventsList // Use retry utility for job events operation err := utils.RetryOperation(ctx, utils.DefaultRetryConfig(), func() error { var err error - eventsList, err = s.provider.GetJobEvents(ctx, jobID, limit, after) + eventsList, err = s.provider.GetJobEvents(ctx, jobID) return err }) @@ -111,13 +111,13 @@ func (s *fineTuningServiceImpl) GetJobEvents(ctx context.Context, jobID string, } // GetJobCheckpoints retrieves checkpoints for a job with pagination -func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) { +func (s *fineTuningServiceImpl) GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) { var checkpointList *models.JobCheckpointsList // Use retry utility for job checkpoints operation err := utils.RetryOperation(ctx, utils.DefaultRetryConfig(), func() error { var err error - checkpointList, err = s.provider.GetJobCheckpoints(ctx, jobID, limit, after) + checkpointList, err = s.provider.GetJobCheckpoints(ctx, jobID) return err }) diff --git a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go index 114ca0bb914..c4d20d13c9d 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/services/interface.go @@ -24,10 +24,10 @@ type FineTuningService interface { GetFineTuningJobDetails(ctx context.Context, jobID string) (*models.FineTuningJobDetail, error) // GetJobEvents retrieves events for a job with filtering and pagination - GetJobEvents(ctx context.Context, jobID string, limit int, after string) (*models.JobEventsList, error) + GetJobEvents(ctx context.Context, jobID string) (*models.JobEventsList, error) // GetJobCheckpoints retrieves checkpoints for a job with pagination - GetJobCheckpoints(ctx context.Context, jobID string, limit int, after string) (*models.JobCheckpointsList, error) + GetJobCheckpoints(ctx context.Context, jobID string) (*models.JobCheckpointsList, error) // PauseJob pauses a running job (if applicable) PauseJob(ctx context.Context, jobID string) (*models.FineTuningJob, error)