Перейти к содержимому
03 дек. 2025 г.·7 мин чтения

Стабильность ответов при температуре 0: как мерить риск

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

Стабильность ответов при температуре 0: как мерить риск

Что ломается даже при температуре 0

Температура 0 не превращает модель в калькулятор. Она только сильнее тянет выбор к самому вероятному следующему токену. Если рядом есть два почти равных варианта, модель может выбрать то один, то другой. Снаружи это выглядит странно: запрос тот же, а ответ немного меняется.

Чаще всего сначала меняется не смысл, а форма. Модель переставляет пункты местами, берет другую формулировку, сокращает объяснение или добавляет лишнюю фразу. Для рекламного текста это мелочь. Для сценария, где ответ уходит клиенту, в CRM или в отчет, даже такая разница иногда ломает процесс.

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

Разницу удобно делить на два типа. Безобидная - это другой порядок пунктов, другие слова, короче или длиннее ответ. Опасная - это смена метки, итогового вывода, числа, приоритета действия или заполнения поля.

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

То же происходит при извлечении данных. Сегодня модель достала из договора верную дату и сумму, а на следующем прогоне поменяла поля местами или пропустила условие. Формально ответ есть в обоих случаях. По делу один из них уже нельзя пускать дальше.

Поэтому проверять стабильность лучше не на абстрактных вопросах вроде "объясни, что такое кредит", а на реальных сценариях: классификация тикетов, проверка анкет, суммаризация звонков, заполнение JSON. Именно там видно, где вариативность безвредна, а где она меняет решение и стоит денег.

Откуда берутся расхождения

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

Такой сдвиг часто дает не сам текст запроса, а все, что происходит вокруг него. Провайдер может отправить одинаковый вызов на разные реплики, сборки или даже разные версии одной модели под тем же именем. На выбор токена влияют декодер, округление чисел, батчинг соседних запросов и железо. Системный промпт, шаблон чата, JSON schema и описания инструментов тоже добавляют скрытый текст, которого пользователь не видит.

Скрытые обновления встречаются чаще, чем кажется. Провайдер может обновить веса, токенизатор, safety-слой или внутренний chat template и не менять публичное имя модели. Снаружи API-вызов выглядит так же, но внутри это уже другой путь.

Похожая история с системным сообщением и инструментами. Один SDK добавляет фразу вроде "отвечай кратко", другой вставляет строгую JSON-схему, третий передает функции в другом формате. Для модели это уже другой промпт, даже если ваш user message не менялся.

Контекст тоже влияет сильнее, чем многие ждут. Если история длинная, одна платформа обрежет ранние сообщения, другая сократит tool output, третья ужмет retrieval-блок. Результат меняется не потому, что модель "передумала", а потому что она увидела другой набор токенов.

Есть и совсем простой случай: слишком маленький max_tokens. Тогда модель обрывает ответ раньше и начинает экономить слова с первых строк. Иногда это выглядит как странная вариативность, хотя причина в лимите.

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

Где расхождения стоят денег

Температура 0 не спасает от лишних расходов, если модель участвует в рабочих решениях. На демо это выглядит как мелочь. В продакшене одна и та же заявка может уйти в разную очередь, получить разный статус или заставить сотрудника перепроверять то, что вчера проходило автоматически.

Самый частый пример - классификация заявок и обращений. Допустим, банк получает жалобу: "Списали деньги, карту не терял, операцию не подтверждал". В одном запуске модель ставит класс "оспаривание операции", в другом - "общая жалоба по карте". Дальше меняется маршрут: в первом случае кейс идет в команду fraud и попадает в SLA 2 часа, во втором - в обычную поддержку с ответом через день. Цена такого расхождения понятна: штраф за просрочку, повторный контакт клиента и лишняя ручная работа.

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

Ответы клиентам тоже расходятся не только по стилю. Иногда меняется факт. Например, в одном ответе модель пишет, что возврат займет до 3 дней, а в другом - до 10. Даже если оба текста вежливые, один из них создает ложное ожидание. Бывает и мягче, но тоже неприятно: сегодня ответ спокойный и деловой, завтра тот же промпт дает сухой тон, который похож на отказ.

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

Как проверить стабильность шаг за шагом

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

Сначала заморозьте окружение. Зафиксируйте одну модель, одного провайдера, один эндпоинт и полный набор параметров: temperature = 0, top_p, max_tokens, system prompt и seed, если он доступен. Затем соберите 30-100 примеров для одного сценария. Не смешивайте в один прогон чат поддержки, суммаризацию и извлечение полей. Чем уже сценарий, тем честнее вывод.

После этого прогоните каждый пример много раз в одинаковых условиях. Часто хватает 10-20 повторов на запрос, чтобы увидеть, где модель начинает "плавать". Между повторами не меняйте ничего, даже один пробел в промпте.

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

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

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

