Формат запроса Anthropic: чем он отличается от привычного
Тот, кто раньше работал с другим поставщиком, при первом обращении к Claude обычно спотыкается на трёх мелочах. Все три исправляются за десять минут, но пока о них не знаешь, запрос возвращает ошибку без внятного объяснения.
Отличие первое: системная инструкция живёт отдельно
В распространённом формате роль и правила передаются как первое сообщение с ролью system. Здесь — иначе: это отдельное поле верхнего уровня, а список сообщений начинается сразу с реплики пользователя.
Разница не косметическая. Раз инструкция не входит в историю, её нельзя случайно потерять при обрезании старых сообщений — типичная беда длинных диалогов, где вместе с давними репликами вымывается и системный промпт. Здесь он живёт своей жизнью и передаётся с каждым запросом целиком.
Побочный эффект: при переносе кода недостаточно переименовать поле. Если у вас в истории несколько системных сообщений, разбросанных по диалогу, их придётся собрать в одно.
Отличие второе: лимит длины обязателен
Поле max_tokens нельзя не указать. У других поставщиков оно необязательное, и про него легко забыть — здесь запрос просто не пройдёт.
Это неудобно ровно один раз, а дальше начинает помогать. Забытый лимит — классический способ получить счёт на порядок больше ожидаемого, когда модель решает, что вопрос требует подробного эссе. Обязательное поле заставляет ответить на вопрос «сколько мне вообще нужно» до того, как придёт счёт.
Ставить лимит стоит по задаче, а не «с запасом». Для короткого классификатора хватит нескольких десятков токенов, для пересказа — нескольких сотен. Слишком тесный лимит обрывает ответ на середине, и это видно по признаку остановки в ответе — его стоит проверять, а не считать, что обрыв не случится.
Отличие третье: ответ — это список блоков
Текст лежит не в готовой строке, а в первом элементе массива содержимого. Привычка обращаться к нему напрямую по индексу работает до первого ответа, где блоков больше одного — например, когда модель вызывает инструмент или отдаёт рассуждение отдельным блоком. Надёжнее пройти по массиву и собрать текстовые блоки.
Полная карта полей
Про два поля стоит сказать подробнее.
temperature управляет разбросом формулировок. Низкое значение — почти одинаковые ответы на один и тот же вопрос, высокое — больше вариаций. Для задач с проверяемым результатом (классификация, извлечение полей, сведение к формату) разброс не нужен и мешает тестировать. Для текстов, где повторяемость не важна, значение по умолчанию обычно подходит.
stop_sequences — недооценённый приём. Если вы просите модель выдать структуру и знаете, чем она заканчивается, укажите строку остановки: генерация прервётся там же, и вы не заплатите за пояснение, которое всё равно выбросите.
Разбор ошибок
Коды ответов стандартные, но тексты у посредников иногда отличаются от вендорских. Обрабатывать нужно как минимум четыре ситуации.
| Что случилось | Что делать |
|---|---|
| Неверный ключ | Не повторять запрос, это не исправится само |
| Превышена частота обращений | Повторить с нарастающей задержкой |
| Слишком длинный запрос | Подрезать историю или фрагменты и повторить |
| Сбой на стороне сервиса | Повторить ограниченное число раз |
Разница между первой строкой и остальными принципиальная: повторять запрос с неверным ключом бессмысленно, а некоторые библиотеки делают это по умолчанию и упираются в лимит попыток вместо того, чтобы сразу сообщить о проблеме.
Инструменты и структурированный ответ
Отдельная часть формата — вызов инструментов. Вы описываете набор функций со схемой параметров, модель в ответ предлагает вызвать одну из них с конкретными аргументами, вы выполняете вызов у себя и возвращаете результат следующим сообщением.
Тут есть ловушка при переносе кода: предложенный вызов приходит отдельным блоком в массиве содержимого, а не полем рядом с текстом. Код, написанный под другой формат, его просто не заметит — и будет казаться, что модель игнорирует инструменты.
Второй момент — ответ на вызов возвращается как сообщение с ролью пользователя и специальным блоком результата, привязанным к идентификатору вызова. Потерять этот идентификатор легко, а без него следующий ответ модели будет бессвязным.
Тот же механизм — самый надёжный способ получить строго структурированный ответ. Вместо просьбы «верни JSON» описывается инструмент с нужной схемой полей, и модель заполняет её. Разбор становится предсказуемым, а проверка типов — бесплатной.
Мелочи, на которых спотыкаются
Роли в истории должны чередоваться. Два сообщения пользователя подряд формат не принимает — их надо склеить в одно. Код, который просто дописывает реплики в список, рано или поздно на это наткнётся.
Пустое сообщение тоже не проходит. Если пользователь отправил пробел, а вы передали его как есть, вы получите ошибку валидации вместо ответа.
Последним сообщением в истории может быть реплика модели — тогда генерация продолжит её, а не начнёт заново. Приём удобен, когда нужно задать начало ответа, но случайное попадание в это состояние даёт странные результаты.
Что переносится без изменений
Хорошая новость для тех, кто мигрирует: почти всё остальное совпадает, и формат не меняется от того, обращаетесь вы к вендору напрямую или к anthropic api через российский доступ. Чередование ролей в истории, передача предыдущих ответов модели, ограничение длины диалога, потоковая отдача — механика одинаковая. Переписывать приходится сборку запроса и разбор ответа, то есть две функции, а не весь слой работы с моделью.
Если в проекте таких мест много, стоит потратить час и завести тонкую прослойку: одна функция принимает вашу структуру диалога и возвращает текст, внутри неё — вся специфика формата. Тогда следующая смена поставщика займёт не день, а пятнадцать минут.
Коротко
Три отличия: системная инструкция вынесена в отдельное поле, лимит длины ответа обязателен, текст ответа лежит в массиве блоков. Остальное совпадает с привычным форматом настолько, что перенос кода сводится к правке двух функций. Обязательный лимит поначалу раздражает, но именно он избавляет от самых дорогих сюрпризов в счёте.