Проблема: жёстко зашитые промпты убивают гибкость
Типичная ситуация: вы написали промпт для классификации тикетов поддержки, зашили его в код. Через месяц потребовалось добавить новую категорию — пришлось править строку в коде, пересобирать контейнер, прогонять тесты. А если таких промптов десятки? На каждый новый 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, частые изменения |







