Files
BlackboxBook/book/06_prompt_is_a_protocol.md
2026-05-20 20:55:03 +03:00

46 KiB
Raw Blame History

ГЛАВА 6. ПРОМПТ — ЭТО ПРОТОКОЛ, А НЕ ПРОСЬБА


6.1. Почему «сделай хорошо» не работает

Представьте, что вы приходите в ресторан и говорите официанту: «Принесите что-нибудь вкусное». Может быть, вам повезёт. А может — вам подадут суши, хотя у вас аллергия на рыбу. Теперь представьте другой вариант: «Стейк medium rare, без гарнира, с соусом отдельно». Здесь официант не гадает — он выполняет спецификацию.

Промпт работает точно так же. Он может быть расплывчатой просьбой — или точным протоколом. Разница между ними — это разница между «авось повезёт» и «стабильный результат».

Самый простой пример

Для начала — минимальная иллюстрация:

✗ Плохой промпт:

Напиши текст про собак.

✓ Хороший промпт:

Напиши информационный абзац (5080 слов) о породе золотистый ретривер 
для детской энциклопедии. Стиль: простой, дружелюбный. 
Упомяни: происхождение, характер, для кого подходит.

Почему второй промпт лучше? Потому что в нём закрыто пять пробелов, которые в первом промпте модель заполняла бы случайно:

  • Жанр — информационный абзац (не эссе, не стихотворение)
  • Объём — 5080 слов (не страница и не два предложения)
  • Тема — конкретная порода (не «собаки вообще»)
  • Аудитория — дети (определяет стиль)
  • Содержание — три конкретных аспекта

Каждый незакрытый пробел — это развилка, где модель бросает монетку. Чем больше развилок, тем менее предсказуем результат.

Пример посложнее

Теперь рассмотрим два промпта для реальной разработки:

Промпт A:

Напиши код для обработки данных.

Промпт B:

<role>Senior Python engineer, data pipeline specialist</role>
<task>
  Write a function that:
  - Reads CSV from S3 (boto3)
  - Validates schema against Pydantic model
  - Transforms: rename columns per mapping, convert dates to ISO 8601
  - Returns list[ProcessedRecord]
</task>
<constraints>
  - Python 3.12+, type hints required
  - No pandas (use csv module + dataclasses)
  - Handle: FileNotFoundError, ValidationError, malformed rows (skip + log)
  - Max 80 lines
</constraints>
<output_format>
  Single Python file, no comments except docstring
</output_format>

Промпт A генерирует неопределённый ответ, потому что модель должна заполнить пробелы вероятностно: какой язык? какие данные? какой формат? какие ограничения? Каждый пробел — развилка, и модель выбирает наиболее вероятное продолжение, а не наиболее полезное для вас.

Промпт B минимизирует неопределённость. Каждый пробел закрыт явно. Модель не угадывает — она выполняет спецификацию.

Три аналогии помогут запомнить эту идею:

  • Промпт как рецепт. В рецепте указаны ингредиенты, пропорции, температура, время. Уберите любой элемент — и результат становится непредсказуемым. «Сделай торт» — это не рецепт. «Бисквит из 4 яиц, 200 г муки, 180°C, 25 минут» — рецепт.
  • Промпт как API-спецификация. Разработчик не пишет эндпоинт с описанием «делает что-то полезное». Он определяет входные параметры, типы, формат ответа, коды ошибок. Промпт — это ваш API-контракт с моделью.
  • Промпт как юридический договор. В договоре двусмысленность ведёт к спорам. Чем точнее формулировки, тем меньше пространства для разночтений. «Разумные сроки» — повод для суда. «30 календарных дней» — нет.

Формализация: информационная энтропия промпта

Промпт с высокой энтропией (много неопределённости) → широкое распределение возможных ответов → непредсказуемый результат.

Промпт с низкой энтропией (всё специфицировано) → узкое распределение → предсказуемый, стабильный результат.

Цель инженера: минимизировать энтропию промпта, оставляя модели свободу только там, где это необходимо (например, в формулировках, не в структуре).


6.2. Прайминг: сначала рамка мышления, потом контекст, потом задача

Из каузальной механики (Глава 4) следует оптимальный порядок элементов промпта. Это не «рекомендация» — это следствие того, как attention accumulation формирует скрытые состояния.

Каузальный порядок промпта

