diff --git a/cli/azd/extensions/azure.ai.finetune/README.md b/cli/azd/extensions/azure.ai.finetune/README.md index 09d2eb6b8e8..fc3b38c37eb 100644 --- a/cli/azd/extensions/azure.ai.finetune/README.md +++ b/cli/azd/extensions/azure.ai.finetune/README.md @@ -1,3 +1,3 @@ -# `azd` Demo Extension +# `azd` Finetune Extension -An AZD Demo extension +An AZD Finetune extension diff --git a/cli/azd/extensions/azure.ai.finetune/internal/cmd/init.go b/cli/azd/extensions/azure.ai.finetune/internal/cmd/init.go index cc211665842..3b20f0c9782 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/cmd/init.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/cmd/init.go @@ -316,6 +316,15 @@ func ensureEnvironment(ctx context.Context, flags *initFlags, azdClient *azdext. return nil, fmt.Errorf("failed to set AZURE_ACCOUNT_NAME in azd environment: %w", err) } + _, err = azdClient.Environment().SetValue(ctx, &azdext.SetEnvRequest{ + EnvName: existingEnv.Name, + Key: "AZURE_PROJECT_NAME", + Value: foundryProject.AiProjectName, + }) + if err != nil { + return nil, fmt.Errorf("failed to set AZURE_PROJECT_NAME in azd environment: %w", err) + } + _, err = azdClient.Environment().SetValue(ctx, &azdext.SetEnvRequest{ EnvName: existingEnv.Name, Key: "AZURE_LOCATION", @@ -513,6 +522,12 @@ func ensureAzureContext( Value: fpDetails.AiAccountName, }) + _, err = azdClient.Environment().SetValue(ctx, &azdext.SetEnvRequest{ + EnvName: env.Name, + Key: "AZURE_PROJECT_NAME", + Value: fpDetails.AiProjectName, + }) + location := *projectResp.Location // Set the location in the environment diff --git a/cli/azd/extensions/azure.ai.finetune/internal/providers/factory/provider_factory.go b/cli/azd/extensions/azure.ai.finetune/internal/providers/factory/provider_factory.go index 0294287189e..d29413dddd1 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/providers/factory/provider_factory.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/providers/factory/provider_factory.go @@ -6,25 +6,28 @@ package factory import ( "context" "fmt" + "net/http" "azure.ai.finetune/internal/providers" azureprovider "azure.ai.finetune/internal/providers/azure" openaiprovider "azure.ai.finetune/internal/providers/openai" "azure.ai.finetune/internal/utils" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices" "github.com/azure/azure-dev/cli/azd/pkg/azdext" "github.com/openai/openai-go/v3" - "github.com/openai/openai-go/v3/azure" "github.com/openai/openai-go/v3/option" ) const ( // OpenAI API version for Azure cognitive services - apiVersion = "2025-04-01-preview" + DefaultApiVersion = "2025-11-15-preview" // Azure cognitive services endpoint URL pattern - azureCognitiveServicesEndpoint = "https://%s.cognitiveservices.azure.com/openai" + DefaultCognitiveServicesEndpoint = "https://%s.services.ai.azure.com/api/projects/%s" + DefaultAzureFinetuningScope = "https://ai.azure.com/.default" ) func GetOpenAIClientFromAzdClient(ctx context.Context, azdClient *azdext.AzdClient) (*openai.Client, error) { @@ -53,17 +56,57 @@ func GetOpenAIClientFromAzdClient(ctx context.Context, azdClient *azdext.AzdClie // Get Azure credentials and endpoint - TODO // You'll need to get these from your environment or config accountName := envValueMap[utils.EnvAzureAccountName] - endpoint := fmt.Sprintf(azureCognitiveServicesEndpoint, accountName) + projectName := envValueMap[utils.EnvAzureOpenAIProjectName] + endpoint := envValueMap[utils.EnvFinetuningRoute] + if endpoint == "" { + endpoint = fmt.Sprintf(DefaultCognitiveServicesEndpoint, accountName, projectName) + } + + apiVersion := envValueMap[utils.EnvAPIVersion] + if apiVersion == "" { + apiVersion = DefaultApiVersion + } + + scope := envValueMap[utils.EnvFineturningTokenScope] + if scope == "" { + scope = DefaultAzureFinetuningScope + } // Create OpenAI client client := openai.NewClient( //azure.WithEndpoint(endpoint, apiVersion), option.WithBaseURL(endpoint), option.WithQuery("api-version", apiVersion), - azure.WithTokenCredential(credential), + WithTokenCredential(credential, scope), ) return &client, nil } +// WithTokenCredential configures this client to authenticate using an [Azure Identity] TokenCredential. +// This function should be paired with a call to [WithEndpoint] to point to your Azure OpenAI instance. +// +// [Azure Identity]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity +func WithTokenCredential(tokenCredential azcore.TokenCredential, scope string) option.RequestOption { + bearerTokenPolicy := runtime.NewBearerTokenPolicy(tokenCredential, []string{scope}, nil) + // add in a middleware that uses the bearer token generated from the token credential + return option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) { + pipeline := runtime.NewPipeline("azopenai-extensions", version, runtime.PipelineOptions{}, &policy.ClientOptions{ + InsecureAllowCredentialWithHTTP: true, // allow for plain HTTP proxies, etc.. + PerRetryPolicies: []policy.Policy{ + bearerTokenPolicy, + policyAdapter(next), + }, + }) + + req2, err := runtime.NewRequestFromRequest(req) + + if err != nil { + return nil, err + } + + return pipeline.Do(req2) + }) +} + // NewFineTuningProvider creates a FineTuningProvider based on provider type func NewFineTuningProvider(ctx context.Context, azdClient *azdext.AzdClient) (providers.FineTuningProvider, error) { client, err := GetOpenAIClientFromAzdClient(ctx, azdClient) @@ -82,3 +125,11 @@ func NewModelDeploymentProvider(subscriptionId string, credential azcore.TokenCr } return azureprovider.NewAzureProvider(clientFactory), err } + +type policyAdapter option.MiddlewareNext + +func (mp policyAdapter) Do(req *policy.Request) (*http.Response, error) { + return (option.MiddlewareNext)(mp)(req.Raw()) +} + +const version = "v.0.1.0" diff --git a/cli/azd/extensions/azure.ai.finetune/internal/utils/environment.go b/cli/azd/extensions/azure.ai.finetune/internal/utils/environment.go index 6e5b3c81204..e3e177f4c4c 100644 --- a/cli/azd/extensions/azure.ai.finetune/internal/utils/environment.go +++ b/cli/azd/extensions/azure.ai.finetune/internal/utils/environment.go @@ -11,10 +11,14 @@ import ( ) const ( - EnvAzureTenantID = "AZURE_TENANT_ID" - EnvAzureSubscriptionID = "AZURE_SUBSCRIPTION_ID" - EnvAzureLocation = "AZURE_LOCATION" - EnvAzureAccountName = "AZURE_ACCOUNT_NAME" + EnvAzureTenantID = "AZURE_TENANT_ID" + EnvAzureSubscriptionID = "AZURE_SUBSCRIPTION_ID" + EnvAzureLocation = "AZURE_LOCATION" + EnvAzureAccountName = "AZURE_ACCOUNT_NAME" + EnvAzureOpenAIProjectName = "AZURE_PROJECT_NAME" + EnvAPIVersion = "AZURE_API_VERSION" + EnvFinetuningRoute = "AZURE_FINETUNING_ROUTE" + EnvFineturningTokenScope = "AZURE_FINETUNING_TOKEN_SCOPE" ) // GetEnvironmentValues retrieves Azure environment configuration from azd client.