- BrainTools - https://www.braintools.ru -

Цена за успешный результат: почему дешёвая модель может обойтись дороже дорогой

Всем привет, меня зовут Сергей Прощаев и в этой статье расскажу про то, почему выбор модели [1] по цене за миллион токенов — это самый быстрый способ получить неприятный счёт в конце месяца.

Я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E‑commerce и преподаю на курсах разработки и архитектуры в ОТУС.

В апреле мы подключили агента к рутине, которую никто в команде не любил: разбор упавших интеграционных тестов и подготовка черновика фикса. Взяли топовую модель по принципу «пусть работает лучшее».

Через три недели финансовый контроллер прислал выписку и вежливо спросил, что это за строчка — годовая проекция перекрывала стоимость ещё одного разработчика. Я сделал то, что казалось очевидным: перешёл на модель подешевле.

Счёт упал примерно на треть, а количество задач, которые агент доводил до зелёных тестов, упало сильнее — и каждая закрытая задача стала обходиться дороже, чем была.

Дальше я сел считать. И выяснил, что считал всё это время неправильно — причём тремя разными способами сразу.

Рис. 1. Цена или скорость: компромисс, который приходится выбирать на каждом запросе

Рис. 1. Цена или скорость: компромисс, который приходится выбирать на каждом запросе

Почему прайс перестал быть ориентиром

Пару лет назад вопрос решался просто: моделей, способных тянуть сложную задачу, было три штуки, стоили они одинаково, разница была видна невооружённым глазом. Сейчас иначе. Мне попалась точная формулировка в разборе июльской волны релизов: вопрос «какая модель лучшая» распался на «какая лучшая на доллар, на тип задачи, на бюджет токенов» (buildthisnow.com [2]).

За одну неделю июля вышло семь заметных моделей от пяти вендоров — перепробовать это руками невозможно.

Второе, что изменилось, — разброс цен. Тарифы на начало августа 2026, здесь и далее сверяйте с прайс‑страницами провайдеров перед тем, как что‑то на них строить:

Модель

Вход, $/1M токенов

Выход, $/1M токенов

GPT-5.6 Sol

5.00

30.00

Claude Opus 5

5.00

25.00

Kimi K3

3.00

15.00

Grok 4.5

2.00

6.00

Gemini 3.6 Flash

1.50

7.50

DeepSeek V4 Flash

0.14

0.28

Между краями таблицы разница более чем в тридцать раз по входу и в сотню по выходу. Соблазн очевиден: взять нижнюю строчку и закрыть вопрос. Именно так я и поступил — и получил результат, обратный ожидаемому.

Третье, самое неприятное для нас, бэкендеров. Агент — это не один запрос. В бенчмарке ProjDevBench, где шесть агентов строят проекты с нуля по двадцати задачам из восьми категорий, среднее потребление составило 138 ходов и 4,81 млн токенов на задачу при доле принятых решений 27,38% (arXiv:2602.01655). Это среднее, а не медиана, и распределение с тяжёлым хвостом: зациклившийся агент вытягивает его вверх. Каждый шаг перечитывает всю накопленную историю — прайс за миллион токенов вам об этом не скажет ни слова.

Ошибка первая: считать токены не в том месте

Первое, обо что я споткнулся, — расхождение между моими расчётами и биллингом ровно вдвое. Причина оказалась банальной: я суммировал usage по финальному ответу агента.

А платите вы за каждый шаг. Агент прочитал файл — это запрос. Запустил тесты, получил лог — ещё запрос, и в него уехала вся предыдущая история. К десятому шагу вы оплачиваете первый файл в десятый раз.

Второй нюанс всплыл позже и дал расхождение уже в другую сторону. Провайдеры тарифицируют повторно отправленный префикс дешевле: Anthropic и новые модели OpenAI берут за чтение из кэша около 0,1 обычной входной ставки, Google на неявном кэшировании — порядка 75%. Простая сумма входных токенов такой скидки не видит и завышает счёт. Cached‑токены нужны отдельной строкой (Kotlin):

/**
 * ВАЖНО: здесь inputTokens — ПОЛНЫЙ вход, включая прочитанное из кэша.
 * Провайдеры отдают эти поля по-разному: у одних cached-токены входят
 * в общий счётчик, у других лежат отдельным полем и в него не входят.
 * Приводите к единой семантике на границе с API, иначе вычитание ниже
 * даст либо двойной учёт, либо отрицательное значение.
 */
