将五秒基础价按 duration / 5 比例结算,保留 provider 返回的小数时长,避免六秒视频被按两个完整单位收费。 影响:所有使用 5s 视频基础价的模型,quantity 与 durationUnitCount 允许小数。新增迁移同步现存规则的旧 ceil 公式元数据。 验证:go vet ./...;pnpm lint;pnpm test;pnpm build;./tests/ci/migrations-test.sh
11 KiB
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
managerpermission for pricing, base-model, platform, platform-model, or user-group writes. Do not use ansk-*API key for management APIs. - Call
POST /api/v1/pricing/estimatewith 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:
- Base model:
pricingRuleSetIdandbaseBillingConfig. - Platform:
pricingRuleSetId,defaultPricingMode, anddefaultDiscountFactor. - Platform model:
pricingRuleSetId,billingConfigOverride,pricingMode, anddiscountFactor. - 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:
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
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:
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
pricingRuleSetIdanddefaultDiscountFactor. - Put a real per-model exception on the platform model with
pricingRuleSetId,discountFactor, orbillingConfigOverride. - Use a user-group
billingDiscountPolicy.discountFactoronly 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
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
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
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:
{
"items": [
{
"model": "example-model",
"modelAlias": "example-model",
"provider": "example",
"platformId": "<platform-id>",
"platformModelId": "<platform-model-id>",
"resourceType": "video",
"unit": "5s_video",
"quantity": 2.4,
"amount": 12.5,
"currency": "resource",
"discountFactor": 0.8,
"simulated": true,
"durationSeconds": 12,
"durationUnitCount": 2.4
}
],
"resolver": "effective-pricing-v2",
"totalAmount": 12.5,
"currency": "resource"
}
Estimate Reconciliation
For every estimate, verify:
platformIdandplatformModelIdidentify the intended first eligible candidate after permissions, capabilities, output-token limits, priority, and runtime availability are applied.resourceType,unit,quantity, and dimension details match the normalized request.discountFactorequals the selected platform/platform-model factor multiplied by the effective user-group factor.- Each
amountmatches the configured base price, weights, quantity, and discount. totalAmountequals the rounded sum ofitems[].amount, andcurrencyis expected.
Useful calculation checks:
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 × (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.
- Record
GET /api/workspace/walletbefore the request. - Submit the exact same
kind, model, and billing-relevant parameters through the corresponding runtime API. - Read
GET /api/workspace/tasks/{taskID}and capture:billings;billingSummary.totalAmountand currency;finalChargeAmount;- resolved model and candidate evidence from metrics.
- Query the final wallet debit:
curl --fail-with-body \
-H "Authorization: Bearer $RUNTIME_TOKEN" \
"$GATEWAY_BASE_URL/api/workspace/wallet/transactions?q=$TASK_ID&transactionType=task_billing&pageSize=20"
- Read the wallet again and compare:
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.