feat(cost): persist reasoning tokens and the peak/off-peak tier
CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s

ob_ai_call is the only input to V2 pricing, and it was dropping the two
numbers needed to interpret its own cost column.

tokens_reasoning was captured from the provider response but never written to
the table, so the cost of thinking mode was invisible once it left the
process. Folding it into tokens_out was not an option either -- it is billed
at the output rate, and mixing the two hides exactly the difference we now
have to watch.

rate_tier records which price list applied. Without it a row's cost_cents
cannot be rechecked: DeepSeek's peak rate is double off-peak from 2026-08-17,
so recomputing an old row at "the current rate" silently produces a different
answer than what was actually billed.

Migration 0002 adds both columns. tokens_reasoning defaults to 0 rather than
NULL so cost aggregates do not collapse to NULL on historical rows;
rate_tier stays nullable because rows written before this change genuinely
have an unknown tier, and NULL says that more honestly than a guessed 'flat'.

The smoke script now asserts both: reasoning tokens must be 0 (a non-zero
value means a provider default re-enabled thinking mode behind our backs) and
rate_tier must be present. Verified against the real API -- tokens_reasoning=0,
rate_tier=off_peak, 3.9s round trip.
This commit is contained in:
xsl
2026-08-14 01:02:13 +08:00
parent f46ecc5eb0
commit c97f8ae3e1
9 changed files with 119 additions and 2 deletions
+15 -1
View File
@@ -115,7 +115,8 @@ PY
bold "6/7 ob_ai_call row (成本归因的唯一数据源, 架构文档 §13)"
${COMPOSE} exec -T postgres psql -U meida -d meida -x -c \
"SELECT call_id, user_id, scene, provider, model, prompt_id, prompt_version,
tokens_in, tokens_out, tokens_cached, latency_ms, cost_cents,
tokens_in, tokens_out, tokens_cached, tokens_reasoning,
latency_ms, cost_cents, rate_tier,
success, error_code, ref_type, created_at
FROM ob_ai_call
WHERE user_id = ${USER_ID}
@@ -124,6 +125,19 @@ ROWS=$(${COMPOSE} exec -T postgres psql -U meida -d meida -tAc \
"SELECT count(*) FROM ob_ai_call WHERE user_id = ${USER_ID} AND success")
[[ "${ROWS// /}" -ge 1 ]] || fail "no successful ob_ai_call row was written"
# 思考模式若被上游默认打开,这里会立刻变成非 0 —— 延迟与输出侧成本会一起涨(T-P1-24a)。
REASONING=$(${COMPOSE} exec -T postgres psql -U meida -d meida -tAc \
"SELECT coalesce(max(tokens_reasoning), 0) FROM ob_ai_call WHERE user_id = ${USER_ID}")
[[ "${REASONING// /}" == "0" ]] \
|| fail "思考模式 token 非 0(${REASONING}):V1 场景应显式关闭 thinking,请检查 routing 配置"
# 档位必须落库,否则事后无法复核 cost_cents 是按哪套单价算的(T-P1-25c)。
TIER=$(${COMPOSE} exec -T postgres psql -U meida -d meida -tAc \
"SELECT coalesce(rate_tier, '') FROM ob_ai_call
WHERE user_id = ${USER_ID} ORDER BY created_at DESC LIMIT 1")
[[ -n "${TIER// /}" ]] || fail "ob_ai_call.rate_tier 为空:成本无法事后复核"
printf 'rate_tier=%s tokens_reasoning=%s\n' "${TIER// /}" "${REASONING// /}"
bold "7/7 bl_ledger rows (预扣三段式, 架构文档 §7.4)"
${COMPOSE} exec -T postgres psql -U meida -d meida -c \
"SELECT tx_id, delta, tx_type, status, quota_key, period_key, balance_after,