data class Usage(
    val inputTokens: Long,
    val cachedInputTokens: Long,
    val outputTokens: Long
) {
    init {
        require(inputTokens >= 0 && cachedInputTokens >= 0 && outputTokens >= 0) {
            "usage не может быть отрицательным"
        }
        require(cachedInputTokens <= inputTokens) {
            "cachedInputTokens ($cachedInputTokens) > inputTokens ($inputTokens): " +
            "похоже, семантика полей провайдера не приведена к общей"
        }
    }
    val uncachedInputTokens: Long get() = inputTokens - cachedInputTokens
}

data class Pricing(
    val inputPerMillion: Double,
    val outputPerMillion: Double,
    val cacheReadMultiplier: Double = 0.1   // сверяйте с документацией провайдера
)

fun Usage.costUsd(p: Pricing): Double =
    uncachedInputTokens / 1_000_000.0 * p.inputPerMillion +
    cachedInputTokens / 1_000_000.0 * p.inputPerMillion * p.cacheReadMultiplier +
    outputTokens / 1_000_000.0 * p.outputPerMillion

Проверка в init выглядит избыточной ровно до первого прогона на новом провайдере: одно из API отдавало cached‑токены отдельным полем, не включая их в общий счётчик, и наивное вычитание уводило часть значений в минус.

Отдельно отмечу: скидка касается только входа. Выходные токены не дешевеют ни у кого, и любая оценка экономии, распространяющая кэш‑дисконт на генерацию, завышена.

Теперь сбор метрик по прогону целиком:

data class RunMetrics(
    val model: String,
    val wallClockMs: Long,
    val toolCalls: Int,
    val usage: Usage,
    val evaluation: EvaluationResult
)

class MeteredAgent(private val agent: Agent) {
    fun runTask(model: String, task: Task): RunMetrics {
        // Монотонные часы: на измерении интервалов настенное время
        // может дать отрицательную дельту при коррекции NTP
        val started = TimeSource.Monotonic.markNow()
        val trace = agent.execute(model, task)
        return RunMetrics(
            model = model,
            wallClockMs = started.elapsedNow().inWholeMilliseconds,
            toolCalls = trace.steps.count { it.isToolCall },
            // Суммируем по КАЖДОМУ шагу, а не по финальному ответу
            usage = Usage(
                inputTokens = trace.steps.sumOf { it.usage.inputTokens },
                cachedInputTokens = trace.steps.sumOf { it.usage.cachedInputTokens },
                outputTokens = trace.steps.sumOf { it.usage.outputTokens }
            ),
            evaluation = Evaluator.check(trace.workdir)
        )
    }
}

Про EvaluationResult скажу отдельно. Соблазн свести успех к одному флагу «тесты зелёные» велик, но это минимальная планка, а не приёмка: модель способна протащить изменение, которое ломает линтер, роняет покрытие или тянет уязвимую зависимость. У нас критерий такой:

/** Детерминированные проверки: воспроизводимы, гоняются в CI без человека. */
data class AutomatedChecks(
    val buildSucceeded: Boolean,
    val testsPassed: Boolean,
    val lintPassed: Boolean,
    val coverageNotDropped: Boolean
) {
    val passed: Boolean get() =
        buildSucceeded && testsPassed && lintPassed && coverageNotDropped
}

data class EvaluationResult(
    val automated: AutomatedChecks,
    val humanReviewAccepted: Boolean
) {
    val isSuccess: Boolean get() = automated.passed && humanReviewAccepted
}

Машинные проверки и человеческое решение разделены намеренно: первые воспроизводимы и гоняются в CI, второе — суждение, и отказ здесь может быть чисто архитектурным. Ситуация «CI зелёный, но ревьюер не согласен с решением» — не баг метрики, а сигнал: если такие отказы копятся, проблема не в надёжности модели, а в том, что вы не описали ей ограничения проекта. Разделив поля, вы это увидите; слепив в одно — нет.

Чем строже критерий, тем ниже доля успеха и тем честнее дальнейшая арифметика. Проверять надо независимо, даже если модель отчиталась об успехе: модели регулярно объявляют задачу решённой при красной сборке.

Ошибка вторая: считать цену прогона вместо цены результата

Вот это и есть главная подмена. Цена прогона — не та величина, которая вам нужна. Вам нужна цена одной закрытой задачи.

