Как писать системные промпты, которые реально работают

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

3 мин · · Обновлено · разработка · промпты

Системный промпт вида «Ты полезный ассистент, отвечай кратко и по делу» ничего не даёт — модель и так пытается быть полезной по умолчанию. Разница между промптом, который меняет поведение, и промптом, который модель молча игнорирует, в конкретике: чёткие ограничения, примеры формата и явные условия «если — то» работают, общие пожелания — нет.

Формат запроса с системным промптом

В Anthropic Messages API системная инструкция передаётся отдельным полем system, а не как сообщение в массиве messages:

bash
curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "max_tokens": 500,
    "system": "Отвечай только на основе переданного контекста. Если ответа нет в контексте, пиши: \"В предоставленных данных нет ответа на этот вопрос\".",
    "messages": [{"role": "user", "content": "Контекст: ... Вопрос: ..."}]
  }'

В OpenAI Chat Completions системная инструкция — это отдельное сообщение с ролью system в начале массива messages:

bash
curl https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-sol",
    "messages": [
      {"role": "system", "content": "Отвечай только JSON-объектом вида {\"category\": string, \"confidence\": number}. Никакого текста вне JSON."},
      {"role": "user", "content": "Классифицируй отзыв: Доставка задержалась на неделю"}
    ]
  }'

Перепутать поля — распространённая ошибка при переносе кода между Anthropic и OpenAI-совместимыми клиентами: у одних system — параметр верхнего уровня, у других — роль внутри messages.

Конкретика вместо общих формулировок

«Отвечай кратко» модель интерпретирует по-своему — три предложения для одной задачи, абзац для другой. Работает ограничение с числом или структурой:

Плохо: Отвечай кратко и по делу.

Хорошо: Ответ — не более 3 предложений. Без вводных фраз и оговорок.

То же самое с форматом. «Верни JSON» модель иногда оборачивает в текстовое пояснение или markdown-блок ```json. Если нужен голый JSON без обёртки, это стоит написать явно и показать пример:

Верни только JSON-объект, без markdown-разметки, без пояснений до или после. Формат: {"result": "...", "score": 0.0}

Явные условия вместо пожеланий

Формулировка «если не уверен, лучше не отвечай» слишком расплывчата. Модель не знает порог уверенности, о котором идёт речь. Работает явное условие с конкретным действием:

Если в контексте нет прямого ответа на вопрос пользователя — верни строго: "Недостаточно данных для ответа." Не додумывай факты, которых нет в контексте.

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

Роль и рамки, а не характер

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

Ты обрабатываешь тикеты поддержки. Задача: определить категорию (billing, technical, account, other) и приоритет (low, medium, high). Правила: - billing — всё, что касается платежей, счетов, возвратов - technical — ошибки в работе продукта - account — доступ, пароли, настройки профиля - high приоритет — если в тексте есть слова "не работает", "потерял доступ", "срочно" Верни JSON: {"category": "...", "priority": "..."}

Явные правила категоризации (какие слова к какой категории) снижают долю случайных ошибок сильнее, чем любое описание «ты внимательный классификатор».

Проверка на граничных случаях

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

bash
import requests

test_cases = [
    "Доставка задержалась на неделю",
    "",
    "asdkjaslkdj",
    "Все отлично, спасибо!",
    "ПОЧЕМУ ВЫ СПИСАЛИ ДЕНЬГИ ДВАЖДЫ????",
]

for text in test_cases:
    resp = requests.post(
        "https://api.zukko.pro/v1/chat/completions",
        headers={"Authorization": "Bearer fetch_ваш_ключ", "X-Zukko-Cache": "true"},

Заголовок X-Zukko-Cache: true включает бесплатный кэш при повторных прогонах одного и того же тестового набора — полезно, когда промпт правится десятками итераций. temperature: 0 здесь — для детерминированности проверки (и тоже включает кэш), но работает только у моделей, которые принимают этот параметр: GLM принимает, а claude-sonnet-5 и claude-opus-5-5 могут отклонить temperature, отличную от 1, ошибкой 400 — с ними temperature не передавайте.

Бесплатная проверка длины промпта

Перед тем как гонять промпт по всем тестовым случаям, полезно один раз проверить, сколько токенов он занимает — особенно если системная инструкция длинная и включает примеры (few-shot). Эндпоинт подсчёта токенов бесплатный и не выполняет сам запрос:

bash
curl https://api.zukko.pro/v1/messages/count_tokens \
  -H "Authorization: Bearer fetch_ваш_ключ" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "system": "Длинная системная инструкция с примерами...",
    "messages": [{"role": "user", "content": "тестовый вопрос"}]
  }'

Это помогает поймать раздутый промпт до того, как он начнёт съедать контекст на каждом запросе — при большом количестве few-shot примеров системная инструкция сама по себе может занимать заметную часть контекстного окна на длинных диалогах (у Claude и GPT — 1 000 000 токенов, у GLM — 128K).

Разные модели — разная чувствительность к формулировкам

Для Claude часто пишут структурированные инструкции с XML-подобной разметкой (<context>...</context>), для GPT-5.6 и GLM — нумерованные списки правил; что сработает лучше в вашем случае, проверьте на своих задачах. Если промпт переносится между моделями через один и тот же ключ — а это удобно делать, раз протокол общий — стоит проверять его на каждой модели отдельно, а не полагаться, что формулировка, работающая на claude-sonnet-5, даст тот же результат на gpt-5.6-sol без изменений.

Итог

Работающий системный промпт — это конкретные ограничения (числа, форматы, списки правил), явные условия «если — то» вместо общих пожеланий и проверка на граничных случаях, а не на трёх удобных примерах. Бесплатный подсчёт токенов через /v1/messages/count_tokens и кэш по заголовку X-Zukko-Cache: true делают итерации по промпту дешёвыми, так что нет смысла экономить на количестве тестовых прогонов перед тем, как выкатывать промпт в продакшен.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.