110 lines
8.6 KiB
Python
110 lines
8.6 KiB
Python
#!/usr/bin/env python3
|
||
"""Ф1 — ЦЕНЫ КАНДИДАТОВ, снятые с прайс-страниц вендоров 06.08.2026.
|
||
|
||
Почему отдельным файлом с датой и URL. Гардрейл CLAUDE.md: цены берутся ТОЛЬКО с прайс-страницы
|
||
вендора, не по памяти; урок календаря D39.61: «слаг живой ≠ модель та же». Смета Ф1/Ф2 стоит на
|
||
этих числах, и если через неделю они разъедутся, должно быть видно, ЧТО именно устарело и откуда
|
||
бралось. Порядок в кортеже: (вход, кэш-хит, выход) долларов за 1M токенов.
|
||
|
||
⚠ ЧТО ЗДЕСЬ НЕ ПРОВЕРЕНО ЖИВЬЁМ: цена — это ЗАЯВЛЕНИЕ вендора, а не замер. Реальная стоимость
|
||
вызова считается из `usage` и может разойтись с прайсом (кэш, тарификация размышления, батч-тир).
|
||
Расхождение прайса с леджером — само по себе находка, и Ф1 обязана его напечатать.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
# (base_url, env-ключ, вход, кэш, выход, механизм гашения размышления, источник цены)
|
||
CANDIDATES: dict[str, tuple] = {
|
||
# DeepSeek — цены пост-катовера, сверены 25.07 (quirks 00, строка эндпоинтов).
|
||
"deepseek-v4-flash": ("https://api.deepseek.com/v1", "DEEPSEEK_API_KEY",
|
||
0.14, 0.0028, 0.28, None, "quirks 00 (25.07, вендор-прайс)"),
|
||
"deepseek-v4-pro": ("https://api.deepseek.com/v1", "DEEPSEEK_API_KEY",
|
||
0.435, 0.003625, 0.87, None, "quirks 00 (25.07, вендор-прайс)"),
|
||
# Z.AI — docs.z.ai/guides/overview/pricing, снято 06.08.
|
||
"glm-5": ("https://api.z.ai/api/paas/v4", "ZAI_API_KEY",
|
||
1.00, 0.20, 3.20, "extra_body_disable", "docs.z.ai/pricing 06.08"),
|
||
"glm-5-turbo": ("https://api.z.ai/api/paas/v4", "ZAI_API_KEY",
|
||
1.20, 0.24, 4.00, "extra_body_disable", "docs.z.ai/pricing 06.08"),
|
||
"glm-5.2": ("https://api.z.ai/api/paas/v4", "ZAI_API_KEY",
|
||
1.40, 0.26, 4.40, "extra_body_disable", "docs.z.ai/pricing 06.08"),
|
||
"glm-4.7": ("https://api.z.ai/api/paas/v4", "ZAI_API_KEY",
|
||
0.60, 0.11, 2.20, "extra_body_disable", "docs.z.ai/pricing 06.08"),
|
||
# xAI — docs.x.ai/docs/models, снято 06.08. Тариф <200k токенов (наши единицы много меньше).
|
||
"grok-4.3": ("https://api.x.ai/v1", "XAI_API_KEY",
|
||
1.25, 0.20, 2.50, "effort_none", "docs.x.ai/models 06.08"),
|
||
"grok-4.5": ("https://api.x.ai/v1", "XAI_API_KEY",
|
||
2.00, 0.30, 6.00, "effort_none", "docs.x.ai/models 06.08 (НОВЫЙ жилец)"),
|
||
# OpenAI — прайс сверен живьём 05.08 (эксп-20 §1.3), изменений против 04.08 не было.
|
||
"gpt-5.6-luna": ("https://api.openai.com/v1", "OPENAI_API_KEY",
|
||
0.20, 0.02, 1.20, "effort_none", "эксп-20 §1.3 (05.08, вендор-прайс)"),
|
||
"gpt-5.6-terra": ("https://api.openai.com/v1", "OPENAI_API_KEY",
|
||
2.00, 0.20, 12.00, "effort_none", "эксп-20 §1.3 (05.08, вендор-прайс)"),
|
||
# Gemini — ai.google.dev/gemini-api/docs/pricing, снято 06.08. Кэш-цена не выделена.
|
||
"gemini-3.6-flash": ("https://generativelanguage.googleapis.com/v1beta/openai",
|
||
"GEMINI_API_KEY", 1.50, 1.50, 7.50, "thinking_budget",
|
||
"ai.google.dev/pricing 06.08 (НОВЫЙ жилец)"),
|
||
"gemini-3.1-flash-lite": ("https://generativelanguage.googleapis.com/v1beta/openai",
|
||
"GEMINI_API_KEY", 0.25, 0.25, 1.50, "thinking_budget",
|
||
"ai.google.dev/pricing 06.08"),
|
||
"gemini-3.1-pro-preview": ("https://generativelanguage.googleapis.com/v1beta/openai",
|
||
"GEMINI_API_KEY", 2.00, 2.00, 12.00, "mandatory",
|
||
"ai.google.dev/pricing 06.08"),
|
||
# Kimi — platform.kimi.ai/docs/pricing/chat-k3, снято 06.08. Самый дорогой выход ростера.
|
||
"kimi-k3": ("https://api.moonshot.ai/v1", "KIMI_API_KEY",
|
||
3.00, 0.30, 15.00, None, "platform.kimi.ai/pricing 06.08 (НОВЫЙ жилец)"),
|
||
# Mistral — mistral.ai/pricing, снято 06.08. Кэш-тариф не опубликован ⇒ равен входу.
|
||
"mistral-large-latest": ("https://api.mistral.ai/v1", "MISTRAL_API_KEY",
|
||
2.00, 2.00, 6.00, None, "mistral.ai/pricing 06.08"),
|
||
}
|
||
|
||
|
||
# Учёт РАЗМЫШЛЕНИЯ в биллинге различается по провайдерам, и это не деталь, а деньги.
|
||
# subset — размышление ВНУТРИ completion_tokens (DeepSeek, OpenAI, Z.AI): считать нечего.
|
||
# additive — размышление СВЕРХ completion (xAI): billed = completion + reasoning.
|
||
# Источник: quirks 00, строка grok — «reasoning у xAI аддитивен к completion (billed =
|
||
# completion+reasoning)». ⚠ Поймано исполнением: скрин показал у grok-4.5 reasoning 3605 при
|
||
# completion 1737, то есть размышление БОЛЬШЕ ответа — при subset-формуле это невозможно, и
|
||
# первая редакция занижала цену grok примерно втрое.
|
||
# additive_total — размышление видно ТОЛЬКО в total_tokens (Gemini): OpenAI-совместимый слой
|
||
# Google отдаёт reasoning_tokens=0 и НЕ кладёт thinking в completion, поэтому оплаченный
|
||
# выход = total − prompt. Источник: quirks 00 §D22.3 — «иначе недоучёт 146×/вызов».
|
||
# ⚠ Класс добавлен 07.08 по адверсариальному ревью: прошлая редакция знала только subset и
|
||
# additive, gemini падал в subset, и его размышление не оплачивалось в леджере вовсе. Величину
|
||
# недоучёта прошлого прогона восстановить НЕЛЬЗЯ: total_tokens тогда не персистился.
|
||
REASONING_BILLING = {"grok-4.3": "additive", "grok-4.5": "additive",
|
||
"gemini-3.6-flash": "additive_total",
|
||
"gemini-3.1-flash-lite": "additive_total",
|
||
"gemini-3.1-pro-preview": "additive_total"}
|
||
|
||
|
||
def billed_output(model: str, completion_tokens: int, reasoning_tokens: int,
|
||
total_tokens: int = 0, prompt_tokens: int = 0) -> int:
|
||
"""Сколько выходных токенов реально тарифицируется."""
|
||
mode = REASONING_BILLING.get(model)
|
||
if mode == "additive":
|
||
return completion_tokens + reasoning_tokens
|
||
if mode == "additive_total":
|
||
# Пол — обычная формула: если total не записан (старый артефакт), не занижаем молча,
|
||
# но и не выдумываем. Отсутствие total_tokens видно по равенству двух путей.
|
||
return max(total_tokens - prompt_tokens, completion_tokens + reasoning_tokens)
|
||
return completion_tokens
|
||
|
||
|
||
def cost(model: str, prompt_tokens: int, cached: int, completion_tokens: int,
|
||
reasoning_tokens: int = 0, total_tokens: int = 0) -> float:
|
||
"""Цена вызова по прайсу. Кэшированные токены тарифицируются отдельной ставкой."""
|
||
_, _, pin, pcache, pout, _, _ = CANDIDATES[model]
|
||
out = billed_output(model, completion_tokens, reasoning_tokens, total_tokens, prompt_tokens)
|
||
return ((prompt_tokens - cached) / 1e6 * pin + cached / 1e6 * pcache + out / 1e6 * pout)
|
||
|
||
|
||
def out_price_order() -> list[str]:
|
||
"""Кандидаты по цене ВЫХОДА — она доминирует в наших ролях (размышление биллится как выход)."""
|
||
return sorted(CANDIDATES, key=lambda m: CANDIDATES[m][4])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
print(f"{'модель':26s}{'вход':>8s}{'кэш':>8s}{'выход':>8s} источник")
|
||
print("-" * 86)
|
||
for m in out_price_order():
|
||
_, _, pin, pc, pout, _, src = CANDIDATES[m]
|
||
print(f"{m:26s}{pin:8.3f}{pc:8.3f}{pout:8.2f} {src}")
|