Такой тест быстро показывает реальную картину на ваших данных. Иногда модель совпадает по смыслу в 95% случаев, но ломает структуру в 8% ответов. Для рабочей системы это уже заметный риск: один сломанный JSON из двадцати легко превращается в лишние ретраи, ручную проверку и сбой на следующем шаге.

Как собрать набор сценариев

Проверьте open-weight рядом
Сравните кейсы на моделях провайдеров и на хостируемых open-weight моделях.

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

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

Хорошая выборка обычно включает короткие запросы в 1-2 строки, длинные сообщения с лишним текстом, пересланные письма, логи, куски чатов, примеры с опечатками и пропусками, а также редкие случаи, где ошибка обходится дороже обычного.

Редкие случаи часто дают самую полезную картину. Для банка это может быть жалоба, где модель должна отличить обычный вопрос от признаков мошенничества. Для ритейла - возврат с противоречивым описанием. Таких примеров немного, но именно на них расхождения заметнее и дороже.

Не исправляйте входные данные перед тестом. Если пользователь пишет с ошибками, пропускает даты или формулирует мысль расплывчато, оставьте это как есть. Иначе вы измерите поведение модели в лаборатории, а не в продакшене.

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

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

Как считать риск без сложной математики

Риск в таких проверках - это не абстрактная "нестабильность", а доля прогонов, где ответ ушел в сторону. Для этой темы такого измерения обычно достаточно: вы не спорите, модель "в целом нормальная" или нет, а видите, на каких кейсах она сбоит и как часто.

Один и тот же кейс лучше гонять серией, а не один раз. На практике хватает 30-50 повторов на каждый сценарий. Если кейс редкий, но дорогой, прогонов стоит сделать больше.

Считать риск удобно в три слоя. Сначала проверьте точное совпадение текста, если для вас важна формулировка. Это нужно для шаблонных писем, юридических фраз и фиксированных ответов. Потом сравните структуру JSON: все ли поля на месте, те ли типы данных пришли, не поменялись ли метки и названия. Отдельно посмотрите на итоговое бизнес-решение. Даже если текст разный, решение может остаться тем же.

Допустим, у вас есть сценарий "определи категорию заявки и верни JSON". Вы сделали 50 прогонов одного и того же запроса. Текст совпал полностью в 34 случаях, JSON по структуре совпал в 47, бизнес-метка совпала в 49. Тогда риск по тексту равен 32%, по структуре 6%, по бизнес-решению 2%.

Такая раскладка быстро отрезвляет. Иногда команда спорит о вариативности формулировок, хотя настоящая проблема в том, что в 4% прогонов модель меняет метку и отправляет заявку не в тот поток.

Для выпуска в продакшен нужен простой порог. Например, для критичных JSON-интеграций допустимо только 0% поломок структуры, для бизнес-метки - не больше 1%, а для текста порог можно не ставить вовсе, если wording не влияет на процесс. Если хотя бы один важный кейс выходит за порог, его не пускают в релиз.

Пример рабочего сценария

Начните с живых кейсов
Прогоните один рабочий сценарий через AI Router и измерьте разброс на повторах.

Возьмем обычную задачу поддержки: модель читает обращение клиента и должна вернуть две вещи - тему заявки и срочность. На бумаге это просто. В реальной очереди даже маленькое расхождение меняет, кто получит тикет и как быстро его возьмут в работу.

Пример текста: "Не могу войти в личный кабинет. Сегодня нужно провести платеж, иначе сорвем отгрузку". Один и тот же запрос можно прогнать много раз даже при температуре 0 и получить не совсем одинаковый результат.

В одних прогонах встречается:

  • тема: "доступ", срочность: "высокая"
  • тема: "техническая проблема", срочность: "высокая"
  • тема: "платежи", срочность: "средняя"

С виду разница небольшая. Но маршрут уже другой. "Доступ" уходит команде авторизации, "техническая проблема" - в общую линию поддержки, а "платежи" - в биллинг. Если срочность падает с высокой до средней, заявка может не попасть в быстрый SLA. Клиент написал почти то же самое, а бизнес получил разный итог.

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

Разброс обычно снижается, когда промпт задает жесткие рамки. Вместо просьбы "определи тему и срочность" лучше дать фиксированные варианты и правило выбора. Например: тема только одна из [access, billing, tech], срочность только одна из [low, medium, high], если клиент не может выполнить платеж сегодня - ставь high, если в тексте две темы - выбирай ту, из-за которой пользователь не может завершить действие.

Еще лучше требовать строгий формат ответа, без свободных формулировок. Например, только JSON с полями topic и priority. Тогда модель меньше "думает вслух" и реже уходит в соседние категории.

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

Частые ошибки в проверке

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