[1. Роль/Режим]     → Задаёт «кто модель» → активирует нужные MLP-ассоциации
[2. Цель/Критерии]  → Задаёт «что делать» → фокусирует attention на релевантных паттернах
[3. Ограничения]    → Задаёт «чего нельзя» → подавляет нежелательные траектории
[4. Контекст/Данные] → Предоставляет материал → входные данные для обработки
[5. Формат вывода]   → Задаёт структуру → constrained decoding через ожидания
[6. Примеры (опц.)]  → Few-shot learning → калибровка формата и стиля
[7. Триггер]         → Запускает генерацию → «Начни.» / «Output:»

Почему этот порядок работает

Роль первой: Ты — старший инженер PostgreSQL с 15 годами опыта активирует MLP-ассоциации, связанные с экспертизой PostgreSQL. Все последующие токены интерпретируются через эту призму.

Цель до данных: модель «знает», зачем она получает данные, ещё до их получения. Это позволяет attention фокусироваться на релевантных частях данных.

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

Триггер последним: явный сигнал к началу генерации. Без триггера модель может начать генерировать мета-текст (Конечно, я помогу вам с...) вместо полезного вывода.


6.3. Промпт как контракт: спецификация, а не просьба

Вернёмся к аналогиям из начала главы и разберём каждую подробнее.

API-аналогия

Если вы разработчик, подумайте о промпте как о спецификации API-эндпоинта. Никто не пишет в документации: «POST /api/data — делает что-то с данными». Вместо этого — чёткая схема запроса, схема ответа, перечень ошибок. Промпт заслуживает такого же подхода:

Элемент API Элемент промпта Пример
Endpoint Задача Сгенерировать SQL-запрос
Request schema Входные данные + формат {table: string, filters: Filter[], limit: int}
Response schema Формат вывода {query: string, estimated_rows: int}
Validation rules Ограничения Только SELECT, без подзапросов, PostgreSQL 16
Error handling Fallback Если невозможно — верни {error: string}
Auth/Permissions Роль DBA с read-only доступом

Аналогия с договором

Юристы знают: в договоре каждое слово имеет значение. Фраза «в разумные сроки» — повод для годового судебного спора. Фраза «в течение 30 календарных дней с момента подписания» — нет.

В промпте работает тот же принцип. «Ответь кратко» — неоднозначно (кратко — это одно предложение? один абзац? страница?). «Ответь одним абзацем из 35 предложений» — однозначно.

Три свойства хорошего промпта-контракта

1. Детерминированность: одинаковый вход → одинаковый формат выхода.

Не обязательно одинаковый текст (при temp > 0), но обязательно одинаковая структура. Если вы просите JSON — всегда получаете валидный JSON. Если просите 3 варианта — всегда 3, не 2 и не 5.

2. Верифицируемость: выход можно программно проверить.

Опишите Pydantic-модель с полями query: str и estimated_rows: int, получите ответ модели через structured outputs и валидируйте его через model_validate_json. Если запрос не начинается с SELECT — это сигнал ошибки. Для генерации такого валидатора используйте промпт:

Промпт для ИИ: «Напиши на Python (Pydantic v2) валидатор ответа LLM: модель SQLResponse с полями query и estimated_rows, функцию validate_llm_response(raw_json: str) -> SQLResponse, которая валидирует JSON и проверяет, что query начинается с SELECT. Покажи пример использования.»

3. Модульность: части промпта можно заменять без переписывания целого.

Роль, задача, ограничения и формат вывода хранятся как отдельные переменные или конфигурационные блоки. Функция build_prompt() собирает их в финальный промпт. Изменение одного блока (например, формата вывода) не требует переписывания остальных.


6.4. Structured Outputs и Function Calling

Что такое Structured Outputs и зачем они нужны

Если вы новичок, начнём с основ. Structured output — это ответ модели не свободным текстом, а в заранее определённом машиночитаемом формате (обычно JSON). Зачем? Потому что ваш код не может надёжно работать со свободным текстом — ему нужны предсказуемые поля с предсказуемыми типами.

JSON Schema — это способ описать «форму» JSON-ответа: какие поля должны быть, каких типов, обязательные или нет. Думайте об этом как о формочке для печенья — тесто (ответ модели) всегда примет нужную форму.

Пример JSON Schema:

{
  "type": "object",
  "properties": {
    "name": {"type": "string"},
    "age": {"type": "integer"},
    "city": {"type": "string"}
  },
  "required": ["name", "age"]
}

Эта схема говорит: «ответ — объект, в нём обязательно name (строка) и age (число), плюс необязательный city».

Проблема свободного текста

Когда модель генерирует свободный текст, парсинг ненадёжен:

Модель: "Вот запрос: SELECT * FROM users WHERE age > 25 ORDER BY name.
Примерное количество строк: около 150."

Как извлечь запрос? Регулярные выражения? А если модель решит добавить пояснение перед запросом? Или после? Нестабильно, хрупко, не масштабируется.

JSON Schema Validation: стандарт индустрии

К апрелю 2026 года все фронтир-провайдеры поддерживают native structured outputs на основе JSON Schema. Это больше не экспериментальная функция — это стандарт:

Провайдер API-параметр Статус (2026)
OpenAI response_format: {type: "json_schema", ...} GA, во всех моделях
Anthropic tool_use с JSON Schema (forced tool choice для structured output) GA, все модели Claude 4.x
Google response_schema в GenerationConfig GA, Gemini 2.0+
Mistral response_format: {type: "json_schema", ...} GA, все модели

Промпт для ИИ: «Покажи вызов OpenAI Chat Completions API (Python SDK) с параметром response_format типа json_schema для модели GPT-5.4. Схема: объект с полями query (string) и estimated_rows (integer), оба обязательные. Используй актуальный формат SDK.»

Модель гарантированно вернёт валидный JSON, соответствующий схеме. Это не «пожелание», а constraint на уровне декодирования.

Реальный пайплайн: от текста к базе данных

Рассмотрим полный пайплайн, где structured outputs встраиваются в продакшн-пайплайн. Задача: извлечь из отзывов клиентов структурированные данные и сохранить в БД.

Подход: определяете Pydantic-модель (ReviewExtraction с полями product_name, sentiment, issues, rating_mentioned, summary), передаёте её JSON-схему в response_format, вызываете модель для каждого отзыва и записываете результат в БД. Между моделью и базой данных нет хрупкого парсинга регулярками — Pydantic-схема одновременно описывает контракт для модели и валидирует результат.

Промпт для ИИ: «Напиши на Python (OpenAI SDK + Pydantic v2) пайплайн извлечения данных из отзывов клиентов. Pydantic-модель ReviewExtraction с полями: product_name (str), sentiment (enum: positive/negative/neutral), issues (list[str]), rating_mentioned (int | None, 15), summary (str). Используй response_format с JSON Schema из model_json_schema(). Покажи обработку потока отзывов с записью в БД.»

Grammar-Guided Decoding

Для open-source моделей существуют библиотеки, которые ограничивают декодирование на уровне грамматики:

  • Outlines (Python) — принимает JSON Schema и гарантирует, что модель сгенерирует валидный JSON, соответствующий схеме.
  • Guidance (Microsoft) — позволяет задать регулярные выражения и выбор из вариантов прямо в шаблоне генерации.

Промпт для ИИ: «Покажи пример использования библиотеки Outlines для генерации структурированного JSON из Llama 3 (Hugging Face Transformers). Схема: объект с полями query (string) и estimated_rows (integer, minimum 0). Используй outlines.generate.json

Function Calling (вызов функций)

Если structured outputs — это «модель отвечает в нужном формате», то function calling (tool use) — это следующий шаг: «модель сама решает, какое действие выполнить».

Представьте, что вы дали модели список кнопок: «проверить погоду», «найти в базе данных», «отправить email». Модель читает запрос пользователя и «нажимает» нужную кнопку с нужными параметрами. Но нажатие — виртуальное: модель генерирует JSON с именем функции и аргументами, а ваш код выполняет реальное действие.

К 2026 году function calling (tool use) — стандартная возможность всех фронтир-моделей: GPT-5.4, Claude Opus 4.6 / Sonnet 4.6, Gemini 3.1 Pro, Mistral Large, Command R+. Вы описываете инструменты как JSON-объекты с именем, описанием и параметрами — модель решает, когда и с какими аргументами вызвать функцию, а вы контролируете, что она делает.

Подробное руководство по проектированию инструментов, бест-практисы описания параметров и интеграция с agent loop — в Главе 11 (§11.2, §11.5).

MCP: стандартизация описания инструментов

