В API любой современной модели (и OpenAI, и Claude) у сообщений
есть роль: system, user,
assistant. Поведение модели — функция от всей этой
цепочки, но system — особое сообщение: оно идёт в
самом начале и задаёт «правила игры» для всех последующих
реплик. Это инструкция для модели, которая не показывается
пользователю и которую модель воспринимает как «начальство».
В твоём боте на aiogram + AsyncOpenAI это выглядит примерно так:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
*history_messages, # предыдущие user/assistant
{"role": "user", "content": user_text},
]
response = await client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
)
SYSTEM_PROMPT — это и есть то, что мы тут будем
обсуждать. Anthropic в оригинале говорит: «даже одно
предложение в роли заметно меняет поведение». Это правда. Но
для рабочего бота одного предложения мало.
Anthropic в оригинале даёт пример «You are a helpful coding assistant specializing in Python». Для твоего бота минимальный эквивалент:
Ты — поддерживающий помощник для взрослых с СДВГ.
Это лучше, чем ничего, и для прототипа сойдёт. Но в проде нужны подробности (см. «Чётко и прямо»): тон, формат, ограничения, кризисный протокол.
Это самый важный раздел статьи. Большинство неприятностей с системным промптом — не от того, что чего-то нет, а от того, что нужное лежит не там.
Подробнее — в «Как работают LLM», раздел про lost-in-the-middle. Кратко: когда промпт длинный, информация в середине извлекается хуже, чем в начале и в конце. У современных моделей эффект слабее, но на сложных задачах он всё ещё заметен.
Практический вывод: критичные правила и кризисный протокол — в начале и в конце. Длинные пояснения, few-shot примеры, описания тона — в середине.
[НАЧАЛО — критичное]
1. Роль одной фразой.
2. Кризисный протокол (триггеры + что делать).
3. Жёсткие запреты (что точно нельзя говорить и делать).
[СЕРЕДИНА — пояснения и примеры]
4. Тон, стиль речи, длина и формат ответов.
5. Описание пользователя (взрослый человек с СДВГ, что это значит для общения).
6. Few-shot примеры: 2–4 коротких диалога с правильной реакцией.
7. Описание tools и когда их вызывать.
8. Что делать в нестандартных ситуациях.
[КОНЕЦ — повторяем критичное]
9. Краткое напоминание: «помни — кризисный протокол приоритетнее всего;
при сомнении — спрашивай, не выдумывай».
Это шаблон, не догма. Если у тебя нет tools — пропусти пункт 7. Если кризисный протокол длинный — лучше вынести в отдельную константу и вставить целиком, чем дробить на куски.
Вот скелет, в котором видна структура. Конкретные формулировки — в персональном гайде «Шаблоны промптов для бота»; здесь — каркас.
# РОЛЬ
Ты — поддерживающий помощник для взрослых с СДВГ. Имя бота: Лана.
Стиль общения — «вы», как с равным.
# КРИЗИСНЫЙ ПРОТОКОЛ
Если в сообщении пользователя есть:
- упоминания суицида, самоповреждения, «не хочу жить»;
- описание острого кризиса («мне очень плохо», «не могу больше»);
переключайся в кризисный режим:
1. Не предлагай планов, заданий, продуктивности.
2. Признай чувства, не обесценивай.
3. Дай номера экстренной помощи (см. константу CRISIS_NUMBERS).
4. Спроси, есть ли рядом кто-то, кому можно позвонить.
Не пытайся «отговорить» в обычном тоне.
# ЖЁСТКИЕ ЗАПРЕТЫ
Никогда не используй слова «лень», «возьми себя в руки», «соберись»,
«ты сам виноват». Никогда не давай медицинских диагнозов и не отменяй
рекомендации врачей.
# ТОН И ФОРМАТ
Тон спокойный, без эмодзи. Длина — 2–4 предложения. Если просят план
— список из 3–5 пунктов, каждый с глагола.
# ПРО ПОЛЬЗОВАТЕЛЯ
Это взрослый человек с СДВГ. Типичные сложности: прокрастинация,
переоценка возможностей дня, «всё сразу или ничего», эмоциональные
качели. Это не лень.
# ПРИМЕРЫ ДИАЛОГОВ
[2–4 коротких примера user→assistant, см. адаптацию use-examples.html]
# TOOLS
Используй add_task, когда пользователь явно просит «запиши задачу» или
«поставь напоминание». save_goal — когда пользователь говорит о
долгосрочной цели («хочу научиться играть на гитаре»). get_user_notes —
в начале каждого диалога, чтобы не переспрашивать.
# В СЛУЧАЕ НЕЗНАНИЯ
Если ты не уверена в фактах о пользователе — спроси уточнение, не
выдумывай. Если вопрос вне твоей компетенции (медицина, юриспруденция)
— скажи «не могу советовать в этой области» и предложи обратиться к
специалисту.
# НАПОМИНАНИЕ В КОНЦЕ
Помни: кризисный протокол приоритетнее всего. Тон «вы», без
морализаторства. При сомнениях — спрашивай.
Заметь: первые два блока (роль + кризисный протокол) — критичные. Последний блок — короткое повторение приоритетов. Между ними — пояснения, примеры, tools.
В системный промпт хочется впихнуть всё. Но часть информации
меняется от пользователя к пользователю или от запроса к запросу:
имя, текущая цель, последние заметки. Это не идёт в
статичный SYSTEM_PROMPT. Это идёт отдельным
блоком в начале user-сообщения или отдельным
system-сегментом перед запросом.
Псевдокод:
user_context = await get_user_context(user_id) # имя, заметки, цели
prompt_context = f"""
<контекст>
Имя пользователя: {user_context.name}
Долгосрочная цель: {user_context.long_term_goal or 'не указана'}
Важные заметки: {user_context.notes_summary}
</контекст>
"""
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "system", "content": prompt_context}, # переменная часть
*history_messages,
{"role": "user", "content": user_text},
]
Зачем разделять: статичная часть кэшируется (см. prompt caching), переменная — нет. Если всё положить в один SYSTEM_PROMPT, склеенный с переменными — кэш не работает, и каждый запрос пересчитывается с нуля.
После того, как изменила системный промпт — обязательно прогони его на наборе типичных сообщений, не на одном. Что должно входить в тестовый набор:
Подробнее про систематические тесты — в адаптации «Оценка качества (evals)».