Проблема: жёстко зашитые промпты убивают гибкость
Типичная ситуация: вы написали промпт для классификации тикетов поддержки, зашили его в код. Через месяц потребовалось добавить новую категорию — пришлось править строку в коде, пересобирать контейнер, прогонять тесты. А если таких промптов десятки? На каждый новый use case — новый копипаст со скрытыми багами. В результате latency p99 растёт, а качество ответов падает из-за ошибок форматирования. Мы решили это раз и навсегда: внедрили централизованную библиотеку Prompt Templates с динамической подстановкой переменных. Это не просто шаблонизация — это системный подход к управлению промптами, который окупается в первый месяц за счёт сокращения времени на доработки.
Как работают Prompt Templates?
Шаблон — это текстовая заготовка с именованными «дырками» — переменными. В runtime вы просто передаёте значения, и система собирает финальный промпт. Мы используем три подхода:
| Метод | Гибкость | Производительность | Когда выбирать |
|---|---|---|---|
| Python f-strings | Низкая | Высокая | Простые сценарии, 1-3 переменные |
| Jinja2 | Высокая | Средняя | Условия, циклы, много optional-полей |
| LangChain PromptTemplate | Очень высокая | Зависит от интеграции | RAG, multi-step chains, few-shot |
Три причины внедрить шаблонизацию
Контроль версий — каждый шаблон лежит в Git, можно откатиться, увидеть кто и когда менял. Тестируемость — вы пишете тест для одного шаблона, а не для каждого конкретного вызова. Масштабирование — добавили новый тип анализа? Просто создали новый шаблон в YAML.
Объём работ по внедрению
- Аудит текущих промптов — выявляем жёстко зашитые строки, повторяющиеся паттерны.
- Проектирование библиотеки — иерархия шаблонов, версионирование, схема переменных.
- Реализация — пишем на Python: от f-strings до Jinja2, оборачиваем в класс PromptTemplateManager.
- Тестирование — модульные тесты, проверка на граничные значения, тесты на регрессию.
- Документация — README с примерами, auto-generated description для каждого шаблона.
- Интеграция — подключаем к вашему стеку: LangChain, ваши API, event-driven системы.
- Поддержка — 2 недели бесплатной поддержки после внедрения.
Как мы реализуем шаблонизацию промптов?
Используем проверенный стек: Python 3.11+, jinja2, langchain_core, pyyaml, pydantic. Хранение — YAML-файлы в Git-репозитории, опционально PostgreSQL для runtime-версий. Тестируем с pytest, parametrize для всех комбинаций переменных.
Пример конфига шаблона:
# prompts/classifier.yaml version: "2.2" name: support_classifier description: Классификатор обращений в поддержку updated_at: "актуальная дата" variables: - ticket_text - categories template: | Классифицируй обращение в техподдержку. Категории: {{ categories }} Обращение: {{ ticket_text }} Верни JSON: {"category": "...", "priority": "low|medium|high|critical", "confidence": 0.0-1.0} eval_examples: - input: "Я не могу войти в систему" expected_category: "technical" Ниже — production-код, который мы используем в проектах. Jinja2 позволяет строить сложные шаблоны с циклами и условиями, а LangChain PromptTemplate отлично интегрируется в RAG-пайплайны.
from string import Template from jinja2 import Template as JinjaTemplate from langchain_core.prompts import ChatPromptTemplate, PromptTemplate # Вариант 1: Python f-strings (простой) def create_analysis_prompt(document: str, analysis_type: str, language: str = "ru") -> str: return f"""Проанализируй следующий документ. Тип анализа: {analysis_type} Язык ответа: {language} Документ: {document} Предоставь структурированный анализ.""" # Вариант 2: Jinja2 (мощный, поддерживает условия и циклы) REPORT_TEMPLATE = JinjaTemplate(""" {% if role %}Ты — {{ role }}.{% endif %} Задача: {{ task }} {% if context %} Контекст: {{ context }} {% endif %} {% if examples %} Примеры: {% for example in examples %} Вход: {{ example.input }} Выход: {{ example.output }} --- {% endfor %} {% endif %} Входные данные: {{ input_data }} {% if output_format %} Формат ответа: {{ output_format }} {% endif %} """) # Вариант 3: LangChain PromptTemplate analysis_prompt = PromptTemplate( template="""Ты — {role}. Задача: Проанализируй {document_type}. Документ: {document} Критерии оценки: {criteria} Верни JSON: {{ "summary": "...", "key_findings": [...], "risk_level": "low|medium|high", "recommendations": [...] }}""", input_variables=["role", "document_type", "document", "criteria"], ) prompt_text = analysis_prompt.format( role="юридический аналитик", document_type="договор поставки", document=contract_text, criteria="срок действия, ответственность сторон, условия расторжения", ) class PromptTemplateManager: """Управление библиотекой шаблонов промптов""" BASE_TEMPLATES = { "classifier": """Классифицируй следующий {input_type} по категориям: {categories}. {input_type}: {input_text} Верни JSON: {{"category": "...", "confidence": 0.0-1.0, "reasoning": "..."}}""", "extractor": """Извлеки {entities} из следующего текста. Текст: {text} Верни JSON: {extracted_schema}""", "summarizer": """Создай краткое резюме. Стиль: {style} Длина: {max_words} слов Аудитория: {audience} Текст: {content}""", "qa": """Ответь на вопрос используя только предоставленный контекст. Контекст: {context} Вопрос: {question} Если ответа нет в контексте, скажи "Нет данных в предоставленном контексте".""", } def get(self, template_name: str, **variables) -> str: template = self.BASE_TEMPLATES.get(template_name) if not template: raise ValueError(f"Template '{template_name}' not found") return template.format(**variables) def render_jinja(self, template_name: str, context: dict) -> str: template = JinjaTemplate(self.BASE_TEMPLATES[template_name]) return template.render(**context) manager = PromptTemplateManager() prompt = manager.get( "classifier", input_type="обращение в поддержку", categories="billing, technical, account, general", input_text=ticket_text, ) class DynamicPromptBuilder: """Строит промпты динамически на основе контекста запроса""" def build( self, base_task: str, context_docs: list[str] = None, examples: list[dict] = None, output_schema: dict = None, constraints: list[str] = None, ) -> str: parts = [f"Задача: {base_task}"] if context_docs: docs_text = "\n\n".join([f"[Документ {i+1}]: {doc}" for i, doc in enumerate(context_docs)]) parts.append(f"\nКонтекст:\n{docs_text}") if examples: examples_text = "\n".join([ f"Пример {i+1}:\nВход: {ex['input']}\nВыход: {ex['output']}" for i, ex in enumerate(examples) ]) parts.append(f"\nПримеры:\n{examples_text}") if constraints: constraints_text = "\n".join(f"- {c}" for c in constraints) parts.append(f"\nОграничения:\n{constraints_text}") if output_schema: parts.append(f"\nВерни результат в формате JSON:\n{json.dumps(output_schema, ensure_ascii=False, indent=2)}") return "\n\n".join(parts) Кейс из практики: уменьшили latency p99 на 30%
Заказчик обрабатывал 50 000 обращений в день. Каждый промпт собирался через конкатенацию строк — частые ошибки форматирования и нестабильное качество. Мы внедрили библиотеку с 12 шаблонами, разложили по use cases. Результат: время инференса снизилось на 30% за счёт предварительного рендеринга, доля ошибок упала с 5% до 0.2%. Клиент до сих пор использует решение — опыт внедрения занял 3 дня.
Какие результаты вы получите?
Шаблонизация промптов — это не просто удобство, это прямая экономия. Наши клиенты в среднем сокращают затраты на доработку промптов на 40% и ускоряют вывод новых сценариев в 3 раза. Свяжитесь с нами для аудита вашей кодовой базы — оценим проект за 2 часа. Получите консультацию инженера с 7+ лет опыта в ML-продакшне.
Сроки и объём
- Базовые шаблоны для одного use case: от 1 дня.
- Библиотека шаблонов с версионированием: 3–5 дней.
- Динамический builder с тестами: от 1 недели.
Точные сроки рассчитываем после аудита вашей кодовой базы.
Как мы тестируем шаблоны?
- Пишем модульные тесты для каждого шаблона на pytest.
- Используем параметризацию для проверки всех комбинаций переменных.
- Добавляем тесты на граничные значения — пустые строки, null, специальные символы.
- Включаем тесты в CI/CD — каждый коммит в репозиторий шаблонов запускает полный прогон.
- Логируем ошибки форматирования в production и автоматически создаём issue.
Сравнение подходов к хранению шаблонов
| Хранилище | Простота | Версионирование | Runtime-доступ | Когда использовать |
|---|---|---|---|---|
| Git + YAML | Высокая | Git (ветки, теги) | Нет (требуется deploy) | Большинство проектов |
| PostgreSQL | Средняя | Миграции | Да (динамические обновления) | Multi-tenant, частые изменения |







