Как писать системные промпты, которые реально работают
Большинство системных промптов не работают не из-за модели, а из-за формулировок. Разбираем, что реально меняет поведение, а что модель просто игнорирует.
3 мин · · Обновлено · разработка · промпты
Системный промпт вида «Ты полезный ассистент, отвечай кратко и по делу» ничего не даёт — модель и так пытается быть полезной по умолчанию. Разница между промптом, который меняет поведение, и промптом, который модель молча игнорирует, в конкретике: чёткие ограничения, примеры формата и явные условия «если — то» работают, общие пожелания — нет.
Формат запроса с системным промптом
В Anthropic Messages API системная инструкция передаётся отдельным полем system, а не как сообщение в массиве messages:
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:
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": "..."}
Явные правила категоризации (какие слова к какой категории) снижают долю случайных ошибок сильнее, чем любое описание «ты внимательный классификатор».
Проверка на граничных случаях
Промпт, который выглядит рабочим на трёх примерах, часто ломается на четвёртом — пустом вводе, вводе на другом языке, вводе с опечатками. Стоит держать набор граничных случаев и прогонять промпт через них при каждом изменении:
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). Эндпоинт подсчёта токенов бесплатный и не выполняет сам запрос:
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.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.