From 1113407eb63567da54be5c74056af25a10c2fc25 Mon Sep 17 00:00:00 2001 From: John Miller Date: Fri, 10 Apr 2026 08:29:41 -0400 Subject: [PATCH 1/2] Persist first deployment name in AZURE_AI_MODEL_DEPLOYMENT_NAME environment variable. Fixes #7632. --- .../azure.ai.agents/internal/cmd/init_models.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go index a021632fe00..22c0682e0c8 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go @@ -791,6 +791,17 @@ func (a *InitAction) ProcessModels(ctx context.Context, manifest *agent_yaml.Age return nil, nil, fmt.Errorf("failed to inject deployment names into manifest: %w", err) } + // Persist the first deployment name so templates and agent code can reference it + // via the AZURE_AI_MODEL_DEPLOYMENT_NAME environment variable. + if len(deploymentDetails) > 0 { + if err := setEnvValue( + ctx, a.azdClient, a.environment.Name, + "AZURE_AI_MODEL_DEPLOYMENT_NAME", deploymentDetails[0].Name, + ); err != nil { + return nil, nil, fmt.Errorf("failed to set AZURE_AI_MODEL_DEPLOYMENT_NAME: %w", err) + } + } + fmt.Println("Model deployment details processed and injected into agent definition. Deployment details can also be found in the JSON formatted AI_PROJECT_DEPLOYMENTS environment variable.") return updatedManifest, deploymentDetails, nil From 723b6690a35c0b1ba1425cde984a1bd2b5e410cf Mon Sep 17 00:00:00 2001 From: John Miller Date: Fri, 10 Apr 2026 12:14:30 -0400 Subject: [PATCH 2/2] Refactor deployment name persistence and add unit tests for `persistFirstDeploymentName` --- .../internal/cmd/init_models.go | 32 +++++-- .../internal/cmd/init_models_test.go | 90 +++++++++++++++++++ 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go index 22c0682e0c8..d31bc6aa410 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models.go @@ -791,18 +791,32 @@ func (a *InitAction) ProcessModels(ctx context.Context, manifest *agent_yaml.Age return nil, nil, fmt.Errorf("failed to inject deployment names into manifest: %w", err) } - // Persist the first deployment name so templates and agent code can reference it - // via the AZURE_AI_MODEL_DEPLOYMENT_NAME environment variable. - if len(deploymentDetails) > 0 { - if err := setEnvValue( - ctx, a.azdClient, a.environment.Name, - "AZURE_AI_MODEL_DEPLOYMENT_NAME", deploymentDetails[0].Name, - ); err != nil { - return nil, nil, fmt.Errorf("failed to set AZURE_AI_MODEL_DEPLOYMENT_NAME: %w", err) - } + setEnv := func(ctx context.Context, key, value string) error { + return setEnvValue(ctx, a.azdClient, a.environment.Name, key, value) + } + if err := persistFirstDeploymentName(ctx, setEnv, deploymentDetails); err != nil { + return nil, nil, fmt.Errorf("failed to set AZURE_AI_MODEL_DEPLOYMENT_NAME: %w", err) } fmt.Println("Model deployment details processed and injected into agent definition. Deployment details can also be found in the JSON formatted AI_PROJECT_DEPLOYMENTS environment variable.") return updatedManifest, deploymentDetails, nil } + +// envValueSetter writes a single key-value pair to the azd environment. +type envValueSetter func(ctx context.Context, key, value string) error + +// persistFirstDeploymentName persists the first deployment's name as +// AZURE_AI_MODEL_DEPLOYMENT_NAME so templates and agent code can reference it. +// It is a no-op when the deployments slice is empty. +func persistFirstDeploymentName( + ctx context.Context, + setEnv envValueSetter, + deployments []project.Deployment, +) error { + if len(deployments) == 0 { + return nil + } + + return setEnv(ctx, "AZURE_AI_MODEL_DEPLOYMENT_NAME", deployments[0].Name) +} diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models_test.go index 1c3b3993913..50759e17453 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_models_test.go @@ -4,10 +4,14 @@ package cmd import ( + "azureaiagent/internal/project" + "context" + "errors" "testing" "github.com/azure/azure-dev/cli/azd/pkg/azdext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestResolveNoPromptCapacity(t *testing.T) { @@ -172,3 +176,89 @@ func TestSkuPriority(t *testing.T) { }) } } + +func TestPersistFirstDeploymentName(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + deployments []project.Deployment + setEnvErr error + wantCalled bool + wantKey string + wantValue string + wantErr bool + }{ + { + name: "empty deployments does not call setter", + deployments: []project.Deployment{}, + wantCalled: false, + }, + { + name: "nil deployments does not call setter", + deployments: nil, + wantCalled: false, + }, + { + name: "single deployment persists its name", + deployments: []project.Deployment{ + {Name: "gpt-4o"}, + }, + wantCalled: true, + wantKey: "AZURE_AI_MODEL_DEPLOYMENT_NAME", + wantValue: "gpt-4o", + }, + { + name: "multiple deployments persists first name only", + deployments: []project.Deployment{ + {Name: "gpt-4o"}, + {Name: "text-embedding-ada-002"}, + }, + wantCalled: true, + wantKey: "AZURE_AI_MODEL_DEPLOYMENT_NAME", + wantValue: "gpt-4o", + }, + { + name: "setter error is propagated", + deployments: []project.Deployment{ + {Name: "gpt-4o"}, + }, + setEnvErr: errors.New("grpc unavailable"), + wantCalled: true, + wantKey: "AZURE_AI_MODEL_DEPLOYMENT_NAME", + wantValue: "gpt-4o", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var called bool + var gotKey, gotValue string + + setter := func(_ context.Context, key, value string) error { + called = true + gotKey = key + gotValue = value + return tt.setEnvErr + } + + err := persistFirstDeploymentName(t.Context(), setter, tt.deployments) + + assert.Equal(t, tt.wantCalled, called, "setter call expectation mismatch") + + if tt.wantCalled { + assert.Equal(t, tt.wantKey, gotKey) + assert.Equal(t, tt.wantValue, gotValue) + } + + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +}