Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ words:
- lipgloss
- gopxl
- subcmd
- genproto
- errdetails
languageSettings:
- languageId: go
ignoreRegExpList:
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
container.MustRegisterSingleton(containerregistry.NewRemoteBuildManager)
container.MustRegisterSingleton(keyvault.NewKeyVaultService)
container.MustRegisterSingleton(storage.NewFileShareService)
container.MustRegisterSingleton(ai.NewAiModelService)

container.MustRegisterScoped(project.NewContainerHelper)
container.MustRegisterScoped(func(serviceLocator ioc.ServiceLocator) *lazy.Lazy[*project.ContainerHelper] {
Expand Down Expand Up @@ -912,6 +913,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
container.MustRegisterSingleton(grpcserver.NewExtensionService)
container.MustRegisterSingleton(grpcserver.NewServiceTargetService)
container.MustRegisterSingleton(grpcserver.NewFrameworkService)
container.MustRegisterSingleton(grpcserver.NewAiModelService)

// Required for nested actions called from composite actions like 'up'
registerAction[*cmd.ProvisionAction](container, "azd-provision-action")
Expand Down
221 changes: 221 additions & 0 deletions cli/azd/docs/extensions/extension-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Table of Contents
- [Deployment Service](#deployment-service)
- [Account Service](#account-service)
- [Prompt Service](#prompt-service)
- [AI Model Service](#ai-model-service)
- [Event Service](#event-service)
- [Container Service](#container-service)
- [Framework Service](#framework-service)
Expand Down Expand Up @@ -1287,6 +1288,7 @@ The following are a list of available gRPC services for extension developer to i
- [Deployment Service](#deployment-service)
- [Account Service](#account-service)
- [Prompt Service](#prompt-service)
- [AI Model Service](#ai-model-service)
- [Event Service](#event-service)
- [Container Service](#container-service)
- [Framework Service](#framework-service)
Expand Down Expand Up @@ -1807,6 +1809,225 @@ Prompts the user to select a resource from a resource group.
- **Response:** _PromptResourceGroupResourceResponse_
- Contains **ResourceExtended**

#### PromptAiModel

Prompts the user to select an AI model from the AI catalog.

- **Request:** _PromptAiModelRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `filter` (AiModelFilterOptions): optional model filters
- `select_options` (SelectOptions): optional prompt customization (currently `message` override)
- `quota` (QuotaCheckOptions): optional quota-aware filtering
- **Response:** _PromptAiModelResponse_
- Contains `model` (_AiModel_)

Effective location is defined by `filter.locations`.
When `filter.locations` is empty, models are considered across subscription locations.
When `quota` is set:
- if `filter.locations` is empty, quota is evaluated across available locations
- if `filter.locations` contains exactly one location, quota is evaluated at that location
- if `filter.locations` contains multiple locations, quota is evaluated across that provided location set
Models are kept when quota is sufficient in at least one effective location.
Effective location only controls model eligibility for selection; returned `model.locations` remains canonical.

#### PromptAiDeployment

Prompts the user to select deployment configuration (version, SKU, and capacity).

- **Request:** _PromptAiDeploymentRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `model_name` (string): target model
- `quota` (QuotaCheckOptions): optional quota-aware filtering
- `options` (AiModelDeploymentOptions): optional filters for locations/versions/skus/capacity
- `use_default_version` (bool): use default version when available; otherwise prompt for version
- `use_default_capacity` (bool): skip capacity prompt when true
- `include_finetune_skus` (bool): include fine-tune SKUs
- **Response:** _PromptAiDeploymentResponse_
- Contains `deployment` (_AiModelDeployment_)

Effective location is defined by `options.locations`.
When `options.locations` is empty, model catalog is considered across subscription locations.
When `quota` is set, exactly one effective location is required via `options.locations`.
SKU selection is always prompted when one or more valid SKU candidates are available.

#### PromptAiLocationWithQuota

Prompts the user to select a location that satisfies quota requirements.

- **Request:** _PromptAiLocationWithQuotaRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `requirements` (repeated QuotaRequirement): usage meter requirements
- `allowed_locations` (repeated string): optional allowed location filter
- `select_options` (SelectOptions): optional prompt customization (currently `message` override)
- **Response:** _PromptAiLocationWithQuotaResponse_
- Contains `location` (_Location_)

#### PromptAiModelLocationWithQuota

Prompts the user to select a location for a specific model and shows quota available in list labels.

- **Request:** _PromptAiModelLocationWithQuotaRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `model_name` (string): required model name
- `allowed_locations` (repeated string): optional location filter
- `quota` (QuotaCheckOptions): optional minimum available requirement (defaults to 1)
- `select_options` (SelectOptions): optional prompt customization (currently `message` override)
- **Response:** _PromptAiModelLocationWithQuotaResponse_
- Contains `location` (_Location_) and `max_remaining_quota` (double, maximum quota available across model SKUs)

---

### AI Model Service

This service provides non-interactive AI catalog, deployment resolution, and quota usage primitives.

> See [ai_model.proto](../grpc/proto/ai_model.proto) for more details.

#### ListModels

Returns available AI models for a subscription.

- **Request:** _ListModelsRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `filter` (AiModelFilterOptions), optional:
- `locations` (repeated string)
- `capabilities` (repeated string)
- `formats` (repeated string)
- `statuses` (repeated string)
- `exclude_model_names` (repeated string)
- **Response:** _ListModelsResponse_
- `models` (repeated _AiModel_)

If `filter.locations` is empty, models are listed across all subscription locations.
When `filter.locations` is provided, it limits which models are returned, but each returned model still contains canonical
`locations`.

#### ResolveModelDeployments

Resolves valid deployment configurations for a model.

- **Request:** _ResolveModelDeploymentsRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `model_name` (string)
- `options` (AiModelDeploymentOptions), optional:
- `locations` (repeated string)
- `versions` (repeated string)
- `skus` (repeated string)
- `capacity` (optional int32)
- `quota` (QuotaCheckOptions), optional:
- `min_remaining_capacity` (double)
- **Response:** _ResolveModelDeploymentsResponse_
- `deployments` (repeated _AiModelDeployment_)
- each deployment includes model/version/location/SKU/capacity and optional `remaining_quota`

If `options.locations` is empty, all subscription locations are considered.
When `quota` is set, `options.locations` must contain exactly one location.

#### ListUsages

Returns usage meter data for a specific location.

- **Request:** _ListUsagesRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `location` (string), required (no fallback from `azure_context.scope.location`)
- **Response:** _ListUsagesResponse_
- `usages` (repeated _AiModelUsage_) with:
- `name` (string)
- `current_value` (double)
- `limit` (double)

#### ListLocationsWithQuota

Returns locations that satisfy all provided quota requirements.

- **Request:** _ListLocationsWithQuotaRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `requirements` (repeated QuotaRequirement)
- `allowed_locations` (repeated string), optional
- **Response:** _ListLocationsWithQuotaResponse_
- `locations` (repeated _Location_)

#### ListModelLocationsWithQuota

Returns locations where a model has sufficient quota, with per-location available quota.

- **Request:** _ListModelLocationsWithQuotaRequest_
- `azure_context` (AzureContext) with `scope.subscription_id` required
- `model_name` (string), required
- `allowed_locations` (repeated string), optional
- `quota` (QuotaCheckOptions), optional (`min_remaining_capacity` defaults to `1`)
- **Response:** _ListModelLocationsWithQuotaResponse_
- `locations` (repeated _ModelLocationQuota_)
- each entry includes `location` (_Location_) and `max_remaining_quota` (double, maximum quota available)

#### AI Error Reasons

AI model and AI prompt APIs return structured gRPC errors with `ErrorInfo`:

- `domain`: `azd.ai`
- `reason`: one of:
- `AI_MISSING_SUBSCRIPTION`
- `AI_LOCATION_REQUIRED`
- `AI_QUOTA_LOCATION_REQUIRED`
- `AI_MODEL_NOT_FOUND`
- `AI_NO_MODELS_MATCH`
- `AI_NO_DEPLOYMENT_MATCH`
- `AI_NO_VALID_SKUS`
- `AI_NO_LOCATIONS_WITH_QUOTA`
- `AI_INVALID_CAPACITY`
- `AI_INTERACTIVE_REQUIRED`

Some reasons include `ErrorInfo.metadata` for additional context (for example `model_name`, `version`, `sku`).

Extensions should prefer `ErrorInfo.reason` over parsing error text when handling recoverable branches.

**Example Usage (Go):**

```go
ctx := azdext.WithAccessToken(cmd.Context())
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()

azureContext := &azdext.AzureContext{
Scope: &azdext.AzureScope{
SubscriptionId: "<subscription-id>",
Location: "eastus2",
},
}

modelResp, err := azdClient.Prompt().PromptAiModel(ctx, &azdext.PromptAiModelRequest{
AzureContext: azureContext,
Filter: &azdext.AiModelFilterOptions{
Locations: []string{"eastus2"},
Capabilities: []string{"chatCompletion"},
},
Quota: &azdext.QuotaCheckOptions{MinRemainingCapacity: 1},
})
if err != nil {
return fmt.Errorf("selecting model: %w", err)
}

deploymentResp, err := azdClient.Prompt().PromptAiDeployment(ctx, &azdext.PromptAiDeploymentRequest{
AzureContext: azureContext,
ModelName: modelResp.Model.Name,
Options: &azdext.AiModelDeploymentOptions{
Locations: []string{"eastus2"},
},
Quota: &azdext.QuotaCheckOptions{MinRemainingCapacity: 1},
})
if err != nil {
return fmt.Errorf("selecting deployment: %w", err)
}

fmt.Printf("Selected deployment: %s %s %s\n",
deploymentResp.Deployment.ModelName,
deploymentResp.Deployment.Version,
deploymentResp.Deployment.Sku.Name)
```

---

### Event Service
Expand Down
20 changes: 19 additions & 1 deletion cli/azd/extensions/microsoft.azd.demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ Displays a list of Azure resources based on the current logged in user, and subs

Displays a list of Azure resources based on the current logged in user, subscription and resource group filtered by resource type and kind.

### `ai`

The `ai` command demonstrates AI model catalog, deployment selection, and quota capabilities through interactive flows.

#### Usage: `azd demo ai <command>`

#### `azd demo ai models`

Browse available AI models interactively and view model details, including locations, versions, SKUs, and capacity constraints.

#### `azd demo ai deployment`

Select model/version/SKU/capacity and resolve a valid deployment configuration.

#### `azd demo ai quota`

View usage meters and limits for a selected location.

### `metadata`

The `metadata` command demonstrates the metadata capability, which provides command structure and configuration schemas.
Expand Down Expand Up @@ -106,4 +124,4 @@ This `listen` command is required when your extension leverages `LifecycleEvents

#### Usage: `azd demo listen`

This command is invoked by `azd` to allow your extension to subscribe to lifecycle events within the current `azd` project and services.
This command is invoked by `azd` to allow your extension to subscribe to lifecycle events within the current `azd` project and services.
11 changes: 10 additions & 1 deletion cli/azd/extensions/microsoft.azd.demo/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ examples:
- name: prompt
description: Display prompt capabilities.
usage: azd demo prompt
- name: ai models
description: Browse available AI models interactively.
usage: azd demo ai models
- name: ai deployment
description: Select model/version/SKU/capacity and resolve a deployment configuration.
usage: azd demo ai deployment
- name: ai quota
description: View usage meters and limits for a selected location.
usage: azd demo ai quota
- name: mcp
description: Start MCP server with demo tools.
usage: azd demo mcp start
usage: azd demo mcp start
Loading
Loading