fix(usage): repair cost analytics and display precision (#1330)

fix(usage): repair cost analytics and display precision

- Automatic OpenRouter pricing sync with cost backfill for traces/snapshots/events
- Live usage data merging for current-hour dashboard accuracy
- 2-decimal API cost formatting across usage/overview pages
- Comprehensive test coverage (PG, SQLite, HTTP, UI)

Merged by github-maintain automation.
This commit is contained in:
Duc Nguyen authored and GitHub committed 2026-07-03 06:24:21 +07:00
1 parent fb4dadce8b
commit 747b58d24c
59 files changed
+3136 -197

No files matched your search

+4 -3
View File
@@ -56,16 +56,17 @@ Streaming fallback is conservative: backup models are tried only if the stream f
## Usage Cap Pricing Enforcement
Standard edition can enforce AI budget caps before billable provider dispatch. API-key providers use OpenRouter `/models` pricing as the catalog source, with optional tenant/provider/model overrides in the dashboard.
Standard edition can enforce AI budget caps before billable provider dispatch. API-key providers use OpenRouter `/models` pricing as the catalog source, with optional tenant/provider/model overrides in the dashboard. The gateway syncs the OpenRouter catalog automatically at startup and then once per day; the dashboard sync action remains available for manual refresh.
Excluded provider classes:
- `chatgpt_oauth`, `claude_cli`, and `bailian` are treated as subscription/non-API pricing in round one.
- `chatgpt_oauth`, `claude_cli`, and `bailian` are skipped for budget-cap enforcement in round one.
- Tracing observability can still map Bailian/DashScope Qwen model IDs to the OpenRouter catalog for market-price dashboard reporting.
- local/no-key subprocess providers such as `acp` and `ollama` are skipped unless a future feature explicitly enables pricing for them.
Runtime flow:
1. Resolve the stored provider by name and skip non-billable provider classes.
2. Load matching policies for tenant, agent, provider, provider type, and model.
3. Resolve custom pricing override first, then OpenRouter catalog pricing when a matching policy has a cost ceiling. Native provider model IDs are mapped to OpenRouter prefixes for common providers such as OpenAI, Anthropic, and Gemini.
3. Resolve custom pricing override first, then OpenRouter catalog pricing when a matching policy has a cost ceiling. Native provider model IDs are mapped to OpenRouter prefixes for common providers such as OpenAI, Anthropic, and Gemini. Tracing cost calculation uses the same override/catalog resolver, with legacy `telemetry.model_pricing` kept only as a fallback.
4. Reserve estimated tokens and cost atomically before each dispatch attempt.
5. Reconcile reserved counters after the provider returns usage or after a failed call.
+11 -3
View File
@@ -121,7 +121,13 @@ The exporter lives in a separate sub-package (`internal/tracing/otelexport/`) so
## 5. Cost Calculation
Per-span cost is calculated using the `CalculateCost()` function in `internal/tracing/cost.go`. For each LLM call span:
Per-span cost is calculated in `internal/tracing/cost.go`. For each LLM call span, pricing resolves in this order:
1. Tenant/provider/model override
2. OpenRouter-backed pricing catalog
3. Legacy `telemetry.model_pricing` config fallback
Catalog and override prices are stored as USD per token/unit. Legacy config prices remain USD per million tokens:
```
Cost = (PromptTokens × InputCostPerMillion) / 1,000,000
@@ -130,9 +136,11 @@ Cost = (PromptTokens × InputCostPerMillion) / 1,000,000
+ (CacheCreationTokens × CacheCreateCostPerMillion) / 1,000,000
```
Model pricing is loaded from `config.ModelPricing` and keyed by `provider/model` (with fallback to `model` only). Cost is stored in the `total_cost` field of each LLM call span. The trace aggregation sums costs from all child `llm_call` spans to compute the trace-level `total_cost`.
Cost is stored in the `total_cost` field of each LLM call span. The trace aggregation sums costs from all child `llm_call` spans to compute the trace-level `total_cost`.
Cache token costs (read + create) are optional and only applied if the pricing config specifies non-zero values.
Cache token costs (read + create) are optional and only applied when the resolved catalog, override, or fallback config provides those price dimensions.
At startup, after the OpenRouter catalog sync succeeds, PostgreSQL tracing runs an idempotent historical backfill for LLM spans whose `total_cost` is still zero. The backfill uses the same override/catalog precedence, refreshes trace totals, and refreshes the affected `usage_snapshots` buckets so the Usage dashboard updates without waiting for new traffic. Bailian/DashScope Qwen model IDs and common unprefixed OpenAI-compatible model IDs are mapped to OpenRouter catalog IDs for observability pricing; usage cap enforcement rules remain separate.
---
+6
View File
@@ -25,6 +25,8 @@ Current bundled core skills:
| `skill-creator` | Meta-skill for creating new skills |
| `workspace-organizing` | Workspace layout and file organization guidance |
| `goclaw` | Gateway CLI/runtime administration and troubleshooting |
| `lark-pm` | Lark/Feishu project-management workflows through GoClaw MCP tools |
| `lark-playbook` | Troubleshooting playbook for failed Lark/Feishu MCP operations |
Shared helper modules live in `skills/_shared/` and are copied alongside each skill but not registered as standalone skills.
@@ -92,6 +94,10 @@ skills/
│ └── SKILL.md
├── workspace-organizing/
│ └── SKILL.md
├── lark-pm/
│ └── SKILL.md
├── lark-playbook/
│ └── SKILL.md
└── goclaw/
└── SKILL.md
```
+116
View File
@@ -4,6 +4,122 @@ Significant changes, features, and fixes in reverse chronological order.
---
## 2026-07-03
### Usage cost display precision
**Fixes**
- Usage and overview cost values now render with two decimal places for API cost
displays, matching dashboard expectations such as `$1.02` and `$5.93`.
**Tests**
- Added web formatter regression coverage for two-decimal API cost rendering.
### Usage analytics historical cost audit
**Fixes**
- Backfilled zero-cost historical `usage_snapshots` rows from the synced pricing
catalog, fixing 7-day and 30-day totals that previously showed the same cost
despite different token counts.
- Usage analytics span aggregation now includes model-using `tool_call` spans in
token and cost totals while keeping direct LLM call counts scoped to
`llm_call` spans.
- Added trace aggregate reconciliation so overview daily token/cost cards match
the same `llm_call` + `tool_call` usage scope used by usage analytics.
**Tests**
- Added PostgreSQL and SQLite regressions for cost backfill and trace aggregate
reconciliation with tool-call model usage.
### Live usage cost refresh
**Fixes**
- Usage summary and breakdown endpoints now include current-hour live trace/span
data, so dashboard cost cards and top model costs update immediately after a
completed LLM call instead of waiting for the hourly snapshot worker.
**Tests**
- Added HTTP regression coverage for current-hour summary cost and provider/model
breakdown cost when no usage snapshot exists yet.
### Usage cost backfill after pricing sync
**Fixes**
- Added startup backfill for historical `llm_call` spans with zero cost after
OpenRouter catalog sync succeeds.
- Backfill now refreshes trace totals and affected usage snapshot buckets so
dashboard cost numbers update for existing data.
- Added catalog aliases for Bailian/DashScope Qwen models and common unprefixed
OpenAI-compatible GPT/o-series model IDs.
**Tests**
- Added PG regression coverage for catalog-backed trace cost backfill.
- Extended pricing resolver coverage for custom OpenAI-compatible and Bailian
model aliases.
---
## 2026-07-02
### Automatic model pricing for usage costs
**Fixes**
- Tracing cost now resolves tenant/provider/model pricing overrides first, then
the OpenRouter catalog, before falling back to legacy telemetry config.
- OpenRouter model pricing catalog now syncs automatically at gateway startup
and once per day, while preserving the manual dashboard sync action.
**Tests**
- Added regressions for catalog-backed trace cost precedence, config fallback,
and cached-token price calculation.
### Zalo Personal nested reply context
**Fixes**
- Preserved Zalo Personal `quote` payloads on inbound messages so agents receive
quoted-message context when users reply to a reply.
- Rendered quoted text and quoted attachment placeholders into DM and group
prompts before mention gating/history flush.
- Added a channel-agnostic bounded reply-context cache so Zalo Personal can
render multi-level reply chains without DB persistence or Zalo API backfill.
- Moved Zalo Personal policy checks before attachment download/media extraction
so rejected messages cannot trigger media fetches or reply-cache writes.
**Tests**
- Added Zalo Personal protocol and handler regressions for quoted text, quoted
attachments, and nested-style replies.
- Added reply-context cache coverage for scoped lookup, multi-level chains,
fallback, depth/size caps, cycle guard, TTL expiry, and clear-on-stop behavior.
- Added a Zalo Personal regression proving denied attachment messages do not
download media before policy rejection.
### Lark MCP skill guidance
**Added**
- Added bundled `lark-pm` and `lark-playbook` skills so GoClaw agents handle
Lark/Feishu PM operations through MCP only.
- Documented schema-first LarkBase writes, writable field checks, person-field
payload shape, per-record updates, and canonical user ID verification.
**Docs**
- Updated the core skills system list to include the new Lark MCP skills.
---
## 2026-07-01
### Usage event analytics schema gate