Model Context Protocol (MCP) — открытый стандарт, который унифицирует способ описания инструментов, контекстов и действий для LLM. Если function calling — это «дать модели кнопки», то MCP — это «единый стандарт для производства кнопок»: описываете инструмент один раз в формате MCP — и он работает с любой моделью. Подробно о протоколе, его архитектуре и практическом применении — в Главе 11, §11.4.


6.5. Паттерны промптов для типовых задач

Паттерн 1: Extraction (извлечение данных)

<role>Data extraction specialist</role>
<task>
  Extract structured information from the following text.
  Return ONLY the JSON, no explanation.
</task>
<schema>
{
  "company_name": "string",
  "founded_year": "integer | null",
  "headquarters": "string | null",
  "revenue_usd": "number | null",
  "employees": "integer | null"
}
</schema>
<rules>
  - If information is not found, use null
  - Do not infer or guess missing values
  - Dates: extract year only
  - Revenue: convert to USD if possible, else null
</rules>
<text>
  {input_text}
</text>

Паттерн 2: Generation (генерация кода)

<role>Senior {language} engineer</role>
<task>
  Implement function with the following contract:
</task>
<contract>
  Name: {function_name}
  Input: {input_types}
  Output: {output_type}
  Behavior: {description}
  Edge cases: {edge_cases}
</contract>
<constraints>
  - {language} {version}+
  - Type hints required
  - No external dependencies beyond {allowed_libs}
  - Max {N} lines
  - Handle errors: {error_types}
</constraints>
<examples>
  Input: {example_input} → Output: {example_output}
</examples>
<output>
  Single code block, no explanation.
</output>

Паттерн 3: Analysis (аналитика)

<role>Senior data analyst</role>
<task>
  Analyze the following dataset and answer the question.
</task>
<dataset>
  {data in CSV/JSON format}
</dataset>
<question>
  {specific_question}
</question>
<constraints>
  - Base conclusions only on provided data
  - If data is insufficient, state explicitly
  - Include confidence level: high/medium/low
  - Cite specific data points
</constraints>
<format>
{
  "answer": "string",
  "confidence": "high|medium|low",
  "supporting_data": ["string"],
  "caveats": ["string"]
}
</format>

6.6. Prompt Caching и оптимизация стоимости

Системные промпты в продакшн-приложениях часто содержат тысячи токенов: роль, инструкции, примеры, правила. Если вы отправляете 2000 токенов системного промпта с каждым из 10 000 запросов в день — вы платите за 20 миллионов входных токенов, из которых 99.99% повторяются.

Prompt caching решает эту проблему. И Anthropic, и OpenAI предлагают кэширование на уровне API:

  • Anthropic — явное кэширование через параметр cache_control: {type: "ephemeral"} в системном промпте. До 90% скидки на кэшированные токены.
  • OpenAI — автоматическое кэширование повторяющихся префиксов промптов (1024+ токенов). Скидка 50% на cached input tokens.

Практические правила кэширования

  1. Размещайте статичный контент в начале — кэш работает по принципу совпадения префикса. Динамические данные (запрос пользователя) — в конце.
  2. Минимальный порог зависит от провайдера и моделиу OpenAI это 1024+ токенов для автоматического prompt caching, у Anthropic пороги и TTL зависят от модели и типа кэша.
  3. Переиспользуйте системные промпты — один и тот же system prompt для всех запросов в рамках задачи.
  4. Группируйте few-shot примеры — вынесите их в кэшируемую часть, а не повторяйте в каждом запросе.

Prompt caching может заметно снижать стоимость и latency на повторяющихся префиксах, но конкретная экономия зависит от hit rate, TTL, длины общего префикса и модели. В книге полезно мыслить так: кэш — не «магическая скидка», а инженерный приём для стабильных повторяющихся блоков.


6.7. Промпт как код: компиляция промптов

Новое направление (20242026) — фреймворки, которые превращают промпты из текстовых шаблонов в компилируемые программы:

DSPy (Stanford NLP): описываете задачу декларативно через сигнатуры (typed вход/выход), фреймворк автоматически оптимизирует промпт, few-shot примеры и даже выбор модели на основе ваших метрик качества. Например, сигнатура "text: str -> facts: list[str]" автоматически превращается в оптимизированный промпт.

LMQL: язык запросов к языковым моделям с контролем типов и ограничениями — например, where SENTIMENT in ["positive", "negative", "neutral"] ограничивает генерацию до трёх вариантов.

