Как вы проектируете промпты, которые работают с разными моделями?

Краткий тезис

Ключевая идея — переносимость промптов между моделями достигается за счёт отказа от специфичных для одной модели токенов и инструкций, использования универсальных шаблонов (chat templates) и фреймворков (LangChain), а также тестирования на «минимальной» модели (например, LLaMA-3-8B) перед запуском на более мощных (GPT-4, Claude). Простые, чёткие промпты без излишних деталей работают везде; сложные конструкции и платформенные фичи (tool use, system prompt в нестандартном формате) ломают совместимость.

1. Проблема: модели различаются, промпты — общие

Каждая модель имеет свои ожидания от формата диалога. Например:

МодельОжидаемый формат сообщений
GPT-4 (OpenAI){"role": "system", "content": "..."}, {"role": "user", "content": "..."}
LLaMA-3 (Meta)`<
Claude (Anthropic)\n\nHuman: ...\n\nAssistant: ...
Mistral[INST] ... [/INST] или <s>[INST] ... [/INST]

Если написать промпт в формате OpenAI и передать его в LLaMA-3, модель не поймёт, что это инструкция, и ответ будет низкого качества. Поэтому универсальный промпт не должен содержать токенов, свойственных одной модели.

2. Универсальный формат общения (Chat Template)

Лучшая практика — использовать chat template — шаблон, который фреймворк (LangChain, Transformers) автоматически преобразует в формат целевой модели. В LangChain промпт задаётся через ChatPromptTemplate:

from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "Ты полезный ассистент, отвечай кратко."),
    ("user", "{question}")
])

При вызове модели LangChain сам применит правильный шаблон для GPT-4, LLaMA или Claude. Это основной способ обеспечить переносимость.

3. Избегание специфичных токенов

Никогда не вставляйте в текст промпта токены вроде <|im_start|>, [INST], \n\nHuman:. Даже если они выглядят как обычный текст, модель может интерпретировать их как служебные. Вместо этого полагайтесь на фреймворк или вручную переводите между форматами через конфигурацию.

Исключение: когда вы точно знаете, что будете использовать только одну модель, но даже тогда лучше держать «чистый» промпт и добавлять токены на уровне кода инференса.

4. Принцип «Keep It Simple» (KISS)

Сложные промпты с многоуровневыми инструкциями, цепочками мыслей (Chain-of-Thought) и форматированным выводом (JSON, XML) работают на GPT-4, но могут «сломаться» на меньших моделях. Поэтому проектируйте так:

  • Минимальные инструкции: «Ответь на вопрос, используя предоставленный контекст. Если информации недостаточно, скажи "не знаю".»
  • Одна задача на сообщение: не смешивайте несколько подзадач в одном user-сообщении.
  • Чёткий разделитель: используйте маркдаун-заголовки (### Контекст, ### Вопрос), которые все модели хотя бы частично понимают.

5. Тестирование на минимальной модели

Всегда сначала тестируйте промпт на самой слабой целевой модели — например, LLaMA-3-8B, Mistral-7B или Phi-3-mini. Если промпт работает на 8B-модели, он почти гарантированно будет работать на GPT-4. Если же промпт требует сложного логирования формата вывода, ошибка проявится в первую очередь на маленькой модели.

Схема тестирования:

models_to_test = ["llama-3-8b", "gpt-4", "claude-sonnet"]
for model in models_to_test:
    response = llm.invoke(prompt.format(question="..."), model=model)
    print(f"{model}: {response}")

6. Использование фреймворков: LangChain, LiteLLM

LiteLLM позволяет вызывать 100+ моделей единым интерфейсом. Он сам преобразует промпты:

from litellm import completion

messages = [{"role": "user", "content": "Расскажи про квантовые вычисления."}]
response = completion(model="claude-3-haiku", messages=messages)

LangChain добавляет PromptTemplate с переменными, что даёт возможность подставлять контекст, few-shot примеры и системные инструкции, не заботясь о формате.

7. Few-shot примеры в общем виде

Примеры (few-shot) должны быть даны в том же формате, что и основные сообщения. В LangChain это делается через FewShotChatMessagePromptTemplate:

from langchain.prompts import FewShotChatMessagePromptTemplate

examples = [
    {"input": "Как погода?", "output": "Солнечно, +25°C."},
    {"input": "Что такое ML?", "output": "Машинное обучение."}
]
example_prompt = ChatPromptTemplate.from_messages([
    ("human", "{input}"),
    ("ai", "{output}")
])
few_shot = FewShotChatMessagePromptTemplate(
    examples=examples,
    example_prompt=example_prompt
)

Этот шаблон автоматически подставит примеры в правильной ролевой структуре для любой модели.

8. Управление длиной контекста (context window)

Разные модели имеют разный размер окна контекста: GPT-4-turbo — 128К, LLaMA-3-8B — 8K, Mistral-7B — 8K. Универсальный промпт должен умещаться в наименьшее окно среди целевых моделей. Если вам нужно передать большой контекст (например, 50 страниц текста), придётся либо использовать модель с большим окном, либо делать RAG. Для переносимого промпта всегда ограничивайте себя 4K токенами, если не уверены.

9. Системный промпт: да, но с осторожностью

Не все модели поддерживают отдельного системного сообщения (например, старые версии LLaMA-2 игнорировали его). Универсальный подход — встраивать инструкции в первое user-сообщение:

Инструкция: ты помощник, отвечай кратко.

Теперь вопрос: {question}

LangChain при использовании ("system", ...) автоматически добавит системный промпт, если модель его поддерживает, иначе объединит с user. Доверяйте фреймворку.

10. Обработка ошибок и fallback

Даже при тщательном проектировании какая-то модель может не понять промпт. Создайте fallback промпт — упрощённую версию без системной инструкции и few-shot, только с вопросом. Если получен пустой или бессмысленный ответ, повторите запрос с fallback.

Пример:

def safe_invoke(prompt_obj, question, model):
    try:
        return llm.invoke(prompt_obj.format(question=question), model=model)
    except Exception:
        fallback = ChatPromptTemplate.from_messages([("user", "{question}")])
        return llm.invoke(fallback.format(question=question), model=model)

11. Автоматизированное тестирование переносимости

Создайте набор тестовых кейсов с известными ожидаемыми ответами (golden answers). Напишите скрипт, который прогоняет каждый кейс через все целевые модели и считает метрики (accuracy, BLEU, ROUGE). Если для какой-то модели метрика падает ниже порога — промпт не переносим.

12. Документирование и версионирование промпта

Ведите версии промпта (Git, DVC), указывая, для каких моделей они протестированы. При обновлении прогоняйте все тесты. Создайте конфигурационный файл:

prompt_version: 2.3
tested_models:
  - gpt-4-turbo
  - gpt-3.5-turbo
  - llama-3-8b
  - claude-3-sonnet
universal: true

Это позволяет быстро понять, что промпт сломался на новой версии модели.

Пет-проект для закрепления

Задача Разработать универсальный промпт для классификации тональности отзывов (положительная/отрицательная), который работает на GPT-4, LLaMA-3-8B и Claude-3.

Инструменты Python, LangChain, LiteLLM, набор отзывов (например, из datasets).

Шаги:

  1. Создайте ChatPromptTemplate с системной инструкцией и одним user-сообщением.
  2. Напишите функцию classify(text, model).
  3. Протестируйте на 50 отзывах на каждой модели, соберите accuracy.
  4. Если точность на LLaMA ниже 90% — упростите промпт (уберите форматирование JSON, сократите инструкцию).
  5. Добавьте fallback (если LLaMA возвращает не positive/negative — перезапустите с тривиальным промптом).
  6. Сохраните результаты в таблицу.

Ожидаемый результат Вы получите один и тот же промпт, который показывает accuracy >85% на всех трёх моделях. Вы научитесь итеративно упрощать промпт, сохраняя качество.

Связь с другими вопросами

ВопросТема
93Как вы оцениваете качество промптов?
95Как вы адаптируете промпты под разные задачи?
96Как вы используете chain-of-thought в промптах?
97Как вы обрабатываете многоязычность в промптах?
98Как вы защищаете промпты от инъекций?
99Как вы управляете версиями промптов?

Навигация