mirror of
https://github.com/coder/coder.git
synced 2026-06-03 04:58:23 +00:00
c3b6284955
Add cost tracking for LLM chat interactions with microdollar precision. ## Changes - Add `chatcost` package for per-message cost calculation using `shopspring/decimal` for intermediate arithmetic - **Ceil rounding policy**: fractional micros round UP to next whole micro (applied once after summing all components) - Database migration: `total_cost_micros` BIGINT column with historical backfill and `created_at` index - API endpoints: per-user cost summary and admin rollup under `/api/experimental/chats/cost/` - SDK types: `ChatCostSummary`, `ChatCostModelBreakdown`, `ChatCostUserRollup` - Fix `modeloptionsgen` to handle `decimal.Decimal` as opaque numeric type - Update frontend pricing test fixtures for string decimal types ## Design decisions - `NULL` = unpriced (no matching model config), `0` = free - Reasoning tokens included in output tokens (no double-counting) - Integer microdollars (BIGINT) for storage and API responses - Price config uses `decimal.Decimal` for exact parsing; totals use `int64` Frontend: #23037
69 lines
2.7 KiB
SQL
69 lines
2.7 KiB
SQL
ALTER TABLE chat_messages ADD COLUMN total_cost_micros BIGINT;
|
|
|
|
WITH message_costs AS (
|
|
SELECT
|
|
msg.id,
|
|
ROUND(
|
|
COALESCE(msg.input_tokens, 0)::numeric * COALESCE(pricing.input_price, 0)
|
|
+ COALESCE(msg.output_tokens, 0)::numeric * COALESCE(pricing.output_price, 0)
|
|
+ COALESCE(msg.cache_read_tokens, 0)::numeric * COALESCE(pricing.cache_read_price, 0)
|
|
+ COALESCE(msg.cache_creation_tokens, 0)::numeric * COALESCE(pricing.cache_write_price, 0)
|
|
)::bigint AS total_cost_micros
|
|
FROM
|
|
chat_messages AS msg
|
|
JOIN
|
|
chat_model_configs AS cfg
|
|
ON
|
|
cfg.id = msg.model_config_id
|
|
CROSS JOIN LATERAL (
|
|
SELECT
|
|
COALESCE(
|
|
(cfg.options -> 'cost' ->> 'input_price_per_million_tokens')::numeric,
|
|
(cfg.options ->> 'input_price_per_million_tokens')::numeric
|
|
) AS input_price,
|
|
COALESCE(
|
|
(cfg.options -> 'cost' ->> 'output_price_per_million_tokens')::numeric,
|
|
(cfg.options ->> 'output_price_per_million_tokens')::numeric
|
|
) AS output_price,
|
|
COALESCE(
|
|
(cfg.options -> 'cost' ->> 'cache_read_price_per_million_tokens')::numeric,
|
|
(cfg.options ->> 'cache_read_price_per_million_tokens')::numeric
|
|
) AS cache_read_price,
|
|
COALESCE(
|
|
(cfg.options -> 'cost' ->> 'cache_write_price_per_million_tokens')::numeric,
|
|
(cfg.options ->> 'cache_write_price_per_million_tokens')::numeric
|
|
) AS cache_write_price
|
|
) AS pricing
|
|
WHERE
|
|
msg.total_cost_micros IS NULL
|
|
AND (
|
|
msg.input_tokens IS NOT NULL
|
|
OR msg.output_tokens IS NOT NULL
|
|
OR msg.reasoning_tokens IS NOT NULL
|
|
OR msg.cache_creation_tokens IS NOT NULL
|
|
OR msg.cache_read_tokens IS NOT NULL
|
|
)
|
|
AND (
|
|
pricing.input_price IS NOT NULL
|
|
OR pricing.output_price IS NOT NULL
|
|
OR pricing.cache_read_price IS NOT NULL
|
|
OR pricing.cache_write_price IS NOT NULL
|
|
)
|
|
AND (
|
|
(msg.input_tokens IS NOT NULL AND pricing.input_price IS NOT NULL)
|
|
OR (msg.output_tokens IS NOT NULL AND pricing.output_price IS NOT NULL)
|
|
OR (msg.cache_read_tokens IS NOT NULL AND pricing.cache_read_price IS NOT NULL)
|
|
OR (msg.cache_creation_tokens IS NOT NULL AND pricing.cache_write_price IS NOT NULL)
|
|
)
|
|
)
|
|
UPDATE
|
|
chat_messages AS msg
|
|
SET
|
|
total_cost_micros = message_costs.total_cost_micros
|
|
FROM
|
|
message_costs
|
|
WHERE
|
|
msg.id = message_costs.id;
|
|
|
|
CREATE INDEX idx_chat_messages_created_at ON chat_messages (created_at);
|