Skip to content

feat: dynamic parameter references in usageName metadata for quota-based location filtering#6928

Open
Copilot wants to merge 7 commits intomainfrom
copilot/vscode-mm59wsrj-ulwy
Open

feat: dynamic parameter references in usageName metadata for quota-based location filtering#6928
Copilot wants to merge 7 commits intomainfrom
copilot/vscode-mm59wsrj-ulwy

Conversation

Copy link
Contributor

Copilot AI commented Feb 27, 2026

  • Understand the issue: allow $(p:paramName) references in usageName metadata so templates can use user-input model names/capacities for quota-based location filtering
  • Add reference parsing utilities in pkg/azure/arm_template.go
  • Add SKU auto-resolution in pkg/infra/provisioning/bicep/prompt.go
  • Add dependency-aware parameter ordering in pkg/infra/provisioning/bicep/bicep_provider.go
  • Update promptForParameter() to accept resolvedValues map[string]any
  • Add tests in pkg/azure/arm_template_test.go and pkg/infra/provisioning/bicep/prompt_test.go
  • Add design doc docs/design/metadata-parameter-references.md
  • Add CHANGELOG entry for the feature
  • Address code review: simplify redundant error message and remove unnecessary code
  • Remove unrelated changes to extensions/ folder (azure.appservice and microsoft.azd.concurx go.mod/go.sum)
  • Fix: skip AI catalog lookup when resolved first token already looks like a full SKU usage name (contains a dot, e.g. OpenAI.GlobalStandard.gpt-4o), allowing mixed usage like OpenAI.GlobalStandard.gpt-4o, $(p:cap)
  • Add tests for full-usage-name pass-through in prompt_test.go
Original prompt

Take the changes to create a PR that fixes #5432

