fix: treat empty AI quota usages as available instead of rejecting#8537
Conversation
When the Azure Cognitive Services /usages API returns an empty array (common on free-tier subscriptions that haven't provisioned resources), azd incorrectly rejects all locations as having insufficient quota. The SKU endpoint already confirms model availability in these locations, so empty usage data should mean 0 current consumption — not 0 quota. This fix updates all affected code paths in model_service.go: - ListLocationsWithQuota: include locations with empty usages - ListModelLocationsWithQuota: include locations with empty usages - resolveDeployments: skip quota filter when usageMap is empty - modelHasQuota: return true for models with SKUs when usages empty - maxModelRemainingQuota: return found=true when usages empty Fixes #8533 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The checkAiModelQuota preflight check has the same issue: when ListUsages returns empty results (free-tier subscriptions), the usageMap lookup returns found=false for every usage name, which is then treated as remaining=0, triggering false 'Insufficient quota' warnings for all model deployments. Skip the quota check for a location when usages are empty, since no consumption data is available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adjusts azd’s AI quota pre-check behavior so that an empty /usages response no longer causes all regions/models to be rejected, unblocking templates that rely on @metadata.usageName quota requirements (notably on free-tier and other subscriptions that return empty usage data).
Changes:
- Treat empty
/usagesresults as “quota data unavailable / do not block” across quota-filtering flows in the AI model service and Bicep preflight quota validation. - Update quota filtering logic to skip quota-based exclusion when the usage map is empty, while still excluding models with no deployable SKUs.
- Add/adjust unit tests to cover empty-usage scenarios for
modelHasQuotaandmaxModelRemainingQuota.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go | Skips AI model quota validation for locations where /usages returns an empty list. |
| cli/azd/pkg/ai/model_service.go | Updates location/model quota filtering to treat empty usage data as non-blocking, and skips quota filtering in deployment resolution when usage data is empty. |
| cli/azd/pkg/ai/model_service_test.go | Updates expectations for empty-usage behavior and adds focused tests for empty-usage handling in quota helpers. |
| cli/azd/pkg/ai/model_helpers_test.go | Updates modelHasQuota test expectations for empty usage maps. |
- Move empty-usages guard in checkAiModelQuota to only skip the quota comparison, not the model-catalog validation. This preserves ai_model_not_found warnings even when usage data is unavailable. - Use -1 as sentinel for MaxRemainingQuota when usage data is empty instead of minRemaining placeholder. Update prompt_service.go to omit the quota label when the value is unknown (negative). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Reuse maxModelRemainingQuota for empty-usages path in ListModelLocationsWithQuota instead of unconditionally appending. This ensures models with no deployable SKUs are still excluded. - Fix comment wording in ListLocationsWithQuota to accurately describe what the SKU endpoint validates (AIServices/S0 account availability, not specific model support). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extract QuotaRemainingUnknown constant (-1) in types.go to centralize the sentinel value for unknown quota. Replace all magic -1 usages across model_service.go and prompt_service.go. - Simplify ListModelLocationsWithQuota by reusing maxModelRemainingQuota for both empty and non-empty usages, ensuring models without deployable SKUs are excluded. - Fix comment wording in ListLocationsWithQuota. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update comment in maxModelRemainingQuota to reference the QuotaRemainingUnknown constant name instead of magic -1. - Document the -1 sentinel semantics in proto comments for max_remaining_quota in both ai_model.proto and prompt.proto. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update test comment to reference QuotaRemainingUnknown constant instead of magic -1. - Update PR description to match shipped behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📋 Prioritization NoteThanks for the contribution! The linked issue isn't in the current milestone yet. |
Filter models by their declared Locations before applying quota data in filterModelsByAnyLocationQuota. Without this, empty usages from an unrelated location (e.g. free-tier subscription) could mark a model as eligible even when its quota is exhausted in its actual location. Add regression test covering: model only in location A with exhausted quota + empty usages from location B where model is not available → model correctly excluded. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
kristenwomack
left a comment
There was a problem hiding this comment.
LGTM, thank you @vhvb1989!
Problem
On free Azure subscriptions (and others that have not yet provisioned Cognitive Services resources), the
/usagesAPI returns an empty array{"value":[]}. The quota pre-check inazdinterprets empty usages as "no quota available" and rejects all locations — even though the SKU endpoint confirms the models are available and the quota is actually sufficient.This blocks
azd upfor templates that use@metadatawithusageNamequota requirements (e.g., customer-chatbot-solution-accelerator).Root Cause
Location prompt flow:
slices.ContainsFunc()on an empty slice always returnsfalse, sofoundis alwaysfalseand every location gets skipped.Preflight quota check: When the usages list is empty, the
usageMaplookup returnsfound=falsefor every usage name, which is treated asremaining=0, triggering false "Insufficient quota" warnings for all model deployments.The same pattern affects several functions in
model_service.goandbicep_provider.go.Fix
When the
/usagesAPI returns an empty list, treat the location as having available quota (usage data unknown). The AI Services account SKU (AIServices/S0) was already confirmed available in the region, so empty usages logically means no consumption data exists — not that quota is zero.A new constant
QuotaRemainingUnknown(-1) is used as a sentinel value to signal that actual remaining quota is unknown. Downstream code (e.g., the location prompt inprompt_service.go) omits the quota label when this sentinel is present, avoiding misleading numeric displays.Affected code paths (location prompt flow —
model_service.go):ListLocationsWithQuota()— include locations with empty usagesListModelLocationsWithQuota()— include locations with empty usages (reportQuotaRemainingUnknownas quota)resolveDeployments()— skip quota filter whenusageMapis emptymodelHasQuota()— returntruefor models with SKUs when usages are emptymaxModelRemainingQuota()— return(QuotaRemainingUnknown, true)when usages are emptyAffected code paths (preflight check —
bicep_provider.go):checkAiModelQuota()— skip quota comparison (but not model-catalog validation) for locations with empty usagesPrompt display (
prompt_service.go):[up to X quota available]label whenMaxRemainingQuotaisQuotaRemainingUnknownWhy this is correct:
ai_model_not_foundwarnings) still runs for empty-usage locationsTesting
modelHasQuotaandmaxModelRemainingQuotawith empty usage mapsFixes #8533