Оба подхода объединяет идея: промпт — это не строка, а программа с типами, ограничениями и автоматической оптимизацией.

Примечание о reasoning-моделях. Модели с цепочкой рассуждений (o1, o3, Claude с extended thinking) могут обрабатывать менее структурированные промпты — они сами «достраивают» недостающую структуру в процессе рассуждения. Но даже для них протокольный подход даёт более стабильные результаты, особенно при программной обработке вывода.


6.8. Антипаттерны промптов

Антипаттерн 1: «Вежливая просьба»

✗ "Пожалуйста, не мог бы ты помочь мне написать функцию для сортировки? 
    Было бы замечательно, если бы она работала быстро. Спасибо!"

Проблемы: нет спецификации (какая сортировка? какие данные? какой язык?), вежливые обороты — это шум, который расходует токены и размывает attention.

Антипаттерн 2: «Многозадачный запрос без структуры»

✗ "Напиши код сортировки, объясни алгоритм, сравни с другими алгоритмами, 
    добавь тесты и документацию."

Проблемы: 5 задач в одном запросе. Модель попытается сделать всё и сделает всё посредственно. Attention распределяется по 5 целям.

Антипаттерн 3: «Инструкция после данных»

✗ [50K токенов данных]
   "Теперь из этих данных извлеки имена компаний в JSON."

Проблемы: модель обработала 50K токенов данных, не зная, что с ними делать. Attention распределился равномерно. Инструкция пришла последней → ей досталось минимум attention.

Антипаттерн 4: «Негативные инструкции без позитивных»

✗ "Не используй рекурсию. Не пиши комментарии. Не создавай классы. 
    Не используй глобальные переменные."

Проблемы: модель «знает» чего не делать, но не знает, что делать. Каждое «не» парадоксально активирует ассоциации с запрещённым — модель «думает» о рекурсии, пытаясь её избежать.

Исправление: дополняйте запреты позитивными альтернативами: Используй итеративный подход (не рекурсию).

Антипаттерн 5: «Копипаста из ChatGPT-гайдов»

✗ "Ты — полезный, безопасный и честный ассистент. Твоя цель — помочь 
    пользователю наилучшим образом. Думай шаг за шагом."

Проблемы: это generic-преамбула, которую модель уже «видела» в миллионах примеров. Она не добавляет информации, а тратит токены. «Думай шаг за шагом» — мощная техника, но только когда за ней следует конкретная задача с конкретным форматом вывода.

Исправление: убрать преамбулу, перейти сразу к роли и задаче.

Антипаттерн 6: «Примеры противоречат инструкциям»

✗ Инструкция: "Отвечай на русском языке."
   Пример: "Input: Hello → Output: This is a greeting."

Проблемы: модель обучена на примерах больше, чем на инструкциях (Глава 4, эффект прайминга). Если пример на английском — модель с высокой вероятностью продолжит на английском, проигнорировав инструкцию.

Исправление: примеры должны демонстрировать все инструкции, включая язык, формат, стиль.

Антипаттерн 7: «Температура 0 вместо структуры»

✗ "Я поставлю temperature=0, значит ответ будет стабильным."

Проблемы: temperature=0 делает генерацию детерминированной при прочих равных, но не гарантирует нужный формат. Модель стабильно вернёт один и тот же ответ — который может быть стабильно неправильным. Температура влияет на случайность, а не на качество.

Исправление: temperature=0 + structured outputs + чёткий промпт = стабильный и правильный результат.


6.9. Библиотека шаблонов

Готовые шаблоны для четырёх распространённых задач. Копируйте, адаптируйте под свои нужды.

Шаблон 1: Суммаризация документа

<role>Technical writer, expert in concise summarization</role>
<task>
  Summarize the following document.
</task>
<rules>
  - Output language: same as input
  - Length: 35 bullet points, each 12 sentences
  - Focus on: key decisions, action items, deadlines
  - Omit: pleasantries, background context, repetition
  - If the document contains no actionable content, state: "No action items found."
</rules>
<format>
{
  "title": "Brief document title",
  "bullets": ["string"],
  "action_items": ["string"],
  "next_deadline": "ISO date or null"
}
</format>
<document>
  {input_text}
</document>

Шаблон 2: Код-ревью

<role>Senior software engineer, code reviewer</role>
<task>
  Review the following code change (diff). Focus on correctness, 
  security, and maintainability. Do NOT suggest style changes.
