feat: 增加计费预估核对运维流程
This commit is contained in:
+4
-1
@@ -8,6 +8,7 @@
|
||||
- Confirm whether real upstream calls are allowed; otherwise use simulation.
|
||||
- Confirm upstream endpoint, auth, model name, capabilities, limits, pricing, and sync/async behavior from documentation.
|
||||
- Record which existing pricing rule and runtime policy were reused, or the exact semantic mismatch that required a new one.
|
||||
- For billing changes, save the current effective rule bindings, discounts, user-group policy, wallet balance, and one representative estimate response as described in `model-billing-configuration-and-estimation.md`.
|
||||
- Record the protocol compatibility decision and why the selected existing `specType` is sufficient, or the concrete gap that requires `universal`.
|
||||
- When one base model has different upstream names across platforms, confirm each binding resolves the expected `providerModelName`.
|
||||
|
||||
@@ -39,6 +40,8 @@ Confirm model alias, model types, provider source, effective capabilities, prici
|
||||
|
||||
For pricing, verify the effective rule source and discount source rather than checking IDs only. Confirm whether the platform-model discount overrides the platform default, and compare the estimated or simulated amount with the intended price.
|
||||
|
||||
Use `/api/v1/pricing/estimate` for the first read-only calculation. A simulation request is a task execution path and may reserve and settle wallet billing; do not treat it as a read-only replacement for the estimate endpoint.
|
||||
|
||||
## Execution Verification
|
||||
|
||||
Start with simulation:
|
||||
@@ -67,7 +70,7 @@ For media or universal scripts, inspect:
|
||||
- `/api/admin/runtime/model-rate-limits`
|
||||
- `/api/admin/runtime/rate-limit-windows`
|
||||
|
||||
With explicit approval, run one real minimal request and verify upstream request ID, normalized output, task completion, billing, and failure handling.
|
||||
With explicit approval, run one real minimal request and verify upstream request ID, normalized output, task completion, `billings`, `billingSummary.totalAmount`, `finalChargeAmount`, and the wallet `task_billing` transaction. Explain any expected estimate difference caused by actual token usage, cached input, generated media duration/audio, preprocessing, or failover to another platform.
|
||||
|
||||
## Rollback
|
||||
|
||||
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
# Model Billing Configuration and Estimate Reconciliation
|
||||
|
||||
## Contents
|
||||
|
||||
- Safety and Identity
|
||||
- Effective Billing Layers
|
||||
- Billing Configuration Workflow
|
||||
- Price Estimate Requests
|
||||
- Estimate Reconciliation
|
||||
- Task and Wallet Charge Verification
|
||||
- Expected Differences and Troubleshooting
|
||||
|
||||
## Safety and Identity
|
||||
|
||||
- Use an administrator JWT with `manager` permission for pricing, base-model, platform, platform-model, or user-group writes. Do not use an `sk-*` API key for management APIs.
|
||||
- Call `POST /api/v1/pricing/estimate` with the same user JWT or API key that will execute the real request. The subject controls access rules, candidate visibility, API-key scopes, and user-group billing discount.
|
||||
- The estimate endpoint is read-only for tasks and wallets: it selects and preprocesses a candidate and returns simulated billing lines, but it does not create a task, freeze balance, or debit the wallet.
|
||||
- A request with `runMode: "simulation"` is different: it enters the task execution and billing path and may create task records, reserve wallet balance, and settle a charge. Use the estimate endpoint first and obtain approval before any task request intended only for billing verification.
|
||||
- Never expose API keys, administrator JWTs, wallet identifiers, or credentials in reports.
|
||||
|
||||
## Effective Billing Layers
|
||||
|
||||
Read every applicable layer before changing one:
|
||||
|
||||
1. Base model: `pricingRuleSetId` and `baseBillingConfig`.
|
||||
2. Platform: `pricingRuleSetId`, `defaultPricingMode`, and `defaultDiscountFactor`.
|
||||
3. Platform model: `pricingRuleSetId`, `billingConfigOverride`, `pricingMode`, and `discountFactor`.
|
||||
4. User group: `billingDiscountPolicy.discountFactor`.
|
||||
|
||||
The runtime resolves an effective billing configuration, then applies discounts. An explicit platform-model rule and `billingConfigOverride` can replace or merge inherited values. Do not infer the final price from one ID; confirm it with an estimate.
|
||||
|
||||
The current discount behavior is:
|
||||
|
||||
```text
|
||||
candidate discount = positive platform-model discountFactor
|
||||
else platform defaultDiscountFactor
|
||||
effective discount = candidate discount × user-group billingDiscountPolicy.discountFactor
|
||||
```
|
||||
|
||||
The platform and platform-model discount factors do not multiply together. The user-group factor, when positive, multiplies the selected candidate discount.
|
||||
|
||||
Pricing rule resource mappings include:
|
||||
|
||||
| `resourceType` | Effective use |
|
||||
| --- | --- |
|
||||
| `text_input` | uncached input token price per 1,000 tokens |
|
||||
| `text_cached_input` | cached input token price per 1,000 tokens |
|
||||
| `text_output` | output token price per 1,000 tokens |
|
||||
| `text_total` | text prices from `basePrice` and optional `formulaConfig` |
|
||||
| `image` | image generation base price and dynamic weights |
|
||||
| `image_edit` | image-edit price; image pricing is a fallback when absent |
|
||||
| `video` | price per five-second unit plus dynamic weights |
|
||||
| other types | generic resource base price and weights, such as music or audio |
|
||||
|
||||
Common `dynamicWeight` dimensions are `qualityWeights`, `sizeWeights`, `resolutionWeights`, `audioWeights`, `referenceVideoWeights`, and `voiceSpecifiedWeights`. Configure only dimensions used by the runtime and proven by the target product pricing.
|
||||
|
||||
## Billing Configuration Workflow
|
||||
|
||||
### 1. Capture the current state
|
||||
|
||||
```bash
|
||||
curl --fail-with-body -H "Authorization: Bearer $GATEWAY_ADMIN_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/admin/pricing/rule-sets"
|
||||
curl --fail-with-body -H "Authorization: Bearer $GATEWAY_ADMIN_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/admin/catalog/base-models"
|
||||
curl --fail-with-body -H "Authorization: Bearer $GATEWAY_ADMIN_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/admin/platforms"
|
||||
curl --fail-with-body -H "Authorization: Bearer $GATEWAY_ADMIN_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/admin/models"
|
||||
curl --fail-with-body -H "Authorization: Bearer $GATEWAY_ADMIN_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/admin/user-groups"
|
||||
```
|
||||
|
||||
Use the runtime subject credential to read its effective group policy and wallet snapshot:
|
||||
|
||||
```bash
|
||||
curl --fail-with-body -H "Authorization: Bearer $RUNTIME_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/workspace/user-groups"
|
||||
curl --fail-with-body -H "Authorization: Bearer $RUNTIME_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/workspace/wallet"
|
||||
```
|
||||
|
||||
Save IDs and non-secret fields. Record the wallet balance and frozen balance only when a later task charge will be verified.
|
||||
|
||||
### 2. Reuse or create the pricing rule
|
||||
|
||||
Compare active rules by resource type, unit, base price, `dynamicWeight`, `formulaConfig`, calculator type, currency, and status. Reuse an existing rule when its effective amount can be adjusted with platform or platform-model discounts. Read `model-pricing-and-policies.md` for the rule-set request shape.
|
||||
|
||||
Create a new rule only when no existing rule can express the required unit, formula, dimensions, or undiscounted price. `PATCH /api/admin/pricing/rule-sets/{ruleSetID}` replaces all rules in the set, so preserve every required row.
|
||||
|
||||
### 3. Bind the minimum required layer
|
||||
|
||||
- Put a reusable default rule on the base model with `pricingRuleSetId`.
|
||||
- Put account-wide pricing on the platform with `pricingRuleSetId` and `defaultDiscountFactor`.
|
||||
- Put a real per-model exception on the platform model with `pricingRuleSetId`, `discountFactor`, or `billingConfigOverride`.
|
||||
- Use a user-group `billingDiscountPolicy.discountFactor` only for a subject-wide discount. Read the complete user-group record before PATCH and preserve unrelated rate-limit, quota, recharge, metadata, and status fields.
|
||||
|
||||
Avoid copying the same prices into multiple layers. Read back every changed record before estimating.
|
||||
|
||||
## Price Estimate Requests
|
||||
|
||||
`POST /api/v1/pricing/estimate` accepts a normal request-shaped JSON object plus `kind`. `kind` defaults to `chat.completions`. An API key must have the scope required by the selected kind.
|
||||
|
||||
### Chat estimate
|
||||
|
||||
```bash
|
||||
curl --fail-with-body \
|
||||
-H "Authorization: Bearer $RUNTIME_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"kind": "chat.completions",
|
||||
"model": "<model-alias>",
|
||||
"messages": [{"role": "user", "content": "Reply with OK"}],
|
||||
"max_tokens": 256
|
||||
}' \
|
||||
"$GATEWAY_BASE_URL/api/v1/pricing/estimate"
|
||||
```
|
||||
|
||||
Text input tokens are estimated from the request. Output tokens use `max_tokens`; when it is absent or zero, the estimate currently uses 64 output tokens. Use the intended output limit for a meaningful upper-bound comparison.
|
||||
|
||||
### Image estimate
|
||||
|
||||
```bash
|
||||
curl --fail-with-body \
|
||||
-H "Authorization: Bearer $RUNTIME_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"kind": "images.generations",
|
||||
"model": "<model-alias>",
|
||||
"prompt": "A small orange cat",
|
||||
"n": 2,
|
||||
"size": "1024x1024",
|
||||
"quality": "high"
|
||||
}' \
|
||||
"$GATEWAY_BASE_URL/api/v1/pricing/estimate"
|
||||
```
|
||||
|
||||
### Video estimate
|
||||
|
||||
```bash
|
||||
curl --fail-with-body \
|
||||
-H "Authorization: Bearer $RUNTIME_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"kind": "videos.generations",
|
||||
"model": "<model-alias>",
|
||||
"prompt": "A slow camera move over a lake",
|
||||
"duration": 12,
|
||||
"resolution": "1080p",
|
||||
"audio": true,
|
||||
"n": 1
|
||||
}' \
|
||||
"$GATEWAY_BASE_URL/api/v1/pricing/estimate"
|
||||
```
|
||||
|
||||
The response has this shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"model": "example-model",
|
||||
"modelAlias": "example-model",
|
||||
"provider": "example",
|
||||
"platformId": "<platform-id>",
|
||||
"platformModelId": "<platform-model-id>",
|
||||
"resourceType": "video",
|
||||
"unit": "5s_video",
|
||||
"quantity": 3,
|
||||
"amount": 12.5,
|
||||
"currency": "resource",
|
||||
"discountFactor": 0.8,
|
||||
"simulated": true,
|
||||
"durationSeconds": 12,
|
||||
"durationUnitCount": 3
|
||||
}
|
||||
],
|
||||
"resolver": "effective-pricing-v1",
|
||||
"totalAmount": 12.5,
|
||||
"currency": "resource"
|
||||
}
|
||||
```
|
||||
|
||||
## Estimate Reconciliation
|
||||
|
||||
For every estimate, verify:
|
||||
|
||||
1. `platformId` and `platformModelId` identify the intended first eligible candidate after permissions, capabilities, output-token limits, priority, and runtime availability are applied.
|
||||
2. `resourceType`, `unit`, `quantity`, and dimension details match the normalized request.
|
||||
3. `discountFactor` equals the selected platform/platform-model factor multiplied by the effective user-group factor.
|
||||
4. Each `amount` matches the configured base price, weights, quantity, and discount.
|
||||
5. `totalAmount` equals the rounded sum of `items[].amount`, and `currency` is expected.
|
||||
|
||||
Useful calculation checks:
|
||||
|
||||
```text
|
||||
text input = input tokens / 1000 × input price × discount
|
||||
text output = output tokens / 1000 × output price × discount
|
||||
image = count × base price × quality/size/resolution weights × discount
|
||||
video = count × ceil(duration seconds / 5) × base price × applicable weights × discount
|
||||
speech = Unicode character count × audio price × discount
|
||||
```
|
||||
|
||||
Cached input is normally known only after execution. When cached input exists but has no configured price, the runtime currently falls back to one tenth of the normal input price.
|
||||
|
||||
If the estimate selects an unexpected platform, do not edit prices to hide the routing problem. Inspect access rules, enabled state, model type, capabilities, priority, cooldown/load, and output-token limits first.
|
||||
|
||||
## Task and Wallet Charge Verification
|
||||
|
||||
After the read-only estimate is accepted, obtain explicit approval before a simulation or real task if wallet mutation is possible.
|
||||
|
||||
1. Record `GET /api/workspace/wallet` before the request.
|
||||
2. Submit the exact same `kind`, model, and billing-relevant parameters through the corresponding runtime API.
|
||||
3. Read `GET /api/workspace/tasks/{taskID}` and capture:
|
||||
- `billings`;
|
||||
- `billingSummary.totalAmount` and currency;
|
||||
- `finalChargeAmount`;
|
||||
- resolved model and candidate evidence from metrics.
|
||||
4. Query the final wallet debit:
|
||||
|
||||
```bash
|
||||
curl --fail-with-body \
|
||||
-H "Authorization: Bearer $RUNTIME_TOKEN" \
|
||||
"$GATEWAY_BASE_URL/api/workspace/wallet/transactions?q=$TASK_ID&transactionType=task_billing&pageSize=20"
|
||||
```
|
||||
|
||||
5. Read the wallet again and compare:
|
||||
|
||||
```text
|
||||
task finalChargeAmount == billingSummary.totalAmount
|
||||
task finalChargeAmount == task_billing transaction amount
|
||||
wallet balance before - wallet balance after == task_billing transaction amount
|
||||
```
|
||||
|
||||
The task may also create `reserve` and `release` transactions. Reservation changes frozen balance, not the spend balance; `task_billing` is the final debit to reconcile. Use the task ID as the reference and never sum `reserve`, `release`, and `task_billing` as three charges.
|
||||
|
||||
## Expected Differences and Troubleshooting
|
||||
|
||||
An estimate and final charge may legitimately differ when:
|
||||
|
||||
- actual text input/output or cached-input usage differs from the estimate;
|
||||
- the request omitted `max_tokens`, so the estimate used the 64-token default;
|
||||
- generated video duration or audio presence overrides the requested/preprocessed value;
|
||||
- preprocessing normalizes duration, resolution, count, or model-specific fields;
|
||||
- the first candidate fails and execution settles on another platform with different pricing;
|
||||
- the estimate and task used different users, API keys, scopes, groups, or access rules;
|
||||
- pricing or discount configuration changed between estimate and execution.
|
||||
|
||||
When the difference is unexplained, compare the estimate line, task `billings`, task preprocessing log, candidate metrics, effective user group, wallet transaction metadata, and configuration timestamps. Do not change wallet balances to make a mismatch disappear.
|
||||
+3
@@ -3,6 +3,7 @@
|
||||
## Contents
|
||||
|
||||
- Pricing Rule Sets
|
||||
- Billing Configuration and Estimate Reconciliation
|
||||
- Runtime Policy Sets
|
||||
- Runner Policy and Runtime Recovery
|
||||
|
||||
@@ -37,6 +38,8 @@ Evaluate the price that the runtime will actually use:
|
||||
|
||||
Do not clone an otherwise identical pricing rule merely to represent a provider or account discount. Keep the reusable base price in the rule and express the instance-specific adjustment with `defaultDiscountFactor` or `discountFactor`.
|
||||
|
||||
For the complete binding, estimate, task-charge, and wallet-reconciliation workflow, read `model-billing-configuration-and-estimation.md` before changing billing settings.
|
||||
|
||||
Create an example text pricing rule set:
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user