-
Notifications
You must be signed in to change notification settings - Fork 100
Add azure-cost-query and azure-cost-forecast skills #1221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
taylorak
wants to merge
6
commits into
microsoft:main
Choose a base branch
from
taylorak:feature/add-cost-management-skills
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2f433b2
initial implementation
taylorak 18326fb
update the guardrails for query and forecast
taylorak ccfd761
reduce token limit of reference files
taylorak e129e3e
update unit tests
taylorak 4b3bc1a
fix breaking PR checks
taylorak 880cd16
fix pr check errors and code review comments
taylorak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| --- | ||
| name: azure-cost-forecast | ||
| description: "Forecast future Azure costs using the Cost Management Forecast API. Builds and executes forecast requests with proper time-period guardrails, training-data validation, and response interpretation. WHEN: forecast Azure costs, predict spending, projected costs, estimate bill, future Azure costs, cost projection, budget forecast, end of month costs, how much will I spend. DO NOT USE FOR: querying historical costs (use azure-cost-query), reducing costs (use azure-cost-optimization)." | ||
| license: MIT | ||
| metadata: | ||
| author: Microsoft | ||
| version: "1.0.0" | ||
| --- | ||
|
|
||
| # Azure Cost Forecast Skill | ||
|
|
||
| Construct and execute Azure Cost Management Forecast API requests to project future Azure spending with actual-cost context. | ||
|
|
||
| ## Quick Reference | ||
|
|
||
| | Property | Value | | ||
| |----------|-------| | ||
| | **API Endpoint** | `POST {scope}/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01` | | ||
| | **MCP Tools** | `azure__documentation`, `azure__extension_cli_generate`, `azure__get_azure_bestpractices` | | ||
| | **CLI** | `az rest` | | ||
| | **Best For** | Future cost projections, budget planning, end-of-month estimates | | ||
| | **Related Skills** | azure-cost-query, azure-cost-optimization | | ||
|
|
||
| ## When to Use This Skill | ||
|
|
||
| - Forecast future Azure spending for subscriptions, resource groups, or billing accounts | ||
| - Project end-of-month or end-of-year costs | ||
| - Get daily or monthly cost projections | ||
| - Include actual costs alongside forecast data for context | ||
| - Estimate future budget impact | ||
|
|
||
| > ⚠️ **Warning:** If the user wants **historical** cost data, use azure-cost-query. If they want to **reduce** costs, use azure-cost-optimization. | ||
|
|
||
| ## Key Differences from Query API | ||
|
|
||
| | Aspect | Query API | Forecast API | | ||
| |--------|-----------|--------------| | ||
| | Purpose | Historical cost data | Projected future costs | | ||
| | Time period | Past dates only | Must include future dates | | ||
| | Grouping | ✅ Up to 2 dimensions | ❌ **Not supported** | | ||
| | `includeActualCost` | N/A | Include historical alongside forecast | | ||
| | Response columns | Cost, Date, Currency | Cost, Date, **CostStatus**, Currency | | ||
| | Max response rows | 5,000/page | 40 rows recommended | | ||
| | Timeframe | Multiple presets + Custom | Typically `Custom` only | | ||
|
|
||
| ## MCP Tools | ||
|
|
||
| | Tool | Purpose | Required | | ||
| |------|---------|----------| | ||
| | `azure__documentation` | Look up Forecast API parameters and reference | Optional | | ||
| | `azure__extension_cli_generate` | Generate `az rest` commands for forecast requests | Optional | | ||
| | `azure__get_azure_bestpractices` | Get cost management best practices | Optional | | ||
|
|
||
| > 💡 **Tip:** Prefer Azure MCP tools over direct CLI commands where possible. | ||
|
|
||
| ## Workflow | ||
|
|
||
| ### Step 1: Determine Scope | ||
|
|
||
| Same scope URL patterns as the Query API: | ||
|
|
||
| | Scope | URL Pattern | | ||
| |-------|-------------| | ||
| | Subscription | `/subscriptions/<subscription-id>` | | ||
| | Resource Group | `/subscriptions/<subscription-id>/resourceGroups/<resource-group>` | | ||
| | Management Group | `/providers/Microsoft.Management/managementGroups/<mg-id>` | | ||
| | Billing Account | `/providers/Microsoft.Billing/billingAccounts/<billing-account-id>` | | ||
|
|
||
| > 💡 **Tip:** These are scope paths only — not complete URLs. Combine with the API endpoint and version: `{scope}/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01` | ||
|
|
||
| ### Step 2: Choose Report Type | ||
|
|
||
| `ActualCost` is most common for forecasting. `AmortizedCost` for reservation/savings plan projections. | ||
|
|
||
| ### Step 3: Set Time Period | ||
|
|
||
| > ⚠️ **Warning:** The `to` date **MUST** be in the future. The API returns an error for entirely past date ranges. | ||
|
|
||
| - Set `timeframe` to `Custom` and provide `timePeriod` with `from` and `to` dates | ||
| - `from` can be in the past — shows actual costs up to today, then forecast to `to` | ||
| - Minimum 28 days of historical cost data required for forecast to work | ||
| - Maximum forecast period: 10 years | ||
|
|
||
| > **Full rules:** [Guardrails Reference](./references/guardrails.md) | ||
|
|
||
| ### Step 4: Configure Dataset | ||
|
|
||
| - **Granularity**: `Daily` or `Monthly` recommended | ||
| - **Aggregation**: Typically `Sum` of `Cost` | ||
| - See [Request Body Schema](./references/request-body-schema.md) for full schema | ||
|
|
||
| > ⚠️ **Warning:** Grouping is **NOT supported** for forecast. If the user requests a grouped forecast, inform them this is an API limitation and suggest using `azure-cost-query` for grouped historical data instead. | ||
|
|
||
| ### Step 5: Set Forecast-Specific Options | ||
|
|
||
| | Field | Default | Description | | ||
| |-------|---------|-------------| | ||
| | `includeActualCost` | `true` | Include historical actual costs alongside forecast | | ||
| | `includeFreshPartialCost` | `true` | Include partial cost data for recent days. **Requires `includeActualCost: true`** | | ||
|
|
||
| ### Step 6: Construct and Execute | ||
|
|
||
| **Create forecast query file:** | ||
|
|
||
| Create `temp/cost-forecast.json` with: | ||
| ```json | ||
| { | ||
| "type": "ActualCost", | ||
| "timeframe": "Custom", | ||
| "timePeriod": { | ||
| "from": "<first-of-month>", | ||
| "to": "<last-of-month>" | ||
| }, | ||
| "dataset": { | ||
| "granularity": "Daily", | ||
| "aggregation": { | ||
| "totalCost": { "name": "Cost", "function": "Sum" } | ||
| }, | ||
| "sorting": [{ "direction": "Ascending", "name": "UsageDate" }] | ||
| }, | ||
| "includeActualCost": true, | ||
| "includeFreshPartialCost": true | ||
| } | ||
| ``` | ||
|
|
||
| **Execute forecast query:** | ||
| ```powershell | ||
| # Create temp folder | ||
| New-Item -ItemType Directory -Path "temp" -Force | ||
|
|
||
| # Query using REST API | ||
| az rest --method post ` | ||
| --url "https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01" ` | ||
| --body '@temp/cost-forecast.json' | ||
| ``` | ||
|
|
||
| ### Step 7: Interpret Response | ||
|
|
||
| The response includes a `CostStatus` column: | ||
|
|
||
| | CostStatus | Meaning | | ||
| |------------|---------| | ||
| | `Actual` | Historical actual cost (when `includeActualCost: true`) | | ||
| | `Forecast` | Projected future cost | | ||
|
|
||
| > 💡 **Tip:** "Forecast is unavailable for the specified time period" is not an error — it means the scope has insufficient historical data. Suggest using `azure-cost-query` for available data. | ||
|
|
||
| ## Key Guardrails | ||
|
|
||
| | Rule | Constraint | | ||
| |------|-----------| | ||
| | `to` date | Must be in the future | | ||
| | Grouping | ❌ Not supported | | ||
| | Min training data | 28 days of historical cost data | | ||
| | Max forecast period | 10 years | | ||
| | Response row limit | 40 rows recommended | | ||
| | `includeFreshPartialCost` | Requires `includeActualCost: true` | | ||
| | Monthly + includeActualCost | Requires explicit `timePeriod` | | ||
|
|
||
| > **Full details:** [Guardrails Reference](./references/guardrails.md) | ||
|
|
||
| ## Error Handling | ||
|
|
||
| | Status | Error | Remediation | | ||
| |--------|-------|-------------| | ||
| | 400 | Can't forecast on the past | Ensure `to` date is in the future. | | ||
| | 400 | Missing dataset | Add required `dataset` field. | | ||
| | 400 | Invalid dependency | Set `includeActualCost: true` when using `includeFreshPartialCost`. | | ||
| | 403 | Forbidden | Needs **Cost Management Reader** role on scope. | | ||
| | 424 | Bad training data | Insufficient history; falls back to actual costs if available. | | ||
| | 429 | Rate limited | Retry after the `x-ms-ratelimit-microsoft.costmanagement-qpu-retry-after` header value. **Max 3 retries.** | | ||
| | 503 | Service unavailable | Check [Azure Status](https://status.azure.com). | | ||
|
|
||
| > **Full details:** [Error Handling Reference](./references/error-handling.md) | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Forecast Rest of Current Month (Daily) | ||
|
|
||
| ```json | ||
| { | ||
| "type": "ActualCost", | ||
| "timeframe": "Custom", | ||
| "timePeriod": { | ||
| "from": "<first-of-current-month>", | ||
| "to": "<last-day-of-current-month>" | ||
| }, | ||
| "dataset": { | ||
| "granularity": "Daily", | ||
| "aggregation": { | ||
| "totalCost": { "name": "Cost", "function": "Sum" } | ||
| }, | ||
| "sorting": [{ "direction": "Ascending", "name": "UsageDate" }] | ||
| }, | ||
| "includeActualCost": true, | ||
| "includeFreshPartialCost": true | ||
| } | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** Set `from` to the first of the month to see actual costs so far alongside the forecast for remaining days. | ||
|
|
||
| More examples: [references/examples.md](./references/examples.md) |
48 changes: 48 additions & 0 deletions
48
plugin/skills/azure-cost-forecast/references/error-handling.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # Forecast API Error Handling | ||
|
|
||
| ## HTTP Status Codes | ||
|
|
||
| | Status | Error | Cause | Remediation | | ||
| |---|---|---|---| | ||
| | 400 | Bad Request | Invalid request body, missing `dataset`, past-only dates, invalid field dependency combinations | Check request body structure; ensure `to` date is in the future; verify `includeActualCost`/`includeFreshPartialCost` dependency | | ||
| | 401 | Unauthorized | Authentication failure — missing or expired token | Re-authenticate with `az login` or refresh the access token | | ||
| | 403 | Forbidden | Insufficient permissions on the scope | Ensure the identity has **Cost Management Reader** role (or higher) on the target scope | | ||
| | 404 | Not Found | Invalid scope URL — subscription, resource group, or billing account not found | Verify the scope URL path and resource IDs are correct | | ||
| | 424 | Failed Dependency | Bad training data — forecast model cannot compute predictions | Falls back to actual costs if `includeActualCost=true`; otherwise suggest using **azure-cost-query** for historical data | | ||
| | 429 | Too Many Requests | Rate limited — QPU quota exceeded | Read `x-ms-ratelimit-microsoft.costmanagement-qpu-retry-after` header and wait before retrying | | ||
| | 503 | Service Unavailable | Temporary service issue | Check [Azure Status](https://status.azure.com) for service health. | | ||
|
|
||
| ## Validation Error Reference | ||
|
|
||
| | Error Code | Description | Fix | | ||
| |---|---|---| | ||
| | `EmptyForecastRequestBody` | Request body is empty or null | Provide a complete request body with `type`, `timeframe`, `timePeriod`, and `dataset` | | ||
| | `InvalidForecastRequestBody` | Request body has invalid JSON structure | Check JSON syntax — verify braces, commas, and field names | | ||
| | `DontContainsDataSet` | The `dataset` field is missing from the request body | Add the `dataset` object with `granularity` and `aggregation` | | ||
| | `DontContainsValidTimeRangeWhileContainsPeriod` | `timePeriod` is present but `from` or `to` is invalid | Ensure both `from` and `to` are valid ISO 8601 datetime strings | | ||
| | `DontContainsValidTimeRangeWhileMonthlyAndIncludeCost` | Monthly granularity with `includeActualCost=true` but missing valid `timePeriod` | Add explicit `timePeriod` with valid `from` and `to` dates | | ||
| | `DontContainIncludeActualCostWhileIncludeFreshPartialCost` | `includeFreshPartialCost=true` without `includeActualCost=true` | Set `includeActualCost=true` or set `includeFreshPartialCost=false` | | ||
| | `CantForecastOnThePast` | Both `from` and `to` dates are in the past | Ensure the `to` date is in the future | | ||
|
|
||
| ## Forecast-Specific Scenarios | ||
|
|
||
| | Scenario | Response | Action | | ||
| |---|---|---| | ||
| | "Forecast is unavailable for the specified time period" | Valid response with null/empty rows | Not an error — insufficient history (< 28 days). Suggest using **azure-cost-query** for available historical data. | | ||
| | "Can't forecast on the past" | 400 error with `CantForecastOnThePast` | Ensure the `to` date is in the future. | | ||
| | Bad training data | 424 Failed Dependency | If `includeActualCost=true`, the response falls back to actual cost data only. Otherwise, suggest using **azure-cost-query** for historical data. | | ||
| | Parsing exception | 400 Bad Request | Check JSON format — validate braces, quotes, commas, and field types. | | ||
|
|
||
| ## Retry Strategy | ||
|
|
||
| | Status | Retry? | Strategy | | ||
| |---|---|---| | ||
| | 429 | ✅ Yes | Wait for duration specified in `x-ms-ratelimit-microsoft.costmanagement-qpu-retry-after` header. **Maximum 3 retries.** | | ||
| | 400 | ❌ No | Fix the request body based on the validation error code | | ||
| | 401 | ❌ No | Re-authenticate — the token is missing or expired | | ||
| | 403 | ❌ No | Grant **Cost Management Reader** role on the target scope | | ||
| | 404 | ❌ No | Fix the scope URL — verify subscription, resource group, or billing account IDs | | ||
| | 424 | ❌ No | Training data issue — retrying will not help. Fall back to actual costs or use **azure-cost-query** | | ||
| | 503 | ❌ No | Do not retry. Check [Azure Status](https://status.azure.com) for service health. | | ||
|
|
||
| > ⚠️ **Warning:** Do not retry any errors except 429. All other errors indicate issues that must be fixed before re-attempting the request. |
125 changes: 125 additions & 0 deletions
125
plugin/skills/azure-cost-forecast/references/examples.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # Forecast API Examples | ||
|
|
||
| Common forecast patterns with request bodies. Use the [SKILL.md workflow](../SKILL.md) to construct and execute the `az rest` command. | ||
|
|
||
| ## 1. Forecast Rest of Current Month (Daily) | ||
|
|
||
| ```json | ||
| { | ||
| "type": "ActualCost", | ||
| "timeframe": "Custom", | ||
| "timePeriod": { | ||
| "from": "<first-of-month>", | ||
| "to": "<last-of-month>" | ||
| }, | ||
| "dataset": { | ||
| "granularity": "Daily", | ||
| "aggregation": { | ||
| "totalCost": { "name": "Cost", "function": "Sum" } | ||
| }, | ||
| "sorting": [ | ||
| { "direction": "Ascending", "name": "UsageDate" } | ||
| ] | ||
| }, | ||
| "includeActualCost": true, | ||
| "includeFreshPartialCost": true | ||
| } | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** Set `from` to the first of the month — the response contains `Actual` rows up to today and `Forecast` rows for remaining days. | ||
|
|
||
| --- | ||
|
|
||
| ## 2. Forecast Next 3 Months (Monthly) | ||
|
|
||
| ```json | ||
| { | ||
| "type": "ActualCost", | ||
| "timeframe": "Custom", | ||
| "timePeriod": { | ||
| "from": "<first-of-month>", | ||
| "to": "<3-months-out>" | ||
| }, | ||
| "dataset": { | ||
| "granularity": "Monthly", | ||
| "aggregation": { | ||
| "totalCost": { "name": "Cost", "function": "Sum" } | ||
| }, | ||
| "sorting": [ | ||
| { "direction": "Ascending", "name": "BillingMonth" } | ||
| ] | ||
| }, | ||
| "includeActualCost": true, | ||
| "includeFreshPartialCost": true | ||
| } | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** Monthly granularity uses the `BillingMonth` column in the response. | ||
|
|
||
| --- | ||
|
|
||
| ## 3. Forecast for Resource Group Scope | ||
|
|
||
| ```json | ||
| { | ||
| "type": "ActualCost", | ||
| "timeframe": "Custom", | ||
| "timePeriod": { | ||
| "from": "<start-date>", | ||
| "to": "<end-date>" | ||
| }, | ||
| "dataset": { | ||
| "granularity": "Daily", | ||
| "aggregation": { | ||
| "totalCost": { "name": "Cost", "function": "Sum" } | ||
| }, | ||
| "sorting": [ | ||
| { "direction": "Ascending", "name": "UsageDate" } | ||
| ] | ||
| }, | ||
| "includeActualCost": true, | ||
| "includeFreshPartialCost": true | ||
| } | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** Scope is set at the URL level. Use the resource group scope URL to limit the forecast. | ||
|
|
||
| --- | ||
|
|
||
| ## 4. Forecast for Billing Account Scope | ||
|
|
||
| ```json | ||
| { | ||
| "type": "ActualCost", | ||
| "timeframe": "Custom", | ||
| "timePeriod": { | ||
| "from": "<start-date>", | ||
| "to": "<end-date>" | ||
| }, | ||
| "dataset": { | ||
| "granularity": "Monthly", | ||
| "aggregation": { | ||
| "totalCost": { "name": "Cost", "function": "Sum" } | ||
| }, | ||
| "sorting": [ | ||
| { "direction": "Ascending", "name": "BillingMonth" } | ||
| ] | ||
| }, | ||
| "includeActualCost": true, | ||
| "includeFreshPartialCost": true | ||
| } | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** Use URL pattern `/providers/Microsoft.Billing/billingAccounts/<id>/...`. Monthly granularity recommended for billing account forecasts. | ||
|
|
||
| --- | ||
|
|
||
| ## Scope URL Reference | ||
|
|
||
| | Scope | URL Pattern | | ||
| |---|---| | ||
| | Subscription | `/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/forecast` | | ||
| | Resource Group | `/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.CostManagement/forecast` | | ||
| | Billing Account | `/providers/Microsoft.Billing/billingAccounts/<id>/providers/Microsoft.CostManagement/forecast` | | ||
|
|
||
| > 💡 **Tip:** These are path-only patterns — not complete URLs. Append `?api-version=2023-11-01` when constructing the full request URL. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.