#!/usr/bin/env python3 """Ф1 — РОСТЕР КАНДИДАТОВ И ЦЕНЫ. Снято 08.08.2026, каждая строка с провенансом. Гардрейл CLAUDE.md: цены берутся с ПРАЙС-СТРАНИЦЫ ВЕНДОРА, не по памяти и не из соседнего пака. Урок календаря D39.61 («слаг живой ≠ модель та же») требует ещё и живого `/models` в день старта — он снят `role_topology/list_models.py`, снимок лежит рядом с сырьём. ⚠ ЧТО ЗДЕСЬ ЗАМЕР, А ЧТО ЗАЯВЛЕНИЕ. Цена — заявление вендора. Фактическая стоимость считается из `usage` и сверяется вторым путём (`verify22.independent_price`). Расхождение прайса с леджером — находка, а не повод молча поправить одну из сторон. ЖИВАЯ СВЕРКА 08.08 УДАЛАСЬ по DeepSeek · Z.AI · Gemini · Kimi (curl, HTTP 200, страницы сохранены в `~/books/tenant-panel/prices/`). НЕ УДАЛАСЬ по OpenAI (Cloudflare 403), xAI (308→403) и Mistral (прайс рендерится JS) — там строки перенесены из эксп-21 с датой его сверки, и это объявлено. Три поправки к `role_topology/prices.py`, найденные живой сверкой: * у Gemini кэш-цена ВЫДЕЛЕНА вендором и втрое-десятикратно ниже входной ($0.20 против $2.00 у 3.1-pro; $0.15 против $1.50 у 3.6-flash; $0.025 против $0.25 у flash-lite) — эксп-21 ставил кэш = входу и тем ЗАВЫШАЛ цену Gemini; * у Z.AI появились `glm-4.7-flash` (бесплатный) и `glm-4.7-flashx` ($0.07/$0.40) — тир дешевле боевого черновика; в live-`/models` их НЕТ, поэтому в ростер они не берутся, а проверяются отдельной $0-пробой существования слага (урок quirks: «нет в /models ≠ не работает»); * DeepSeek на прайс-странице 08.08 несёт объявление о ПОВЫШЕНИИ цен («significant increase expected») — это бьёт по проекции себестоимости книги и обязано ехать с ИТОГом. """ from __future__ import annotations # (base_url, env-ключ, вход, кэш-хит, выход, тир, провенанс цены) CANDIDATES: dict[str, tuple] = { # ⚠⚠ ПЕРЕ-ПИН DeepSeek 16.08.2026, ПОСЛЕ ПЕРЕЛОМА ПРАЙСА. Вендор ввёл TIME-BASED прайсинг с # 16:00 UTC 16.08 (сноска прайс-страницы, факт-чек 15.08, зеркало в `backend/configs/models.yaml`). # Пишем ПИКОВЫЕ цены — та же политика, что у бэкенда (D39.136): наша схема цены ПЛОСКАЯ и # время суток не выражает, а пиковый пин даёт потолку сработать раньше, а не позже. # ⚠ Цена этого решения названа числом: пиковые часы у вендора — 01:00–04:00 и 06:00–10:00 UTC, # то есть 7 часов из 24; остальные 17 часов идут по ПОЛОВИНЕ. Значит на офф-пиковой покупке # леджер ЗАВЫШАЕТ вдвое, и это осознанная переплата резерва ради простоты, а не измерение. # Рост против июльского пина: flash ×3.1 вход / ×4.7 выход / ×5 кэш-хит; pro ×3.0 / ×4.6 / ×12.1. # ⚠ ЧТО ЭТО ДВИГАЕТ СВЕРХ КАССЫ: правило ничьей эксп-22 («при равном качестве берём дешёвого») # отдало редакторскую роль `deepseek-v4-pro` при разрыве ×9 с альтернативами. `glm-5` живёт у # Z.AI и не дорожал. Разрыв сжался, и правило обязано быть пере-считано — см. `plan.py --tier`. "deepseek-v4-flash": ("https://api.deepseek.com/v1", "DEEPSEEK_API_KEY", 0.44, 0.014, 1.32, "draft", "api-docs.deepseek.com/quick_start/pricing + news260813, " "факт-чек 15.08, ПИК с 16:00 UTC 16.08"), "deepseek-v4-pro": ("https://api.deepseek.com/v1", "DEEPSEEK_API_KEY", 1.32, 0.044, 3.96, "editor", "api-docs.deepseek.com/quick_start/pricing + news260813, " "факт-чек 15.08, ПИК с 16:00 UTC 16.08"), "glm-4.7": ("https://api.z.ai/api/paas/v4", "ZAI_API_KEY", 0.60, 0.11, 2.20, "draft", "docs.z.ai/guides/overview/pricing, live 08.08"), "glm-5": ("https://api.z.ai/api/paas/v4", "ZAI_API_KEY", 1.00, 0.20, 3.20, "editor", "docs.z.ai/guides/overview/pricing, live 08.08"), "gpt-5.6-luna": ("https://api.openai.com/v1", "OPENAI_API_KEY", 0.20, 0.02, 1.20, "draft", "эксп-20 §1.3 / эксп-21 (05–06.08); живой ре-чек 08.08 — Cloudflare 403"), "gemini-3.1-flash-lite": ("https://generativelanguage.googleapis.com/v1beta/openai", "GEMINI_API_KEY", 0.25, 0.025, 1.50, "draft", "ai.google.dev/gemini-api/docs/pricing, live 08.08"), "gemini-3.6-flash": ("https://generativelanguage.googleapis.com/v1beta/openai", "GEMINI_API_KEY", 1.50, 0.15, 7.50, "editor", "ai.google.dev/gemini-api/docs/pricing, live 08.08"), "gemini-3.1-pro-preview": ("https://generativelanguage.googleapis.com/v1beta/openai", "GEMINI_API_KEY", 2.00, 0.20, 12.00, "editor", "ai.google.dev/gemini-api/docs/pricing, live 08.08"), "kimi-k2.6": ("https://api.moonshot.ai/v1", "KIMI_API_KEY", 0.95, 0.16, 4.00, "draft", "platform.kimi.ai/docs/pricing/chat-k26, live 08.08"), "mistral-large-latest": ("https://api.mistral.ai/v1", "MISTRAL_API_KEY", 2.00, 2.00, 6.00, "draft", "эксп-21 (06.08); живой ре-чек 08.08 — прайс рендерится JS"), "grok-4.3": ("https://api.x.ai/v1", "XAI_API_KEY", 1.25, 0.20, 2.50, "editor", "эксп-21 (06.08); живой ре-чек 08.08 — 308→403, Wayback свежее не имеет"), } # ⚠ РЕЗ ПО ПРАЙСУ, объявлен ДО замера. Промт разрешает срезать при >16 кандидатах и требует # оставить каждому провайдеру хотя бы один слот. Снимаются те, кто ДОМИНИРОВАН одноимённым # семейством: дороже при той же роли и без уникальной способности. CUT = { "gpt-5.6-terra": "выход $12.00 против $1.20 у luna того же семейства", "kimi-k3": "выход $15.00 против $4.00 у k2.6 того же семейства", "glm-5.1": "цена в цент совпадает с glm-5.2, семейство представлено glm-5 и glm-4.7", "glm-5.2": "выход $4.40 против $3.20 у glm-5 — сильный тир Z.AI уже представлен", "glm-5-turbo": "выход $4.00 против $3.20 у glm-5", "grok-4.5": "выход $6.00 против $2.50 у grok-4.3", "mistral-medium-latest": "цену вендора живьём подтвердить не удалось — в смету не берётся", } # Учёт размышления в биллинге (эксп-21 `prices.py`, пере-проверено прайс-страницами 08.08): # subset — размышление внутри `completion_tokens` (DeepSeek, OpenAI, Z.AI, Kimi); # additive — сверх completion (xAI); # additive_total — видно только в `total_tokens` (Gemini: OpenAI-слой отдаёт reasoning 0). # Gemini-страница 08.08 подтверждает дословно: «Output price (including thinking tokens)». REASONING_BILLING = {"grok-4.3": "additive", "gemini-3.6-flash": "additive_total", "gemini-3.1-flash-lite": "additive_total", "gemini-3.1-pro-preview": "additive_total"} # БОЕВАЯ КОНФИГУРАЦИЯ РАЗМЫШЛЕНИЯ — из quirks 00, не из вкуса. `none` = ручку не шлём (вендор- # дефолт), и это осознанный выбор, а не пропуск. # deepseek — НИКОГДА не гасить (эхо-мина, гардрейл CLAUDE.md); у flash боевая ставит # `reasoning_effort: low`, иначе размышление съедает бюджет (quirks §10); у pro ручка `low` # ⚠ С 30.08 ДОХОДИТ ДО МОДЕЛИ (quirks 3г: серверная реакция −79 входных токенов при побайтно # одинаковых messages) — прежняя причина «не установлена» БОЛЬШЕ НЕ ДЕЙСТВУЕТ. Едем всё равно # вендор-дефолтом, но по другой причине: ВЕЛИЧИНА среза не замерена (дизайн конфаундирован # дрейфом, exp23 §Д46.2), а цена ручки в эхе и качестве прозы не мерена ни разу ⇒ смена ступени # у боевого редактора была бы обменом с неизвестным курсом. Менять — после интерливинг-замера; # glm — гасится `extra_body {"thinking":{"type":"disabled"}}`, так шёл арм B эксп-21; # luna — без явной ручки выжигает бюджет и отдаёт пустой `content` (провод-факт эксп-21 §8); # gemini — конфиг размышления НЕ шлём: у 3.1-pro `thinking_budget:0` даёт HTTP 400, а у # 3.x-flash поведение ручки не замерено, и гадать запрещено правилом двух направлений; # grok — reasoning-off из РЕДАКТОРСКИХ ролей снят как no-op (D30.1) ⇒ вендор-дефолт `low`; # kimi — ручки нет, зато принимает ТОЛЬКО `temperature: 1` (quirks, «Параметры сэмплинга»). THINK = { "deepseek-v4-flash": ("effort", "low"), "deepseek-v4-pro": ("none", ""), "glm-4.7": ("extra_body_disable", ""), "glm-5": ("extra_body_disable", ""), "gpt-5.6-luna": ("effort", "low"), "gemini-3.1-flash-lite": ("none", ""), "gemini-3.6-flash": ("none", ""), "gemini-3.1-pro-preview": ("mandatory", ""), "kimi-k2.6": ("none", ""), "mistral-large-latest": ("none", ""), "grok-4.3": ("none", ""), } OPENAI_FAMILY = {"gpt-5.6-luna"} # max_completion_tokens, temperature не шлём FIXED_TEMP = {"kimi-k2.6": 1.0} # quirks: иное → HTTP 400 «only 1 is allowed» def call_kwargs(model: str, msgs: list[dict], max_out: int = 16000, temperature: float = 0.3) -> dict: """Тело вызова в боевой конфигурации модели. Одно место — чтобы квирк не разъехался по файлам.""" kw: dict = dict(model=model, messages=msgs) if model in OPENAI_FAMILY: kw["max_completion_tokens"] = max_out else: kw["max_tokens"] = max_out kw["temperature"] = FIXED_TEMP.get(model, temperature) mode, val = THINK[model] if mode == "effort": kw["extra_body"] = {"reasoning_effort": val} elif mode == "effort_none": kw["extra_body"] = {"reasoning_effort": "none"} elif mode == "extra_body_disable": kw["extra_body"] = {"thinking": {"type": "disabled"}} return kw 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": 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 by_out_price() -> list[str]: """Порядок замера — по ВОЗРАСТАНИЮ цены выхода: если потолок кончится, непокрытыми останутся дорогие, и это будет объявлено числом, а не скрыто.""" return sorted(CANDIDATES, key=lambda m: CANDIDATES[m][4]) if __name__ == "__main__": print(f"{'модель':26s}{'тир':>8s}{'вход':>8s}{'кэш':>8s}{'выход':>8s} провенанс") print("-" * 110) for m in by_out_price(): _, _, pin, pc, pout, tier, src = CANDIDATES[m] print(f"{m:26s}{tier:>8s}{pin:8.3f}{pc:8.3f}{pout:8.2f} {src}") print(f"\nсрезано по прайсу ({len(CUT)}):") for m, why in sorted(CUT.items()): print(f" {m:24s} {why}")