</task>
<severity_levels>
  - CRITICAL: bugs, security vulnerabilities, data loss risks
  - WARNING: performance issues, error handling gaps
  - SUGGESTION: optional improvements
</severity_levels>
<format>
{
  "issues": [
    {
      "severity": "CRITICAL | WARNING | SUGGESTION",
      "line": "number or range",
      "description": "What's wrong",
      "fix": "Suggested fix (code snippet)"
    }
  ],
  "summary": "Overall assessment in 12 sentences",
  "approve": true/false
}
</format>
<diff>
  {code_diff}
</diff>

Шаблон 3: Классификация текста

<role>Text classification specialist</role>
<task>
  Classify the following message into one of the categories.
  If confidence is below 0.7, set category to "unknown".
</task>
<categories>
  - billing: payment, invoice, refund, charge
  - technical: bug, error, crash, not working
  - feature_request: wish, would be nice, please add
  - account: login, password, access, permissions
</categories>
<format>
{
  "category": "string",
  "confidence": 0.0-1.0,
  "reasoning": "One sentence explaining the classification"
}
</format>
<message>
  {input_message}
</message>

Шаблон 4: Генерация тест-кейсов

<role>QA engineer, test design specialist</role>
<task>
  Generate test cases for the following function signature.
  Cover: happy path, edge cases, error cases.
</task>
<function>
  Name: {function_name}
  Input: {input_types}
  Output: {output_type}
  Description: {description}
</function>
<constraints>
  - 58 test cases total
  - Use pytest format
  - Include parametrize where appropriate
  - Each test: clear name, arrange/act/assert structure
</constraints>
<output>
  Single Python file with test functions. No explanation.
</output>

6.10. Prompt A/B testing в production

Промпт, который работает на 10 ручных примерах, может провалиться на реальном трафике. А/B-тест промптов — это способ сравнить два варианта промпта на живых пользователях и принять решение на данных, а не на интуиции.

Когда A/B-тест оправдан

  • Вы меняете структуру промпта (новый формат, перестановка секций, другой режим reasoning).
  • Вы переходите на новую модель и хотите сравнить её со старой в связке с промптом.
  • Вы оптимизируете стоимость — более дешёвая модель с улучшенным промптом vs дорогая с базовым.

Не оправдан: косметические правки формулировок (меняете «пожалуйста» на «будьте добры» — идите в eval-набор, а не в A/B).

Метрики A/B-теста

Первичные метрики — те, на которые вы оптимизируете:

Метрика Что измеряет Как считать
Accuracy / Correctness Доля ответов без ошибок LLM-judge или ручная разметка
Task completion rate Доля запросов, где задача выполнена Автоматическая проверка результата
User satisfaction Thumbs up/down, CSAT Сбор через UI

Вторичные метрики — контролируете, чтобы не просели:

Метрика Что измеряет Алерт при
Parse rate Доля ответов с валидной структурой < 95%
Latency P95 Время ответа Рост > 20% vs baseline
Cost per request Средняя стоимость Рост > 30% vs baseline
Hallucination rate Доля фактических ошибок Рост > 2 п.п.

Дизайн эксперимента

  1. Рандомизация. Каждый запрос случайно направляется в вариант A (текущий промпт) или B (новый). Не чередуйте A/B/A/B — истинная рандомизация. Используйте hash(user_id) для sticky-экспериментов, если нужна консистентность для одного пользователя.

  2. Размер выборки. Минимум 200 запросов на вариант для обнаружения разницы в 10 п.п. с 80% мощностью. Для разницы в 5 п.п. — около 800 запросов на вариант. Используйте online-калькулятор размера выборки (ABBA, Evan Miller) для точного расчёта.

  3. Длительность. Минимум один полный бизнес-цикл (обычно неделя), чтобы захватить все паттерны использования. Не останавливайте тест, увидев разницу через 2 часа — это может быть шум.

  4. Статистическая значимость. Не доверяйте «на глаз». Используйте тест пропорций (z-test) для бинарных метрик (accuracy, parse rate) или Mann-Whitney U для непрерывных (latency, cost). Порог: p < 0.05. Если p > 0.05 после целевого размера выборки — разницы нет, выбирайте тот вариант, который дешевле или проще.

Чек-лист A/B-теста промпта