In the issue, there is a linked azd template that uses static quota checks (https://github.com/microsoft/Multi-Agent-Custom-Automation-Engine-Solution-Accelerator/blob/main/infra/main.bicep#L39). Mentioned how this sample can be updated to use:

@metadata({
  azd: {
    type: 'location'
    usageName: [
      '$(p:gptModelName), $(p:gptModelCapacity)'
      '$(p:gpt4_1ModelName), $(p:gpt4_1ModelCapacity)'
      '$(p:gptReasoningModelName), $(p:gptReasoningModelCapacity)'
    ]
  }
})

And that would make it dynamic quota checks that depends on the parameters defined for models.

The user has attached the following file paths as relevant context:

  • cli/azd/AGENTS.md

Created from VS Code.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits February 27, 2026 19:21
… quota checks (#5432)

Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
… unnecessary code

Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
…sed location filtering

Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix static quota checks to use dynamic quota checks feat: dynamic parameter references in usageName metadata for quota-based location filtering Feb 27, 2026
@vhvb1989 vhvb1989 marked this pull request as ready for review February 27, 2026 19:32
@vhvb1989 vhvb1989 requested a review from wbreza as a code owner February 27, 2026 19:32
Copilot AI review requested due to automatic review settings February 27, 2026 19:32
go 1.25

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot remove all the changes to extensions folder /extensions
This PR should not be updating anything from the extensions folder

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — reverted the unrelated go.mod/go.sum changes to extensions/azure.appservice and extensions/microsoft.azd.concurx in commit e219a7c.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables dynamic quota-based location filtering in azd templates by allowing usageName metadata to reference other template parameters (e.g., model name and capacity), then resolving those into SKU usage names via the AI model catalog before performing quota checks.

Changes:

  • Added $(p:paramName) reference parsing/resolution utilities and metadata dependency extraction.
  • Introduced dependency-aware parameter prompt ordering (topological sort) so referenced parameters are prompted before dependents.
  • Implemented SKU usage-name auto-resolution for referenced usageName entries, plus supporting docs/tests and changelog entry.

Reviewed changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
cli/azd/pkg/infra/provisioning/bicep/prompt.go Resolves $(p:...) in usageName, auto-resolves model SKU usage names, and wires resolved usage names into quota-based location filtering.
cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go Adds topo-sorted prompting and a resolvedValues map to support dependency-aware prompting and reference resolution.
cli/azd/pkg/infra/provisioning/bicep/prompt_test.go Updates prompt tests for signature changes and adds tests for topo-sort ordering/error cases.
cli/azd/pkg/azure/arm_template.go Adds HasParamReferences, ExtractParamReferences, ResolveParamReferences, and AzdMetadata.ParamDependencies().
cli/azd/pkg/azure/arm_template_test.go Adds unit tests for parameter reference detection, extraction, resolution, and dependency collection.
cli/azd/docs/design/metadata-parameter-references.md Design doc describing reference syntax, SKU resolution behavior, ordering, and intended UX/errors.
cli/azd/CHANGELOG.md Documents the new usageName parameter reference feature for location parameters.
cli/azd/extensions/microsoft.azd.concurx/go.mod Updates extension dependencies (large tidy/bump set) not directly tied to this feature.
cli/azd/extensions/microsoft.azd.concurx/go.sum Corresponding dependency checksum updates for concurx extension.
cli/azd/extensions/azure.appservice/go.mod Updates extension dependencies (large tidy/bump set) not directly tied to this feature.
cli/azd/extensions/azure.appservice/go.sum Corresponding dependency checksum updates for azure.appservice extension.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 255 to 267
// Parse the resolved string as "modelName, capacity" or "modelName"
parts := strings.SplitN(resolved, ",", 2)
modelName := strings.TrimSpace(parts[0])
capacityStr := ""
if len(parts) == 2 {
capacityStr = strings.TrimSpace(parts[1])
}

// Resolve the model name to its full SKU usage name
skuUsageName, err := p.resolveModelSkuUsageName(ctx, p.env.GetSubscriptionId(), modelName)
if err != nil {
return nil, fmt.Errorf("resolving SKU for model '%s': %w", modelName, err)
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any presence of $(p:...) triggers “model name → SKU usage name” catalog lookup, even if the template author only intended to reference capacity (e.g. OpenAI.GlobalStandard.gpt-4o, $(p:cap)). In that case, the code will treat the full usage name as a model name and likely fail the catalog lookup. Consider supporting mixed cases by detecting when the resolved first token already looks like a full usage name (or providing an explicit opt-in marker), and skipping SKU auto-resolution in that scenario.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in commit c9b8afc. After resolving $(p:...) references, the code now checks whether the first token already looks like a full SKU usage name by detecting a dot (Provider.SKU.ModelName format). If so, the AI catalog lookup is skipped and the token is used as-is — so OpenAI.GlobalStandard.gpt-4o, $(p:cap) works correctly. Two new unit tests cover this path.

Comment on lines +130 to +240
// resolveModelSkuUsageName resolves a model name to its full SKU usage name by querying the
// AI model catalog. It filters SKUs to only standard-tier types (GlobalStandard, DataZoneStandard,
// Standard), excluding provisioned, batch, and developer SKUs which have fundamentally different
// billing and usage models. If GlobalStandard is available, it is selected automatically as it
// offers the highest quota and broadest availability. Otherwise, the user is prompted to choose
// among the remaining standard-tier SKUs.
func (p *BicepProvider) resolveModelSkuUsageName(
ctx context.Context,
subId string,
modelName string,
) (string, error) {
if p.aiModelService == nil {
return "", fmt.Errorf("AI model service is not configured")
}

models, err := p.aiModelService.ListModels(ctx, subId, nil)
if err != nil {
return "", fmt.Errorf("listing AI models: %w", err)
}

// Find the target model and collect unique SKUs across all versions
type skuInfo struct {
Name string
UsageName string
}
var allSkus []skuInfo
seen := map[string]struct{}{}
modelFound := false

for _, model := range models {
if model.Name != modelName {
continue
}
modelFound = true
for _, version := range model.Versions {
for _, sku := range version.Skus {
if _, ok := seen[sku.UsageName]; !ok {
seen[sku.UsageName] = struct{}{}
allSkus = append(allSkus, skuInfo{
Name: sku.Name,
UsageName: sku.UsageName,
})
}
}
}
break
}

if !modelFound {
return "", fmt.Errorf("model '%s' not found in the AI model catalog", modelName)
}
if len(allSkus) == 0 {
return "", fmt.Errorf("no SKUs found for model '%s'", modelName)
}

// Filter to standard-tier SKUs only.
// Provisioned (PTU-based), Batch (async 24-hr), and Developer SKUs have fundamentally
// different billing and usage models that are not suitable for typical deployments.
standardTierSkus := make([]skuInfo, 0, len(allSkus))
for _, s := range allSkus {
switch s.Name {
case "GlobalStandard", "DataZoneStandard", "Standard":
standardTierSkus = append(standardTierSkus, s)
}
}

// If no standard-tier SKUs are available, fall back to all SKUs so the user
// can still proceed (they may have a provisioned deployment in mind).
candidates := standardTierSkus
if len(candidates) == 0 {
candidates = allSkus
}

if len(candidates) == 1 {
return candidates[0].UsageName, nil
}

// Auto-select GlobalStandard if available — it has the highest default quota
// and broadest availability, recommended as the starting point for most workloads.
for _, s := range candidates {
if s.Name == "GlobalStandard" {
return s.UsageName, nil
}
}

// Multiple candidates without GlobalStandard — prompt the user to select one
options := make([]string, len(candidates))
for i, s := range candidates {
options[i] = fmt.Sprintf("%s (%s)", s.Name, s.UsageName)
}

choice, err := p.console.Select(ctx, input.ConsoleOptions{
Message: fmt.Sprintf("Select a deployment SKU for model '%s':", modelName),
Options: options,
})
if err != nil {
return "", err
}

return candidates[choice].UsageName, nil
}

// resolveUsageNamesWithReferences resolves usageName entries that contain $(p:...) parameter
// references. For referenced entries, the resolved value is treated as "modelName, capacity"
// and the model name is resolved to its full SKU usage name via the AI model catalog.
// Constant (non-referenced) entries are passed through unchanged.
func (p *BicepProvider) resolveUsageNamesWithReferences(
ctx context.Context,
usageNames []string,
resolvedValues map[string]any,
) ([]string, error) {
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior adds SKU auto-resolution (resolveModelSkuUsageName) and reference expansion (resolveUsageNamesWithReferences), but there are no unit tests covering these paths (e.g., multiple SKUs prompting, GlobalStandard auto-selection, model-not-found error, and end-to-end quota filtering using $(p:...)). Adding focused tests with a mocked aiModelService and console selection would help prevent regressions and validate the intended UX.

Copilot uses AI. Check for mistakes.
Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
Copilot AI and others added 2 commits March 1, 2026 04:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamic Region Filtering Based on User-Specified Model and Capacity on azd Quota check

3 participants