Провалившийся прогон не бесплатен. Он сжигает те же токены, что и успешный, а иногда и больше — модель, которая не поняла задачу, начинает ходить по кругу.

Схема на рис. 2 показывает, где именно рождается правильная метрика.

Рис. 2. Схема принципиальная: где рождается честная метрика стоимости

Рис. 2. Схема принципиальная: где рождается честная метрика стоимости

Главное, что стоит вынести из схемы: обе ветки, и успешная, и провальная, сходятся в одном узле. Формула получается такая:

Цена закрытой задачи = (стоимость прогона + стоимость человеко‑времени на прогон) / доля успешных прогонов

Здесь спрятано допущение, которое я делаю сознательно: успешный и провальный прогон стоят одинаково. В жизни это не так — провал обычно дороже. Строгая форма выглядела бы вот так:

Цена закрытой задачи = (p × стоимость успешного прогона + (1 − p) × стоимость провального) / p

Пользуюсь упрощённой версией потому, что провал сжигает больше токенов и больше времени, чем успех: упрощение занижает итог и работает как консервативная оценка. Если у вас есть раздельная статистика — считайте по строгой формуле, выводы ниже только усилятся.

Слагаемое с человеко‑временем я поначалу не учитывал вообще. Зря — сейчас покажу, почему именно оно всё решает.

Считаем: сколько стоит закрытая задача

Дальше — открытая арифметика на публичных тарифах. Это не замер, а расчётная модель: я фиксирую профиль нагрузки и смотрю, как ведут себя деньги. Свои доли успеха вы подставите сами, механика от этого не меняется.

Возьмём скромный по нынешним меркам профиль: 250 тысяч входных токенов и 25 тысяч выходных на один прогон. Это заметно меньше средних 4,81 млн из ProjDevBench, так что оценка консервативная.

Модель

Прогон без кэша

Прогон при 80% попаданий в кэш

GPT-5.6 Sol

$2.00

$1.10

Claude Opus 5

$1.88

$0.98

Kimi K3

$1.12

$0.59

Grok 4.5

$0.65

$0.29

Gemini 3.6 Flash

$0.56

$0.29

Теперь вопрос, ради которого всё затевалось: какая доля успеха нужна дешёвой модели, чтобы обойти дорогую по деньгам?

Порог считается в несколько строк, так что подставить свои числа можно прямо сейчас (Kotlin):

/**
 * Минимальная доля успеха, при которой дешёвая модель
 * выгоднее дорогой с учётом стоимости человеко-времени.
 */
fun breakEvenRate(
    cheapRunCost: Double,      // стоимость прогона дешёвой модели
    strongRunCost: Double,     // стоимость прогона дорогой модели
    strongSuccessRate: Double, // её доля успеха
    humanCostPerRun: Double    // стоимость времени инженера на один прогон
): Double {
    require(cheapRunCost >= 0 && strongRunCost >= 0) { "стоимость прогона не может быть отрицательной" }
    require(humanCostPerRun >= 0) { "стоимость человеко-времени не может быть отрицательной" }
    require(strongSuccessRate > 0.0 && strongSuccessRate <= 1.0) { "доля успеха вне диапазона (0, 1]" }
    return strongSuccessRate *
        (cheapRunCost + humanCostPerRun) / (strongRunCost + humanCostPerRun)
}

// breakEvenRate(0.56, 1.88, 0.9, 0.0)  -> 0.27
// breakEvenRate(0.56, 1.88, 0.9, 6.70) -> 0.76
// breakEvenRate(0.29, 0.98, 0.9, 6.70) -> 0.82   // тот же расчёт с кэшем

Пусть Opus закрывает задачу в 90% прогонов. Пороги для Flash при разных допущениях:

Стоимость человеко‑времени на прогон

Без кэша

С кэшем

$0 (полностью автономный пайплайн)

27%

27%

$6.70 (10 минут инженера по ставке $40/час)

76%

82%

$20 (полчаса senior‑инженера)

85%

87%

Именно этот расчёт заставил меня пересмотреть подход.

Если человеко‑время не учитывать, дешёвая модель почти непобедима: ей достаточно закрывать чуть больше четверти задач, чтобы выигрывать по деньгам. Эта логика [3] арифметически верна ровно до тех пор, пока за агентом никто не смотрит.

