V4.1 is natively multimodal so every stage uses deepseek-flash; pricing and the UI now surface dollars instead of empty credits. KiCad netlists and neighborhood fingerprints land in the same cut so a second run can keep the project and skip unchanged ICs. Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
4.7 KiB
Python
99 lines
4.7 KiB
Python
"""Per-provider pricing tables and cost computation.
|
||
|
||
Replaces the flat ``PRICING`` dict that used to live in
|
||
``backend/services/api_logs.py``. Indexed by (provider, model).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
|
||
# Per-million-token USD rates. Source-of-truth links:
|
||
# DeepSeek: https://api-docs.deepseek.com/quick_start/pricing
|
||
# Anthropic: https://docs.anthropic.com/en/docs/about-claude/pricing
|
||
# Google: https://ai.google.dev/pricing
|
||
# Last updated: 2026-09-10
|
||
#
|
||
# DeepSeek: peak weekday rates (conservative). Off-peak is 50% of these.
|
||
# Cache-hit input is billed via CACHE_RATES["deepseek"]["read"] as a
|
||
# multiplier on the miss input rate (0.006 / 0.30 = 0.02).
|
||
_DEEPSEEK_FLASH = {"input": 0.30, "output": 1.20}
|
||
_DEEPSEEK_PRO = {"input": 1.32, "output": 3.96}
|
||
|
||
PRICING: dict[str, dict[str, dict[str, float]]] = {
|
||
"deepseek": {
|
||
"deepseek-flash": _DEEPSEEK_FLASH,
|
||
"deepseek-v4-flash": _DEEPSEEK_FLASH,
|
||
"deepseek-v4-flash-vision-exp": _DEEPSEEK_FLASH,
|
||
# Billed at Pro until 2026-09-14 04:00 UTC, then routed to Flash.
|
||
"deepseek-v4-pro": _DEEPSEEK_PRO,
|
||
"default": _DEEPSEEK_FLASH,
|
||
},
|
||
"anthropic": {
|
||
"claude-opus-4-6": {"input": 5.00, "output": 25.00},
|
||
"claude-opus-4-5": {"input": 5.00, "output": 25.00},
|
||
"claude-opus-4-1": {"input": 15.00, "output": 75.00},
|
||
"claude-opus-4": {"input": 15.00, "output": 75.00},
|
||
"claude-sonnet-4-6": {"input": 3.00, "output": 15.00},
|
||
# Sonnet 5 standard rate (== Sonnet 4.6). Introductory pricing of
|
||
# $2/$10 runs through 2026-08-31; intentionally NOT tracked here —
|
||
# chosen set-and-forget so no dated bump is needed on 2026-09-01.
|
||
# (New tokenizer emits ~30% more tokens, so per-run cost still rises.)
|
||
"claude-sonnet-5": {"input": 3.00, "output": 15.00},
|
||
"claude-sonnet-4-5": {"input": 3.00, "output": 15.00},
|
||
"claude-sonnet-4": {"input": 3.00, "output": 15.00},
|
||
"claude-haiku-4-5-20251001": {"input": 1.00, "output": 5.00},
|
||
"claude-haiku-4-5": {"input": 1.00, "output": 5.00},
|
||
"claude-haiku-3-5": {"input": 0.80, "output": 4.00},
|
||
"default": {"input": 3.00, "output": 15.00},
|
||
},
|
||
"gemini": {
|
||
# Gemini 3 Flash pricing (per 1M tokens). Preview alias mirrors GA.
|
||
"gemini-3-flash-preview": {"input": 0.30, "output": 2.50},
|
||
"gemini-3-flash": {"input": 0.30, "output": 2.50},
|
||
"gemini-flash-latest": {"input": 0.30, "output": 2.50},
|
||
"gemini-2.5-flash": {"input": 0.30, "output": 2.50},
|
||
"gemini-2.5-pro": {"input": 1.25, "output": 10.00},
|
||
# Gemini 3.1 Pro Preview — standard tier, prompts ≤200k tokens.
|
||
# Above 200k Google charges $4.00/$18.00; we don't yet split by
|
||
# prompt size, so we use the smaller-tier rate. Almost every
|
||
# pipeline call here is well under 200k.
|
||
"gemini-3.1-pro-preview": {"input": 2.00, "output": 12.00},
|
||
"gemini-3-pro-preview": {"input": 2.00, "output": 12.00},
|
||
"default": {"input": 0.30, "output": 2.50},
|
||
},
|
||
}
|
||
|
||
|
||
# Per-provider cache token multipliers, applied on top of the input rate.
|
||
# create: cost when a cache is *written* (Anthropic charges 1.25× input;
|
||
# Gemini charges 1.0× input — caching writes are billed as a
|
||
# normal input pass)
|
||
# read: cost when a cached prefix is *reused* (much cheaper)
|
||
CACHE_RATES: dict[str, dict[str, float]] = {
|
||
"deepseek": {"create": 1.00, "read": 0.02},
|
||
"anthropic": {"create": 1.25, "read": 0.10},
|
||
"gemini": {"create": 1.00, "read": 0.25},
|
||
}
|
||
|
||
|
||
def cost_for_entry(entry: dict) -> float:
|
||
"""USD cost for an api_logs entry. Reads ``provider`` (default
|
||
``anthropic`` for legacy entries) and ``model`` to pick rates."""
|
||
provider = entry.get("provider") or "deepseek"
|
||
table = PRICING.get(provider) or PRICING["deepseek"]
|
||
rates = table.get(entry.get("model", ""), table["default"])
|
||
cache_rates = CACHE_RATES.get(provider, CACHE_RATES["deepseek"])
|
||
input_rate = rates["input"]
|
||
output_rate = rates["output"]
|
||
return (
|
||
entry.get("input_tokens", 0) * input_rate
|
||
+ entry.get("cache_creation_input_tokens", 0) * input_rate * cache_rates["create"]
|
||
+ entry.get("cache_read_input_tokens", 0) * input_rate * cache_rates["read"]
|
||
+ entry.get("output_tokens", 0) * output_rate
|
||
) / 1_000_000
|
||
|
||
|
||
def total_cost(entries: list[dict]) -> float:
|
||
"""Sum USD across entries."""
|
||
return round(sum(cost_for_entry(e) for e in entries), 6)
|