Самая частая ошибка проста: делают один прогон, видят хороший ответ и ставят галочку. Так вы проверяете удачу, а не риск. Один и тот же запрос лучше гонять сериями, хотя бы по 20-50 повторов на одном наборе входов. Иначе мелкие расхождения просто не успеют проявиться.

Не меньше вредит слишком удобная выборка. Команда берет чистые, короткие, хорошо сформулированные примеры и радуется ровным результатам. В работе таких данных мало. Нужны опечатки, длинные сообщения, смешанные языки, лишние поля и обрывы текста. Если тестировать только витринные кейсы, потом сбой придет из обычного боевого трафика.

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

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

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

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

Нормальная проверка выглядит скучно, и это хорошо. Чем меньше в ней догадок и памяти "на словах", тем проще потом понять, где именно появляется риск расхождений.

Короткий чек-лист перед запуском

Добавьте контроль на уровне ключа
Используйте аудит-логи, маскирование PII и rate-limits в рабочих проверках.

Перед релизом зафиксируйте не только промпт, но и весь контекст запроса. Температура 0 не спасает, если между прогонами меняются модель, версия, top_p, seed, системная инструкция, схема tool calls или маршрут через другого провайдера.

У команды должен быть короткий набор проверок, который можно пройти за 15-20 минут. Если хотя бы один пункт висит в воздухе, расхождения обычно всплывают уже на живом трафике.

  • Запишите все параметры запроса в одном месте и не меняйте их между тестами.
  • Проверьте, что тестовый набор не состоит только из простых примеров.
  • Считайте не только сходство текста, но и итоговое решение.
  • Договоритесь о допустимом разбросе заранее.
  • Повторяйте проверку после любого заметного изменения: новой модели, правки промпта, смены провайдера, маскирования PII или обновления SDK.

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

Этот список кажется строгим только на бумаге. На деле он экономит много часов разборов после запуска.

Что делать дальше

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

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

Дальше схема простая: выберите 20-50 запросов из рабочего потока, запустите каждый запрос серией повторов, затем повторите тот же набор на второй модели или у другого провайдера. Сравнивайте не только качество, но и разброс ответов.

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

Если вы сравниваете несколько моделей через один OpenAI-совместимый эндпоинт, такие проверки проще повторять без лишней переделки интеграции. Например, в AI Router на airouter.kz можно сменить base_url на api.airouter.kz и прогонять один и тот же набор кейсов через разных провайдеров, не меняя SDK, код и промпты. Это удобно, когда нужно сравнить модели в одинаковой обвязке, а не разбираться, что именно поменялось вокруг них.

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

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

Часто задаваемые вопросы

Температура 0 гарантирует одинаковый ответ?

Нет. Температура 0 только тянет модель к самому вероятному продолжению, но не убирает расхождения совсем. Если варианты почти равны по вероятности или вокруг запроса меняется скрытый контекст, ответ может немного или заметно уйти в сторону.

Какие расхождения можно считать нормальными?

Безопасные расхождения обычно меняют форму: порядок пунктов, длину ответа, отдельные слова. Опасные меняют решение: класс обращения, число, дату, приоритет, маршрут заявки или поле в JSON.

Почему один и тот же API-вызов все равно дает разный результат?

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

Сколько прогонов нужно для проверки стабильности?

Начните с одного рабочего сценария и соберите 30–100 живых примеров. Каждый пример прогоните 10–20 раз в одинаковых условиях; для дорогих редких кейсов дайте 30–50 повторов, чтобы увидеть реальный разброс.

Что нужно логировать во время теста?

Сохраняйте полный ответ, параметры вызова, имя модели, провайдера, system prompt, top_p, max_tokens, seed, если он есть, и finish_reason. Записывайте еще дату теста и версию парсера, иначе потом команда не поймет, что именно изменилось.

На каких задачах лучше проверять стабильность?

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

Как оценивать стабильность для текста и JSON?

Смотрите на три вещи отдельно: точный текст, структуру и итоговое решение. Для свободного текста важнее, сохранился ли смысл, а для интеграций с JSON важнее, пришли ли все поля и те ли типы данных.

Можно ли снизить разброс одним промптом?

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

Когда нужно запускать проверку заново?

Повторяйте тест после любой заметной правки: смены модели, провайдера, SDK, системной инструкции, схемы инструментов, маскирования PII или лимитов токенов. Даже одна новая строка в промпте может сдвинуть маршрут ответа.

Как сравнить несколько моделей без переделки интеграции?

Да, так быстрее и честнее. Если вы гоняете один и тот же набор кейсов через один OpenAI-совместимый интерфейс, вы меньше рискуете спутать разницу моделей с разницей в обвязке. В AI Router для этого можно сменить base_url на api.airouter.kz и оставить SDK, код и промпты без переделки.