Но за ним смотрят. Каждый прогон кто‑то ставит, дожидается, читает диф и решает, годится ли результат. Как только вы добавляете в формулу десять минут инженера, порог подскакивает с 27% до 76%. А если вы ещё и настроили кэширование — то есть сделали ровно то, что советуют все гайды по экономии — порог поднимается до 82%.

Причина неочевидная, но простая: кэш уменьшает токенную часть счёта, а человеческая остаётся на месте.

По мере оптимизации токенных расходов человеко‑время занимает всё большую долю общей стоимости, и разница между тарифами перестаёт быть главным фактором.

  • Условие важное: это верно там, где человеческое время сопоставимо со стоимостью прогона или превышает её. В полностью автономном пайплайне тариф по‑прежнему решает всё.

  • Практический вывод: для полностью автономного пайплайна, где провал не стоит ничего кроме токенов, дешёвый тариф выигрывает почти всегда. Для сценария с человеком в контуре — почти никогда.

Оговорюсь про качество модели: в этой формуле оно не является отдельной переменной, но это не значит, что оно не важно.

Качество уже встроено в долю успешных прогонов, а косвенно влияет ещё и на объём контекста, число итераций и количество последующих правок. Формула не отменяет качество — она переводит его в деньги.

Ошибка третья: брать долю успеха из лидерборда

Формула упирается в одно число — долю успешных прогонов. И тут возникает соблазн не мерить её самому, а взять из бенчмарка. Я на этом обжёгся раньше всего.

Проблема даже не в том, что бенчмарк меряет другую задачу. Проблема в том, что публичные цифры часто несопоставимы между собой. официальный лидерборд [4], и там результат — это оценка пары «агент плюс модель», а не модели самой по себе: один и тот же бэкенд под разными обвязками показывает заметно разные числа.

Сравнивать строчки из вендорских анонсов с лидербордными можно только по направлению, но не по абсолютной величине.

Дальше хуже. Исследование корпоративных агентов зафиксировало разрыв в 37% между лабораторными оценками и поведением [5] в проде, а фреймворк kili‑technology.com [6]).

Пятипроцентный разрыв на бенчмарке может обернуться сорокапроцентным на вашем репозитории — в любую сторону.

Вывод простой: лидерборд — повод добавить модель в шорт‑лист, а не источник числа для расчёта. Долю успеха придётся померить руками, и это единственная часть работы, которую нельзя ни у кого списать.

Что с этим делать: не выбирать модель, а строить слой

Помню, как на внутреннем обсуждении коллега предложил раз в квартал перевыбирать модель по результатам замера. Идея звучала разумно ровно до момента, когда я посчитал темп релизов — примерно один в два дня.

Мой вариант, который я использую сейчас: модель перестаёт быть архитектурным решением и становится параметром конфигурации, а решением становится слой между приложением и провайдерами.

Команды, внедрившие настроенный слой digitalapplied.com [7]). Разброс огромный и не случайный: у нижней границы те, чей трафик однородно сложный и роутеру нечего отдавать вниз, у верхней — те, у кого преобладают короткие однотипные запросы. Где внутри этой вилки окажетесь вы, покажет только распределение ваших задач.

Из готовых решений в шорт‑листе обычно оказываются Portkey (с марта под Apache 2.0, больше 1600 моделей у 250+ провайдеров) и Model Router в Azure AI Foundry, где режим Balanced берёт самую дешёвую модель в пределах 1–2% качества от лучшей. Оговорюсь честно: ни то, ни другое я на боевой нагрузке не гонял, рекомендацией это не считаю — только ориентиром.

Полная конструкция — на рис. 3.

Рис. 3. План действий: четырёхслойная схема управления стоимостью запроса

Рис. 3. План действий: четырёхслойная схема управления стоимостью запроса

Главная мысль схемы: слои бьют по разным частям счёта и поэтому складываются. Сжатие уменьшает то, за что вы платите на входе. Кэш убирает повторную оплату одного и того же.

Маршрутизация меняет тариф. Batch API — отдельная ветка для всего, что не горит: провайдеры дают за отложенную обработку около половины цены, и ночная переиндексация или пакетная разметка туда переезжают безболезненно. А слой 4, eval‑проверка, держит ту самую долю успеха из формулы и не даёт дешёвой модели тихо утащить её вниз. Под eval здесь понимается не LLM‑as‑a‑judge, а тот же набор детерминированных проверок: сборка, тесты, линтер, пороги покрытия.

Судить модель другой моделью в денежном контуре я бы не стал — это ещё один источник ошибки [8] и ещё одна строчка в счёте.

Схема намеренно упрощена до денежного контура. В продовом шлюзе рядом живут ещё квоты и rate limiting, circuit breaker на падающего провайдера, failover между вендорами и сквозной трейсинг по маршрутам. Без них конструкция красиво считает деньги ровно до первого инцидента у провайдера.

Конфиг роутера в простейшем виде (YAML):

routes:
  - name: simple-lookup
    match: { intent: [ "explain", "summarize", "lint" ] }
    model: gemini-3.6-flash
    max_retries: 1
    fallback: grok-4.5

  - name: code-change
    match: { intent: [ "refactor", "bugfix" ], touches_tests: true }
    model: grok-4.5
    eval: gradle-test-green
    fallback: claude-opus-5      # эскалация только после провала eval

  - name: critical
    match: { paths: [ "payments/**", "ledger/**" ] }
    model: claude-opus-5
    routing: disabled            # здесь экономия не окупает риск

Тут сразу возникает резонный вопрос: откуда берётся intent, готовым он не приходит. Вариантов четыре, по возрастанию цены: правила на метаданных запроса, классификация по эмбеддингам, отдельный вызов дешёвой модели‑классификатора (гибко, но это лишний round trip на каждый запрос) и дообученный классификатор. Мы начали с правил на путях в репозитории: скучно, зато отлаживается за пять минут и не добавляет к счёту ни цента.

Последнее правило в конфиге появилось не сразу. Один раз роутер отправил на дешёвую модель задачу по модулю расчёта комиссий, и модель предложила изменение, которое проходило тесты, но меняло округление в третьем знаке. Поймали на ревью. С тех пор у нас есть список путей, где маршрутизация выключена в принципе — риск дороже любой экономии.

Реальная история: сколько токенов вообще лишние

Тут стоит рассказать про кейс, который заставил меня пересмотреть первый слой схемы.

Старший инженер Netflix Теджас Чопра копнул не в сторону выбора модели, а в сторону того, что именно уезжает в контекст. По его оценке, до 90% отправляемых в модель токенов избыточны.

Причина знакома любому, кто подключал MCP‑инструменты: ответы инструментов написаны для человека. Модели нужны три поля, а прилетает 200 строк JSON.

Решение он оформил в проект Headroom и в январе 2026 выложил под Apache 2.0 (github.com/chopratejas/headroom [9]). Это прослойка между агентом и API модели, которая сжимает контекст шестью движками — AST‑осведомлённое сжатие кода для Python, JavaScript, Go, Rust, Java и C++, отдельный компрессор JSON, обученная на агентных трейсах модель для текста. Сжатие обратимо: оригиналы кэшируются локально, и модель может запросить полный фрагмент.

Числа впечатляют: сокращение объёма на 60–95% без измеримой потери качества (точность держалась на opensourceforu.com [10]).

Отдельно отмечу CacheAligner: он определяет, какие части контекста не изменились с прошлого вызова, чтобы не выбивать боль [11] — вставил в промпт текущее время, и весь кэш префикса пошёл насмарку.

Проект честно называют сыроватым. Но идея правильная в любом масштабе: сначала посмотреть, что вы отправляете, и только потом торговаться за тариф.

Где эта модель не работает

Обязан обозначить границы, иначе получится не разбор, а проповедь.

  • Первое и главное: всё выше — расчёт, а не замер. Я показываю механику и пороги, а не утверждаю, что у вас Flash закрывает ровно столько‑то процентов задач. Долю успеха придётся померить на своей задаче и своём репозитории, иначе формула останется красивой и бесполезной.

  • Второе: человеко‑время в формуле — константа, а в жизни это сумма слагаемых, распределённых неравномерно: провальный прогон почти всегда съедает больше времени, чем успешный. Более точная модель развела бы стоимость успеха и провала на два числа. Я этого не делал сознательно: усложнение не меняет направление вывода, а порог только повышает.

  • Третье: цены и механика кэширования двигаются. Claude Sonnet 5 стартовал по вводному тарифу $2/$10, а с сентября переходит на $3/$15. Скидки на чтение из кэша тоже различаются между провайдерами и поколениями моделей. Любые числа в статье — снимок на дату.

  • Четвёртое: профиль в 250 тысяч входных токенов я взял с потолка, пусть и консервативно. Если ваш агент потребляет вчетверо больше, соотношения между строчками сохранятся, но вес человеко‑времени упадёт — и пороги сдвинутся обратно в пользу дешёвой модели.

  • Пятое: маршрутизация не окупается, если весь ваш трафик действительно сложный — роутеру нечего оптимизировать. И включать его без eval нельзя: будете тихо деградировать, пока не пожалуются пользователи.

  • Шестое: self‑hosting открытых весов не дешевле автоматически. Вы меняете плату за токены на плату за GPU и за людей, которые это эксплуатируют.

Что сделать у себя на этой неделе

Могу себе представить, как после такого текста хочется закрыть вкладку и вернуться к задачам. Поэтому сведу к минимуму:

  1. Проверьте, где вы считаете токены. Если по финальному ответу — недосчитываете кратно; если не отделяете cached‑токены — наоборот, завышаете.

  2. Ужесточите критерий успеха. Зелёные тесты — это минимум, а не приёмка.

  3. Померьте долю успеха на своей задаче. Даже несколько прогонов дадут порядок величины — это несравнимо лучше, чем цифра из чужого бенчмарка.

  4. Оцените стоимость человеко‑времени на один прогон. Именно она решает, есть ли смысл в дешёвом тарифе.

  5. Подставьте всё это в формулу цены закрытой задачи. Возможно, вы удивитесь так же, как удивился я.

  6. Заведите eval раньше роутера. Без него маршрутизация — это выключенный свет в комнате с граблями.

  7. Составьте список путей и модулей, где экономия запрещена. У нас это платежи и учёт.

Модель — это не архитектурное решение, а параметр конфигурации, который вы обязаны уметь менять за один деплой: через две недели выйдет ещё семь моделей и половина ваших выводов устареет. Инженерное решение находится не в выборе строчки из прайса, а в том, умеете ли вы считать, во сколько вам обходится один закрытый тикет.

И вопрос к тем, кто уже гонял агентов в проде: вы учитываете человеко‑время в стоимости? Мне интересно, у скольких команд после такого пересчёта дешёвый тариф перестал быть дешёвым — напишите в комментариях свои цифры.

Цена за успешный результат: почему дешёвая модель может обойтись дороже дорогой - 4

Когда ИИ уже встроен в разработку, быстро выясняется: выбрать модель и подключить API недостаточно. Нужно понимать, где агент теряет деньги, почему ошибается, как проверять его результат и в каких задачах локальная или более дорогая модель действительно окупается.

Если хочется перейти от экспериментов с LLM к управляемому рабочему процессу, важно разобраться и в самих моделях, и в сценариях их применения — тогда выбор перестаёт быть гаданием по прайсу и превращается в инженерное решение.

Ближайшие бесплатные открытые уроки по теме:

  • 3 сентября, 20:00. «Локальные LLM модели для разработки». Записаться [12]

  • 8 сентября, 20:00. «Что надо знать про работу LLM моделей». Записаться [13]

  • 8 сентября, 20:00. «ИИ против бага: как разобрать инцидент в Python‑проекте от логов до исправления». Записаться [14]

Полный список бесплатных уроков августа можно найти в дайджесте. [15]

Автор: sproshchaev

Источник [16]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/34740

URLs in this post:

[1] модели: https://otus.pw/8Poa/

[2] buildthisnow.com: http://buildthisnow.com

[3] логика: http://www.braintools.ru/article/7640

[4] официальный лидерборд: https://www.tbench.ai/leaderboard/terminal-bench/2.1

[5] поведением: http://www.braintools.ru/article/9372

[6] kili‑technology.com: http://kili-technology.com

[7] digitalapplied.com: http://digitalapplied.com

[8] ошибки: http://www.braintools.ru/article/4192

[9] github.com/chopratejas/headroom: https://github.com/chopratejas/headroom

[10] opensourceforu.com: http://opensourceforu.com

[11] боль: http://www.braintools.ru/article/9901

[12] Записаться: https://otus.pw/HPU4/

[13] Записаться: https://otus.pw/Stqe/

[14] Записаться: https://otus.pw/FfXS/

[15] в дайджесте.: https://otus.pw/m1b7/

[16] Источник: https://habr.com/ru/companies/otus/articles/1067744/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1067744

www.BrainTools.ru

Rambler's Top100