Проектирование промптов для продакшена: системный, разработческий и пользовательский слои
Продакшен-промпты — это не «скажите ИИ, чего вы хотите». Это многослойная система: стабильные инструкции, динамический контекст, переменные конкретного вызова — управляемая как код. Архитектура, паттерны и дисциплина, отделяющие продакшен от прототипа.
Outcome: Разделять системные, разработческие и пользовательские инструкции и тестировать продакшен-промпты как версионируемые компоненты системы.
В прототипе промпт — это строка, которую вы написали за один день. В продакшене такой подход разваливается примерно в первый же месяц.
Вам захочется поменять одну часть инструкций, но не другие. Захочется разного поведения для разных тарифов клиентов. Захочется A/B-тестировать версии. Откатиться, когда что-то сломалось. Знать, когда промпт последний раз менялся и почему.
Продакшен-система промптов справляется со всем этим. Это не «написать строку». Это архитектура.
Эта статья посвящена тому, как такая архитектура выглядит — три слоя, дисциплина шаблонирования, контроль версий, оценка и операционные практики, превращающие промпты из артефактов в инфраструктуру.
В работе с продакшен-промптами есть два отдельных артефакта: переиспользуемые шаблоны и данные конкретного запроса. Версионируйте шаблоны как код. Рассматривайте отрендеренные промпты и ответы модели как чувствительные логи, если они содержат пользовательские, клиентские или внутренние данные.
Три слоя
У продакшен-промптов три отдельных слоя, у каждого свои заботы:
Системный слой. Стабильное поведение, идентичность, ограничения. Меняется редко. Принадлежит команде, проектирующей поведение ИИ.
Разработческий слой. Инструкции конкретной фичи, описания инструментов, требования к формату выхода. Меняется, когда меняются фичи. Принадлежит команде фичи.
Пользовательский слой. Конкретный запрос пользователя плюс динамический контекст (его данные, история диалога, извлечённые знания). Разный в каждом вызове.
Смешивание этих слоёв — самая частая продакшен-ошибка в промптах. Системный промпт разрастается до 5000 слов, в которых перемешаны идентичность, инструкции фич и динамический контекст, и теперь изменение одного ломает другое.
Их разделение — фундамент:
┌─────────────────────────────────────┐
│ System prompt (stable) │ Identity, behavior, hard constraints
├─────────────────────────────────────┤
│ Developer prompt (per-feature) │ Feature instructions, tools, format
├─────────────────────────────────────┤
│ User prompt (per-call) │ User query, context, conversation
└─────────────────────────────────────┘API моделей это явно поддерживают:
- OpenAI: роли
system,developer,user. - Anthropic:
system, затемmessagesс ролямиuserиassistant. Описания инструментов — отдельный параметр. - Gemini:
systemInstruction, затемcontentsс ролями.
Используйте эти различия осознанно.
Слой 1: системный промпт
Системный промпт определяет, кто такой ИИ и как он себя ведёт. Меняется редко.
Хороший системный промпт покрывает:
Идентичность. Кто такой ИИ. «Вы — ИИ-ассистент для [Компания], специализирующийся на [домен].»
Голос и стиль. Как он должен звучать. Конкретные черты, не размытые дескрипторы.
Жёсткие ограничения. То, чего он не должен делать никогда. Выдавать определённый контент, принимать определённые решения, игнорировать определённые инструкции.
Поведенческие паттерны. Как он обращается с типичными ситуациями. Отказы, эскалации, неопределённость.
Безопасность и комплаенс. Обязательные раскрытия, регуляторные правила, контент-политики.
Чего он НЕ должен содержать:
- Инструкции под конкретные фичи («для писем по продажам делай X»).
- Динамический контекст («история заказов пользователя такая...»).
- Описания инструментов (это в другое место).
- То, что часто меняется.
Хороший системный промпт — 300-1000 слов. Длиннее — становится сложно сопровождать; короче — недоопределяете поведение.
Шаблон, который работает:
You are [name], an AI assistant for [company / context].
## Ваша роль
[2-3 sentences on what you do]
## Голос и стиль
- [Specific trait 1]
- [Specific trait 2]
- [Specific trait 3]
- Do not [anti-pattern 1]
- Do not [anti-pattern 2]
## Жёсткие ограничения
- Never [hard rule 1]
- Never [hard rule 2]
- Always [hard rule 3]
## Как обрабатывать неопределённость
- If you don't know something factual: say so explicitly.
- If a user asks for something outside scope: offer what you can help with.
- If a request might cause harm: refuse and explain why.
## Ожидания к формату
- Plain text by default
- Use markdown when displaying code or structured data
- Be concise; do not pad responses with fillerЭто позвоночник. Каждое взаимодействие проходит через него. Изменения — осознанные и нечастые.
Слой 2: разработческий промпт
Разработческий промпт — фичеспецифичный. У разных фич — разные разработческие промпты.
Разработческий промпт фичи суммаризации:
Task: produce a summary of the document below.
Requirements:
- 3-5 bullet points
- Each bullet is one complete sentence
- Focus on facts and concrete claims, not impressions
- If the document contains numbers, include the most important ones
- Do not include marketing language or speculation
- If the document is ambiguous about something important, note it
Format: plain markdown bullets, no preamble.Разработческий промпт фичи code review:
Task: review the code diff below.
Output a JSON object with:
- summary: 1-2 sentence overview of the change
- concerns: array of specific issues (each: file, line, severity, description)
- suggestions: array of improvements (each: file, line, suggestion)
- approved: boolean (true if no blocking concerns)
Severity levels:
- "blocker": must be fixed before merge
- "warning": should be addressed but not blocking
- "nit": stylistic, optional
Focus on:
- Logic errors
- Security issues
- Performance issues
- Missing test coverage
- Unclear naming or structure
Skip:
- Formatting (handled by formatter)
- Subjective style preferencesУ каждой фичи свой разработческий промпт. Они хранятся отдельно, версионируются отдельно, оцениваются отдельно.
Слой 3: пользовательский промпт
Пользовательский слой — динамический. Обычно включает:
Собственно запрос пользователя. «Суммируй этот документ.»
Контекст, извлечённый системой. Документы из RAG, история клиента, история диалога.
Переменные на вызов. Имя пользователя, часовой пояс, языковые предпочтения, тариф.
Этот слой собирается программно во время вызова. Структура обычно выглядит так:
{conversation_history_summary}
{retrieved_context}
User's request: {user_query}
Additional context:
- User name: {name}
- User timezone: {timezone}
- User tier: {tier}Конкретная структура зависит от фичи. Принцип: данные идут сюда, а не в системный или разработческий промпты.
Дисциплина шаблонизации
Промпты в продакшене собираются из шаблонов. Конкатенация строк inline — прототипный подход; на масштабе он не работает.
Простая шаблонная система:
from string import Template
SUMMARIZE_TEMPLATE = Template("""
$conversation_summary
Document to summarize:
$document
User's specific instructions: $user_instructions
""")
prompt = SUMMARIZE_TEMPLATE.substitute(
conversation_summary=summarize_conversation(history),
document=document_text,
user_instructions=user_query,
)Поинтереснее: шаблонная библиотека (Jinja2, Handlebars) с условиями и partial-ами.
{% if user_tier == "enterprise" %}
You have access to advanced analysis features.
{% endif %}
{% if retrieved_context %}
Relevant context from your knowledge base:
{{ retrieved_context }}
{% endif %}
User's request: {{ user_query }}Шаблонизация предотвращает prompt injection через переменные (экранируйте пользовательский ввод, где уместно), позволяет условную логику и сохраняет структуру промпта согласованной.
Контроль версий
Промпты — это код. Храните их в системе контроля версий.
Паттерн, который работает: директория prompts/ в репозитории, по одному файлу на промпт:
prompts/
system/
main.txt
customer-support.txt
code-assistant.txt
features/
summarize.txt
classify-ticket.txt
generate-email.txt
templates/
base.j2Каждый файл — отдельный промпт, со своей историей коммитов. Изменения ревьюятся через PR. Продакшен-деплои ссылаются на конкретные версии.
Почему это важно:
- Видимость диффа. Когда промпт меняется, дифф виден в PR. Ревьюеры видят ровно то, что изменилось.
- Откат. Когда изменение что-то ломает, можно откатить.
- История. «Когда мы поменяли политику возвратов в промпте?» «Почему этот абзац здесь?» — отвечается через git blame.
- Инструменты. Линтеры, валидаторы и наборы оценок интегрируются с файловыми промптами.
Избегайте: промпты, хранимые как строки в коде (сложно найти, сложно дифать); промпты, хранимые в инструменте с интерфейсом (версионирование принадлежит инструменту, а не вам); промпты, скопированные из чата с людьми (не отслеживаются, не тестируются).
Промпт как данные: внешнее хранилище
Для промптов, которые часто меняются — A/B-тесты, вариации по тарифам, локализованные промпты — файловый контроль версий слишком медленный.
Паттерн: база или сервис, хранящий версии промптов с метаданными.
prompt = prompt_service.get(
name="summarize",
version="v3",
locale="en",
user_tier="enterprise",
)Сервис поддерживает:
- Текущие и исторические версии каждого промпта.
- Метаданные: когда добавлено, кем, зачем.
- Оценки, привязанные к каждой версии.
- Возможность отката.
Инструменты: PromptLayer, Helicone или внутренний сервис. Большинству команд достаточно внутреннего сервиса с простой схемой БД.
Интерфейс критичен. Инженеры и неинженеры (продакт, контент) должны иметь возможность редактировать промпты. Но изменения проходят через ревью и оценки до выкатки.
Изменения через оценочный шлюз
Каждое изменение промпта проходит оценки перед деплоем. Это не обсуждается для серьёзных продакшен-систем.
Поток:
- Инженер или неинженер набрасывает изменение промпта.
- Изменение прогоняется по набору оценок.
- Результаты оценок ревьюятся вместе с изменением.
- Если оценки прошли (нет регрессий, в идеале — улучшения), изменение можно одобрить.
- Одобренные изменения деплоятся.
- Постдеплойный мониторинг ловит то, что оценки пропустили.
На практике это означает, что у каждого промпта есть набор оценок, и набор гоняется в CI на изменениях промпта.
Без этой проходной изменения промпта ломают вещи непредсказуемо. С ней можно двигаться быстро и уверенно.
Практический чеклист релиза
Перед изменением продакшен-промпта проверьте минимум эти пункты:
| Проверка | Что должно быть видно | |---|---| | Владелец | У промпта есть ответственный и ясная причина изменения. | | Слои инструкций | Системный, разработческий и пользовательский слои разделены, а не смешаны в одну строку. | | Схема | Структурированный выход имеет валидируемую JSON schema или эквивалентную проверку. | | Обработка инъекций | Пользовательский и извлечённый текст не могут менять системные или разработческие инструкции. | | Оценки | Изменение проходит регрессионные проверки и включает хотя бы несколько примеров плохого ввода. | | Логи | Логируются версия шаблона, категория входа, результат и ошибки без лишних секретов. | | Откат | Понятно, как восстановить старую версию промпта и какой сигнал запускает откат. |
Сопроводительный чеклист подходит для PR или заметок к релизу: чеклист релиза продакшен-промпта.
A/B-тестирование в продакшене
Для новых промптов A/B-тестирование их против существующей версии на малой доле продакшен-трафика даёт реальный сигнал, выходящий за рамки оценок.
Паттерн:
- 95% трафика идёт через продакшен-промпт v3.
- 5% получают новый кандидат v4.
- Меряем: пользовательский фидбек, нижестоящие метрики, оценки на реальном трафике.
- После достаточного объёма данных решаем: катить v4 на 100% или оставить v3.
Инструменты: флаги функций (LaunchDarkly, внутренние), сервисы версионирования промптов (PromptLayer), кастомный роутинг.
Оговорки:
- A/B-тестирование ловит только те сигналы, что вы меряете. Без пользовательского фидбека или нижестоящей конверсии A/B-тест мало что скажет.
- Статзначимость требует объёма. Для низковолюмных фич A/B сложен.
- Не гоняйте слишком много A/B одновременно; взаимодействия путаются.
Наблюдаемость промптов
Каждый продакшен-вызов LLM должен логировать:
- Какой шаблон промпта использовался (имя, версия).
- Какие переменные подставлены.
- Финальный отрендеренный промпт.
- Ответ модели.
- Латентность, токены, стоимость.
- Downstream-сигналы (пользовательский фидбек, метрики успеха).
Это данные, которые нужны, чтобы дебажить «почему модель дала странный ответ этому пользователю». Без них вы гадаете.
Хранение: таблица в базе или инструмент наблюдаемости. Затраты реальны, если логировать всё (объём вызовов × длина в токенах × хранилище). Некоторые команды сэмплируют.
Ревью: регулярная практика (раз в неделю) читать выборку реальных продакшен-промптов и ответов. Ловит проблемы, которые автоматические оценки не ловят.
Анти-паттерны промптов
Несколько паттернов, которых стоит избегать:
Анти-паттерн 1: мега-промпт. Системный промпт на 10 000 слов, пытающийся охватить любую ситуацию. Тяжело менять, тяжело дебажить, часто игнорируется моделью на поздних инструкциях.
Лекарство: разделить на слоистые сфокусированные промпты. По одному на фичу.
Анти-паттерн 2: inline-конкатенация строк.
prompt = "You are helpful. " + (
"The user is a paid customer. " if user.tier == "paid" else ""
) + f"Their name is {user.name}. " + ...Хрупко, нечитаемо, открыто для инъекций.
Лекарство: шаблонная система.
Анти-паттерн 3: один и тот же промпт для слишком многого.
Единый «универсальный ассистент»-промпт, который используется для черновика письма, code review, поддержки клиентов и ресёрча. Это разные задачи; один промпт ни одну из них не оптимизирует.
Лекарство: фичеспецифичные разработческие промпты поверх общего системного.
Анти-паттерн 4: захардкоженные промпты.
response = openai.chat.completions.create(
messages=[
{"role": "system", "content": "You are a helpful assistant..."},
{"role": "user", "content": query}
]
)Промпт зарыт в коде. Нельзя отредактировать без деплоя. Нельзя A/B-тестировать. Нельзя версионировать отдельно.
Лекарство: вынести в файл промпта или сервис.
Анти-паттерн 5: нет оценочного покрытия.
Фича выкатывается с промптом, который никогда систематически не тестировали. Качество — «по ощущениям». Дрейф недетектируем.
Лекарство: у каждого промпта есть набор оценок.
Анти-паттерн 6: данные подмешаны в системный промпт.
You are an assistant for John, a premium customer who joined in 2023, lives in Tallinn, and has 47 open tickets.Теперь системный промпт меняется на каждый вызов. Кэширование ломается. Путаница повсюду.
Лекарство: динамические данные идут в пользовательский/контекстный слой, а не в системный промпт.
Анти-паттерн 7: инструкции, закопанные посередине.
Help the user with their request. Be polite. Format output as JSON. Don't use markdown. The user is asking about pricing, so be careful about quoting numbers. Output should be 1-2 sentences. Now help them.Важные инструкции теряются. Модель может их пропустить.
Лекарство: структурируйте чёткими секциями, помещайте критичные инструкции в начале и в конце (эффект свежести помогает).
Специфичные паттерны для типичных фич
Несколько фичеспецифичных паттернов:
Классификация
Task: classify the following text into one of these categories:
- billing: payment, refund, subscription
- technical: bug, error, integration issue
- account: login, password, profile changes
- feature_request: new functionality requests
- complaint: general dissatisfaction without specific actionable issue
Output a JSON object: {"category": "<one of above>", "confidence": "<high|medium|low>", "reasoning": "<1 sentence>"}
Text to classify:
{text}Паттерны: перечисленные категории с определениями, структурированный выход, поля confidence и reasoning.
Извлечение
Task: extract structured data from the document below.
Schema:
- vendor_name: company that issued the invoice
- invoice_number: as printed on the document
- date: ISO 8601 format
- line_items: array of {description, quantity, unit_price, total}
- subtotal, tax, total: numbers
Rules:
- If a field is not present, use null
- Numbers should be numeric, not strings
- For ambiguous cases, set "needs_review": true and explain
Document:
{document}Паттерны: явная схема, ожидания по типам, обработка пропущенных данных, эскалация при неоднозначности.
Генерация со стилем
Task: write a {format} on the topic of {topic}, targeting {audience}.
Style:
- {Specific style trait 1}
- {Specific style trait 2}
- Avoid: {anti-pattern 1}, {anti-pattern 2}
Constraints:
- Length: {N} words
- Include: {required elements}
- Exclude: {forbidden elements}
Voice reference:
[Provide a sample of the desired voice]
Output: the {format} only, with no preamble or post-script.Паттерны: конкретные стилевые черты (не общие), явные ограничения, голос, закреплённый образцом.
Агентный цикл
You have access to the following tools:
{tool_descriptions}
For each turn:
1. Think about what you need to do.
2. Decide if you need a tool. If yes, call it.
3. After observing the result, decide if you need more tools or can answer.
4. When you have enough information, produce the final answer.
Constraints:
- Maximum 5 tool calls per request.
- If after 5 calls you can't complete, explain what's missing.
- Never invent tool names or arguments.
- Verify tool results before acting on them.
User request:
{user_query}Паттерны: явные шаги рассуждения, бюджет инструментов, защита от галлюцинаций, рефлексия.
Командный аспект
Промпты в продакшене обычно затрагивают нескольких людей:
- Инженеры вшивают промпты в систему, поддерживают шаблоны, управляют деплоями.
- Продакт определяет, чего промпты должны добиваться.
- Контент/маркетинг владеет руководствами по голосу и стилю.
- Доменные эксперты знают, что правильно для конкретных сценариев (юридические формулировки, медицинские термины и т. д.).
Полезный паттерн: процесс «ревью промптов», аналогичный code review, с правильными ревьюерами под каждый домен. Изменения голоса — на ревью у контента. Изменения логики — у инженерии. Доменный контент — у эксперта.
Для чувствительных сценариев (юридические, медицинские, финансовые) промпты могут требовать формального ревью и подписи. Стройте процесс соответственно.
90-дневный план зрелости промптов
Для команд, переходящих от «промпты — это строки в коде» к «промпты — управляемая инфраструктура»:
Дни 1-30: фундамент.
- Вынесите все промпты в отдельные файлы в системе контроля версий.
- Установите трёхслойный паттерн (system / developer / user).
- Постройте простой слой шаблонов.
- Настройте базовое логирование промптов и ответов.
Дни 31-60: оценка.
- Постройте наборы оценок для топ-5 промптов.
- Гоняйте оценки на изменениях промптов (поначалу вручную).
- Настройте CI-интеграцию для оценок (автозапуск на PR).
Дни 61-90: операции.
- Внедрите версионирование промптов (база или сервис).
- Добавьте возможность A/B-тестирования хотя бы для одного критичного промпта.
- Постройте дашборды качества продакшен-промптов.
- Установите процесс ревью для изменений промптов.
После 90 дней промпты — управляемая инфраструктура. Изменения осознанные, тестируемые, ревьюируемые, откатываемые. Качество измеримо. Дрейф детектируем.
Главное
Продакшен-промпты — не строки. Это многослойная система с дисциплиной вокруг версионирования, шаблонизации, оценки и наблюдаемости.
Трёхслойная архитектура (system / developer / user) разделяет заботы и держит промпты поддерживаемыми. Шаблонизация устраняет хрупкость. Система контроля версий или prompt-сервис дают историю версий. Оценки охраняют изменения. Наблюдаемость ловит то, что оценки пропускают.
В серьёзной продакшен-работе это не опционально. Команды, пропускающие эти шаги, скатываются в хаос промптов — строки разбросаны по коду, никто не знает, какая версия в продакшене, качество не измеряется, поведение меняется без объяснений.
Команды, инвестирующие в инфраструктуру промптов, получают поведение ИИ, которым можно управлять, которое можно мерить и улучшать. Это разница между фичей, которая стареет хорошо, и фичей, превращающейся в технический долг.
Начните с архитектуры. Всё остальное даётся проще.