feat(cost): persist reasoning tokens and the peak/off-peak tier
CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
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:
@@ -229,10 +229,16 @@ class AiCall(Base):
|
||||
tokens_in: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
tokens_out: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
tokens_cached: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
# 思考模式 token 单独记,不并入 tokens_out:它按输出价计费,混在一起就看不出
|
||||
# 开关思考模式的成本差异,而这正是 T-P1-24a 要长期盯的数。
|
||||
tokens_reasoning: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
latency_ms: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
cost_cents: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 6), nullable=False, default=Decimal("0")
|
||||
)
|
||||
# 该次调用适用的峰谷档位。没有它就无法事后复核 cost_cents ——
|
||||
# 单价随时刻变化,事后按「当前价」回算会得出不同结果 (T-P1-25c)。
|
||||
rate_tier: Mapped[str | None] = mapped_column(String(16))
|
||||
success: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
error_code: Mapped[str | None] = mapped_column(String(64))
|
||||
error_message: Mapped[str | None] = mapped_column(Text)
|
||||
|
||||
Reference in New Issue
Block a user