# Шаг Проверка
1 Определена primary metric Одна метрика для принятия решения
2 Определены guardrail metrics Минимум latency, cost, parse rate
3 Настроена рандомизация Hash-based или true random
4 Рассчитан размер выборки 200+ на вариант (10 п.п. разницы)
5 Задана длительность ≥ 1 бизнес-цикл
6 Инструментация собирает метрики Все метрики логируются
7 Настроен дашборд теста Видно разницу A vs B в реальном времени
8 Критерий остановки p < 0.05 ИЛИ достигнут размер выборки без значимой разницы

Антипаттерны

  • Peeking. Смотреть результаты каждый час и останавливать, как только p < 0.05 — это inflates false positive rate. Зафиксируйте размер выборки и длительность заранее.
  • Слишком много метрик. Если вы измеряете 10 метрик, одна из них покажет «значимую» разницу чисто случайно. Выберите одну primary metric.
  • A/B без eval-набора. Если оба варианта проваливаются на одних и тех же кейсах — проблема не в промпте, а в архитектуре. Сначала прогоните оба варианта через offline eval.

Практический вывод

Чек-лист проектирования промпта

# Правило Проверка
1 Проектируйте как API-контракт Есть ли schema для входа и выхода?
2 Используйте каузальный порядок Роль → Цель → Ограничения → Данные → Формат?
3 Специфицируйте формат явно JSON Schema, Pydantic model, пример вывода?
4 Одна задача = один промпт Нет ли нескольких несвязанных целей?
5 Тестируйте на edge-cases Что произойдёт, если данных нет? Если формат неверный?
6 Избегайте антипаттернов Нет вежливого шума? Позитивные инструкции? Примеры не противоречат?
7 Используйте structured outputs JSON Schema / function calling вместо свободного текста?
8 Кэшируйте повторяющиеся части Статичный system prompt в начале? Prompt caching включён?
9 Примеры соответствуют инструкциям Язык, формат и стиль примеров = инструкциям?
10 A/B-тест перед деплоем Прогнан ли тест на 200+ запросах?

Задания

Задание 1. Промпт-контракт для вашего проекта. Возьмите реальную задачу из текущего проекта (extraction, classification, генерация кода). Напишите промпт по каузальному порядку из §6.2: роль → цель → ограничения → данные → формат. Запустите 5 раз с temp=0.3. Измерьте: (a) parse rate (все ли ответы соответствуют формату?), (b) семантическую стабильность (насколько похожи ответы между собой). Ожидаемый результат: parse rate ≥ 95%, если промпт-контракт составлен корректно.

Задание 2. Structured outputs в продакшн-пайплайне. Реализуйте пайплайн, который принимает свободный текст (отзыв, баг-репорт, тикет), извлекает структурированные данные через response_format с JSON Schema и записывает в БД. Используйте Pydantic-модель как единый контракт для LLM и валидации. Проверьте: что происходит, если в тексте нет нужных данных? Если текст на другом языке?

Задание 3. Антипаттерн-аудит. Соберите 510 промптов из вашей кодовой базы. Для каждого проверьте по списку антипаттернов из §6.8: есть ли вежливый шум, негативные инструкции без позитивных, инструкции после данных, противоречащие примеры? Исправьте найденные проблемы и сравните результаты до/после.

Задание 4. Проведите A/B-тест промпта. Возьмите работающий промпт (вариант A) и создайте вариант B (изменённый формат, другая модель, другие few-shot примеры). Настройте рандомизацию, соберите ≥ 200 ответов на вариант, проанализируйте primary metric и guardrail metrics с z-test. Ожидаемый результат: отчёт с p-value, рекомендацией и анализом trade-off (качество vs стоимость vs latency).


Источники

  • OpenAI. "Structured Outputs." API Documentation (20242026).
  • Anthropic. "Tool Use" and "Prompt Caching." Claude Documentation (20242026).
  • Google. "Structured Output with Gemini." Vertex AI Documentation (20252026).
  • Outlines. "Structured Generation." https://github.com/outlines-dev/outlines
  • Microsoft. "Guidance." https://github.com/guidance-ai/guidance
  • Khattab, O. et al. "DSPy: Compiling Declarative Language Model Calls." Stanford NLP (20242026).
  • Beurer-Kellner, L. et al. "LMQL: Programming Large Language Models." (20232025).
  • Anthropic. "Model Context Protocol (MCP)." https://modelcontextprotocol.io (20242026).

Навигация: