Выбираем модель замером

«Беру anthropic/claude-sonnet-4, он лучше следует инструкциям». Так агент в прогоне выбрал модель для ассистента, ещё до первой строчки кода. Название он взял по памяти, и оно устарело, а в файле настроек уже стояли другие модели. Когда мы вместо мнения сделали замер, модели Claude через CloseRouter ответили по базе «Кодиума» 0 раз из 10.

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

Перед стартом

  1. Бот «Кодиума» из П1 лежит у тебя в папке проекта, и он же работает на сервере в Амстердаме.
  2. В .env бота ты сам положил ключ CloseRouter и две модели: CLOSEROUTER_API_KEY, MODEL_MAIN, MODEL_FALLBACK. В прогоне это были google/gemini-3.6-flash и openai/gpt-5.4-mini. Ключ лежит в .env, в чат ты его не вставляешь (как в уроке 1.8). Показывать ключ агенту не нужно, но прочитать .env он может, поэтому в каждом задании стоит «ключ не печатать».
  3. В кабинете CloseRouter у ключа стоит лимит расхода. В П6 учебный лимит 5 $ кончился посреди шага, на П7 закладывай запас.
  4. Комплект проверок из урока 1.11 установлен.

Задание 49. git и план

В П4–П6 у папки бота не было git, и откатить неудачную правку было нечем. Первым делом заводим его. Вместо <путь к папке проекта> подставь свой путь.

Работай ТОЛЬКО в текущей папке проекта (<путь к папке проекта>). Другие папки не открывай и не меняй. На сервер не выкладывай. Отвечай по-русски. У тебя установлен комплект навыков и проверок (~/.claude) — пользуйся им.
Это Telegram-бот «Кодиума» из П1 (aiogram): сценарий заявки с согласием → заявка уходит в CRM. Новая задача — «Ассистент приёмной»: человек пишет боту вопрос свободным текстом, бот отвечает по базе знаний школы через модель CloseRouter (ключ и модели — CLOSEROUTER_API_KEY, MODEL_MAIN, MODEL_FALLBACK в .env, я их уже положил).
Правила: отвечает только по базе знаний; чего нет в базе — честно «не знаю» и предлагает человека; хочет записаться — запускается ГОТОВЫЙ сценарий заявки (модель сама заявку не пишет и имени/телефона не видит: телефоны и почты в тексте заменяются пометкой до отправки модели); жалобы, возвраты, вопросы без ответа — передача администратору (OWNER_CHAT_ID) без персональных данных; история диалога только в памяти, 30–60 минут; лимиты на длину и частоту.
1. Сначала заведи git в этой папке (git init, .gitignore: .env, *.db, логи, .venv) и сделай первый коммит текущего состояния.
2. Потом — только план, без правок кода: файлы, формат ответа модели (JSON: answer / action: none|lead|handoff), где маска персональных данных, что пишется в журнал (без текстов сообщений и телефонов), какие тесты, какая модель и почему. Без PostgreSQL, векторной базы и новых сервисов.
3. Базу знаний (knowledge/kodium.md) предложи черновиком в ответе: курсы, длительность, цены, рассрочка, возврат, расписание, и отдельным разделом «чего школа не делает». Бери данные «Кодиума» из этой папки (курсы в сценарии заявки), остальное придумай правдоподобно — отметь, что придумано.

План вышел годный: курсы агент взял из сценария бота, без PostgreSQL и векторной базы. Две вещи я поправил. Модели по умолчанию были из памяти: anthropic/claude-sonnet-4 и gpt-4o-mini. А маска «слово с заглавной буквы = имя» закрыла бы пометкой «Python», «Нейросети» и первое слово каждой фразы.

Задание 50. Замер вместо мнения

План годный, курсы взяты из бота — хорошо. Две правки к плану, прежде чем писать код:
- Модель: ты предлагаешь по умолчанию anthropic/claude-sonnet-4 и gpt-4o-mini. В .env уже стоят MODEL_MAIN=google/gemini-3.6-flash и MODEL_FALLBACK=openai/gpt-5.4-mini; названия по умолчанию из памяти устарели. Выбор модели решим замером, не мнением.
- Маска «слово с заглавной = имя» испортит вопросы («Python», «Нейросети», начало фразы). Имена не маскируем эвристикой: модель их не просит, а запись идёт готовым сценарием. Маскируем телефоны и почты.
Сейчас — шаг замера, БЕЗ кода бота. Отдельный скрипт scripts/zamer_modeley.py: один и тот же системный промпт (черновик базы знаний + правила + формат JSON) и 5 разных вопросов к трём моделям: google/gemini-3.6-flash, openai/gpt-5.4-mini, anthropic/claude-sonnet-5. Для каждого вызова записать в таблицу: модель, prompt_tokens (и сравнить с длиной нашего промпта — нет ли скрытой добавки), completion_tokens, рассуждения (если есть в usage), cached/cache_read на повторном вызове, стоимость из usage (если есть), время ответа, валиден ли JSON, не вышла ли модель из роли. Ключ — из .env, в вывод не печатать. Сохрани базу знаний в knowledge/kodium.md. Закоммить. В ответе — таблица и вывод: какую модель ставим и почему, с цифрами. Отвечай по-русски.

Задание 50b разрешает агенту запустить скрипт:

Запуск скрипта теперь разрешён (.venv\Scripts\python.exe). Запусти замер сам, разберись с ошибками, если будут, и дай таблицу и вывод с цифрами. Отвечай по-русски.

Тут случилось два неприятных эпизода. Сначала скрипт не запустился из-за разрешения на команду. Потом пришла ошибка 401 (сервер не принял ключ), и агент сделал вывод, что CloseRouter — «вымышленный сервис», и предложил взять «публичные бенчмарки». Настоящая причина была в его же скрипте: адрес api.closerouter.ai он придумал, правильный домен closerouter.dev. А в ответе агент напечатал начало ключа. Так нельзя: в задании ниже вместо настоящих символов стоит closerouter_<начало ключа>….

Задание 50c. Адрес из документации, ключ не печатать

CloseRouter — настоящий сервис, ключ рабочий. Ошибка в адресе: в скрипте стоит api.closerouter.ai — домен ты придумал. Правильный адрес и формат — в документации: https://closerouter.dev/closerouter-api.md (прочитай её, не угадывай). Через этот же ключ сейчас работают мониторинг и дашборд из прошлых модулей.
И ещё: в ответе ты напечатал начало ключа (closerouter_<начало ключа>…). Ключ нельзя показывать ни целиком, ни частично — даже в рассуждениях.
Почини адрес по документации, запусти замер, дай таблицу и вывод с цифрами. Отвечай по-русски.
Ключ нельзя показывать ни целиком, ни кусочком. Даже первые символы сужают перебор и попадают в историю чата. Увидел кусок ключа в ответе агента — скажи ему об этом прямо, а сам ключ в кабинете CloseRouter замени на новый.

Задание 50d. Сырые ответы

Замер прошёл, агент принёс таблицу и вывод. Я открыл файл с результатами: ответов моделей там не было, только «JSON верный: да/нет». Кэш и стоимость везде нули, а «добавка» у gpt посчитана средним, в которое попал один вызов на 6 141 токен.

Я открыл scripts/benchmark_results.json — вывод делать рано:
1. Ответов моделей в файле нет — только «JSON верный: да/нет». Почему gemini «провалился» 2 раза, а Claude 5 раз, не видно; «видимо» — не вывод. Сохраняй сырой ответ каждой модели. Частая причина — JSON в обёртке ```json ... ``` — это не нарушение роли: разбор должен её снимать, а «вышла из роли» считать отдельно, по смыслу ответа.
2. cached_tokens, cache_read_tokens и total_cost — нули во всех 15 вызовах. Кэш у CloseRouter работает (в прошлом модуле был виден) — значит, скрипт читает не те поля. Посмотри в документации https://closerouter.dev/closerouter-api.md и в сыром usage одного ответа, где лежат кэш и стоимость.
3. У gpt-5.4-mini первый вызов 6 141 токенов промпта, остальные ~1 770 — среднее 2 644 вводит в заблуждение. Показывай по вызовам, а «добавку» считай не от длины в символах (/4 для русского текста неверно), а сравнением моделей между собой на одном промпте.
Сделай каждый вопрос дважды подряд (второй раз — для кэша). Перезапусти, покажи таблицу по вызовам и по три сырых ответа gemini и Claude, которые не прошли. Потом вывод. Отвечай по-русски, ключ не печатать.

Что показал честный замер

  • gpt-5.4-mini и gemini-3.6-flash ответили по базе 10 из 10.
  • Claude через CloseRouter: 0 из 10. Он отвечал как универсальный помощник: «существуют тысячи курсов Python…», «какую школу вы рассматриваете?». Базу и формат бланка не замечал.
  • На одном и том же промпте у Claude prompt_tokens на 1 231 больше, чем у gemini. Это скрытая инструкция прокси: CloseRouter отдаёт модели Anthropic через Claude Code и подмешивает свою инструкцию, наша идёт вторым голосом.
  • Один ответ стоил около 0,000116 $ у gpt-5.4-mini и около 0,000244 $ у gemini-3.6-flash.

Ещё три поправки я нашёл сам, открыв сырой ответ одного вызова. Стоимость лежит в usage.cost, а агент писал null. У gemini есть скрытые рассуждения (модель сначала «думает» про себя, и это оплачивается как ответ): в поле usage.completion_tokens_details.reasoning_tokens около 300 из примерно 400 токенов ответа, у агента было 0. Поэтому gemini дороже gpt, хотя цена за токен у неё ниже. А кэша на дату прогона действительно не было.

Вот так выглядит сырой ответ gemini на «Сколько стоит курс Python?» (сокращён):

{
  "answer": "Стоимость курса «Python для начинающих» зависит от формата обучения: ... В группе (до 6 человек) — 1 500 руб. за занятие (72 000 руб. за полный курс из 48 занятий) ...",
  "action": "none",
  "context": "Предоставлена информация о стоимости курса Python для начинающих"
}
Два столбика prompt_tokens на одном и том же промпте. Слева gemini-3.6-flash: столбик «наш промпт», ответов по базе 10 из 10. Справа Claude через CloseRouter: тот же столбик плюс сверху красный кусок «+1 231 токен, которых мы не писали», ответов по базе 0 из 10.

Четыре правила проверки модели

Ошибка за всеми четырьмя одна: одно наблюдение принимают за свойство модели. Правила пришли из боевых случаев ИИ-наставника и подходят любому ассистенту.

  1. Формат проверяют минимум на двух непохожих вопросах. Модель, давшая верный бланк 3 раза из 3 на одном вопросе, на другом вернула другую схему целиком, с русскими названиями полей. Для ассистента: короткий вопрос и длинный, с ответом в базе и без.
  2. Скорость меряют несколькими проходами в разные часы. Время ответа поставщика менялось втрое за пять минут. Один замер годится, чтобы заметить, и не годится, чтобы решать.
  3. Роль проверяют отдельно: настоящий промпт целиком, короткий диалог и прямая просьба нарушить. Один срыв означает отказ от модели, допуск даёт минимум два чистых прогона. Через прокси вердикт живёт, пока не поменялся маршрут, поэтому перепроверять периодически.
  4. Поддакивание проверяют вопросом с уверенным неверным числом. «Курс ведь 3 месяца?» при 6 в базе. Согласие с однозначно неверной ценой или сроком означает отказ от модели.

Почему правило про маршрут не формальность: во внутреннем замере 06.10 Claude Sonnet 5 через CloseRouter роль держал, а 11.10 ответил по базе 0 раз из 10. Полный прогон по этим правилам — в уроке 8.5. Сейчас из замера берём главное: модели Anthropic для роли ассистента через CloseRouter не ставим без своего теста.

Скорость меряй с сервера бота

По замеру с моего ноутбука основной стала gpt-5.4-mini, запасной gemini. В живом боте вышло иначе. С сервера в Амстердаме gpt-5.4-mini не отвечала за 60 секунд (с ноутбука 9,4 секунды). Бот ждал её, потом переключался на gemini, и человек получал ответ через 35–42 секунды. На «Кто ты?» обе модели не успели, и бот написал «Извините, произошла ошибка». Gemini с сервера отвечала за 3–7 секунд, её и поставили основной.

Вывод: скорость меряют там, где работает бот, и в разные часы. Для этого хватит короткой пробы: один и тот же вопрос к основной и запасной модели и время ответа. Файл кладут на сервер временно, в /tmp, запускают два раза с перерывом в час и удаляют. В папку бота его не клади: scripts/ на сервер не выкладываем (урок 8.6), а после замера проба не нужна. Адрес запроса сверь с тем, что стоит в коде бота (документация closerouter.dev/closerouter-api.md). Ключ проба берёт из .env бота и не печатает.

# proba.py: временная проба скорости с сервера бота. После замера удалить.
import json, time, urllib.request

env = {}
for line in open("/opt/bot-kodium/.env", encoding="utf-8"):
    if "=" in line and not line.startswith("#"):
        k, v = line.strip().split("=", 1)
        env[k] = v.strip('"')

for model in (env["MODEL_MAIN"], env["MODEL_FALLBACK"]):
    body = json.dumps({"model": model, "messages": [
        {"role": "user", "content": "Сколько длится курс Python?"}]}).encode()
    req = urllib.request.Request(
        "https://api.closerouter.dev/v1/chat/completions", data=body,
        headers={"Authorization": "Bearer " + env["CLOSEROUTER_API_KEY"],
                 "Content-Type": "application/json"})
    t = time.time()
    try:
        urllib.request.urlopen(req, timeout=90).read()
        print(model, round(time.time() - t, 1), "с", time.strftime("%H:%M"))
    except Exception as e:
        print(model, "ошибка", type(e).__name__, round(time.time() - t, 1), "с")
scp -i ~/.ssh/<файл SSH-ключа> proba.py root@<адрес сервера>:/tmp/proba.py
ssh -i ~/.ssh/<файл SSH-ключа> root@<адрес сервера> "python3 /tmp/proba.py"
# через час ещё раз вторую строку, потом удалить:
ssh -i ~/.ssh/<файл SSH-ключа> root@<адрес сервера> "rm /tmp/proba.py"

Цены моделей меняются. Пример на 11.10.2026 за миллион токенов, вход и выход: gemini-3.6-flash 0,03 и 0,15 $; gpt-5.4-mini 0,0587 и 0,352 $. Та же gpt-5.4-mini 9 октября стоила 0,0225 и 0,135 $. Свежие цены смотри на главной CloseRouter или запросом списка моделей со своим ключом.

Сломай и почини

  1. Запусти замер дважды с перерывом в час и сравни время ответа одних и тех же моделей. Разница в разы — нормальная картина, решение принимай по обоим проходам.
  2. Временно убери из базы раздел «Чего школа не делает», задай в скрипте замера вопрос «Готовите к ЕГЭ по информатике?» и прочитай сырые ответы. Если модель достроила «да», ты увидел, зачем этот раздел. Верни раздел и прогони снова.

Если не получилось

  • Агент выбирает модель «потому что лучше следует инструкциям» → попроси замер на твоих моделях из .env, выбор только по таблице.
  • Ошибка 401, агент говорит, что сервис не существует → проверь адрес в скрипте по документации closerouter.dev/closerouter-api.md; ключ рабочий, если через него отвечают мониторинг и дашборд.
  • В ответе агента кусок ключа → скажи агенту, замени ключ в кабинете CloseRouter.
  • В таблице «JSON неверный» без причины → попроси сырой ответ; часто там обёртка из кавычек, её снимает код.
  • Стоимость и рассуждения нули → открой сырой usage одного ответа и покажи агенту, где лежат cost и reasoning_tokens.
  • У модели prompt_tokens намного больше, чем у других на том же промпте → скрытая инструкция прокси; эту модель для роли не ставь без теста роли.
  • С ноутбука модель быстрая, в боте долгие ответы → замерь с сервера бота.
  • Нужно вернуть код как было → git revert <хеш неудачного коммита>: он отменяет коммит новым коммитом, история остаётся. Вернуть один файл как в последнем коммите: git checkout -- <файл>. git reset --hard комплект из урока 1.11 блокирует намеренно: он стирает несохранённое.

Контрольный вопрос. Замер: модель A ответила по базе 10 из 10, у модели B prompt_tokens на том же промпте на 1 231 больше, а отвечает она про «тысячи курсов Python». Что это значит?

АМодель B просто умнее и отвечает шире
БУ модели B закончился лимит ключа
ВМодель A сломалась, раз отвечает так коротко
ГУ модели B в запрос подмешана чужая инструкция, роль ассистента она не держит

Подсказка: Откуда в запросе берутся токены, которых ты не писал?

На курсе задания проверяются автоматически. Решить первое задание бесплатно →  ·  Подобрать курс

Контрольный вопрос. Агент получил ошибку 401 и пишет: «CloseRouter, видимо, вымышленный сервис, предлагаю публичные бенчмарки». Мониторинг и дашборд через тот же ключ работают. Что проверить первым?

АКупить подписку у другого поставщика
БАдрес сервиса в скрипте агента по документации
ВУдалить ключ из .env
ГСогласиться на публичные бенчмарки

Подсказка: Если ключ работает в других программах, где ошибка?

На курсе задания проверяются автоматически. Решить первое задание бесплатно →  ·  Подобрать курс

Контрольный вопрос. С ноутбука модель отвечает за 9 секунд, а бот на сервере ждёт её 60 секунд и не дожидается. Как правильно выбрать основную модель?

АПо замеру с ноутбука: он точнее
БВзять самую дешёвую модель
ВЗамерить с сервера бота в разные часы и поставить основной ту, что там отвечает быстро
ГУвеличить ожидание до пяти минут

Подсказка: Где живёт бот и где он ждёт ответа?

На курсе задания проверяются автоматически. Решить первое задание бесплатно →  ·  Подобрать курс

Задание. МИССИЯ «Модель по замеру». 1. В папке своего бота заведи git и сделай первый коммит (задание 49, свой путь). 2. Дай агенту задания 50 и 50d по образцу урока: один системный промпт со своей базой, пять разных вопросов, каждый дважды, три модели, сырые ответы в файл. 3. Открой файл результатов сам. Посчитай по сырым ответам, сколько раз каждая модель ответила по базе. Найди у одного ответа usage.cost и reasoning_tokens. 4. Сравни prompt_tokens разных моделей на одном промпте. 5. Запусти пробу скорости с сервера бота (proba.py из урока) два раза с перерывом не меньше часа, запиши время ответа и удали пробу с сервера. 6. Выбери основную и запасную модель. Принято, если: приложены строка git log с первым коммитом, таблица «модель → ответов по базе из 10 → prompt_tokens → cost одного ответа → reasoning_tokens», по одному сырому ответу на каждую модель, время ответа с сервера в двух проходах с указанием часа и твой выбор основной и запасной модели с объяснением в двух-трёх предложениях. Ключа (даже части) в приложенном нет.

Подсказка: Сначала посчитай ответы по базе в сырых ответах, а потом сравни со своим счётом то, что написал агент.

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

На курсе задания проверяются автоматически. Решить первое задание бесплатно →  ·  Подобрать курс

Что дальше

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

Итог:
• в папке бота есть git, и каждый шаг можно откатить;
• модель выбирается по таблице замера, а не по памяти агента;
• выводы делаются по сырым ответам и полям usage, а не по итоговой фразе;
• модели Anthropic через CloseRouter для роли без своего теста не ставятся;
• скорость меряется с сервера бота, в разные часы.

← Назад  ·  ↑ В начало урока  ·  ⌂ В начало курса  ·  Вперёд →

Школа Виктора Комлева