656 lines
46 KiB
Markdown
656 lines
46 KiB
Markdown
# ГЛАВА 6. ПРОМПТ — ЭТО ПРОТОКОЛ, А НЕ ПРОСЬБА
|
||
|
||
---
|
||
|
||
## 6.1. Почему «сделай хорошо» не работает
|
||
|
||
Представьте, что вы приходите в ресторан и говорите официанту: «Принесите что-нибудь вкусное». Может быть, вам повезёт. А может — вам подадут суши, хотя у вас аллергия на рыбу. Теперь представьте другой вариант: «Стейк medium rare, без гарнира, с соусом отдельно». Здесь официант не гадает — он выполняет спецификацию.
|
||
|
||
Промпт работает точно так же. Он может быть расплывчатой просьбой — или точным протоколом. Разница между ними — это разница между «авось повезёт» и «стабильный результат».
|
||
|
||
### Самый простой пример
|
||
|
||
Для начала — минимальная иллюстрация:
|
||
|
||
**✗ Плохой промпт:**
|
||
```
|
||
Напиши текст про собак.
|
||
```
|
||
|
||
**✓ Хороший промпт:**
|
||
```
|
||
Напиши информационный абзац (50–80 слов) о породе золотистый ретривер
|
||
для детской энциклопедии. Стиль: простой, дружелюбный.
|
||
Упомяни: происхождение, характер, для кого подходит.
|
||
```
|
||
|
||
Почему второй промпт лучше? Потому что в нём закрыто **пять пробелов**, которые в первом промпте модель заполняла бы случайно:
|
||
- **Жанр** — информационный абзац (не эссе, не стихотворение)
|
||
- **Объём** — 50–80 слов (не страница и не два предложения)
|
||
- **Тема** — конкретная порода (не «собаки вообще»)
|
||
- **Аудитория** — дети (определяет стиль)
|
||
- **Содержание** — три конкретных аспекта
|
||
|
||
Каждый незакрытый пробел — это развилка, где модель бросает монетку. Чем больше развилок, тем менее предсказуем результат.
|
||
|
||
### Пример посложнее
|
||
|
||
Теперь рассмотрим два промпта для реальной разработки:
|
||
|
||
**Промпт A:**
|
||
```
|
||
Напиши код для обработки данных.
|
||
```
|
||
|
||
**Промпт B:**
|
||
```xml
|
||
<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](04_causal_reading_and_the_power_of_the_first_frame.md)) следует оптимальный порядок элементов промпта. Это не «рекомендация» — это следствие того, как 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 календарных дней с момента подписания» — нет.
|
||
|
||
В промпте работает тот же принцип. «Ответь кратко» — неоднозначно (кратко — это одно предложение? один абзац? страница?). «Ответь одним абзацем из 3–5 предложений» — однозначно.
|
||
|
||
### Три свойства хорошего промпта-контракта
|
||
|
||
**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:
|
||
```json
|
||
{
|
||
"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, 1–5), 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_tools.md) (§11.2, §11.5).
|
||
|
||
### MCP: стандартизация описания инструментов
|
||
|
||
**Model Context Protocol (MCP)** — открытый стандарт, который унифицирует способ описания инструментов, контекстов и действий для LLM. Если function calling — это «дать модели кнопки», то MCP — это «единый стандарт для производства кнопок»: описываете инструмент один раз в формате MCP — и он работает с любой моделью. Подробно о протоколе, его архитектуре и практическом применении — в [Главе 11, §11.4](11_tools.md).
|
||
|
||
---
|
||
|
||
## 6.5. Паттерны промптов для типовых задач
|
||
|
||
### Паттерн 1: Extraction (извлечение данных)
|
||
|
||
```xml
|
||
<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 (генерация кода)
|
||
|
||
```xml
|
||
<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 (аналитика)
|
||
|
||
```xml
|
||
<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. Промпт как код: компиляция промптов
|
||
|
||
Новое направление (2024–2026) — фреймворки, которые превращают промпты из текстовых шаблонов в **компилируемые программы**:
|
||
|
||
**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](04_causal_reading_and_the_power_of_the_first_frame.md), эффект прайминга). Если пример на английском — модель с высокой вероятностью продолжит на английском, проигнорировав инструкцию.
|
||
|
||
**Исправление**: примеры должны **демонстрировать** все инструкции, включая язык, формат, стиль.
|
||
|
||
### Антипаттерн 7: «Температура 0 вместо структуры»
|
||
|
||
```
|
||
✗ "Я поставлю temperature=0, значит ответ будет стабильным."
|
||
```
|
||
|
||
Проблемы: `temperature=0` делает генерацию детерминированной при прочих равных, но **не гарантирует нужный формат**. Модель стабильно вернёт один и тот же ответ — который может быть стабильно неправильным. Температура влияет на случайность, а не на качество.
|
||
|
||
**Исправление**: temperature=0 + structured outputs + чёткий промпт = стабильный **и** правильный результат.
|
||
|
||
---
|
||
|
||
## 6.9. Библиотека шаблонов
|
||
|
||
Готовые шаблоны для четырёх распространённых задач. Копируйте, адаптируйте под свои нужды.
|
||
|
||
### Шаблон 1: Суммаризация документа
|
||
|
||
```xml
|
||
<role>Technical writer, expert in concise summarization</role>
|
||
<task>
|
||
Summarize the following document.
|
||
</task>
|
||
<rules>
|
||
- Output language: same as input
|
||
- Length: 3–5 bullet points, each 1–2 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: Код-ревью
|
||
|
||
```xml
|
||
<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 1–2 sentences",
|
||
"approve": true/false
|
||
}
|
||
</format>
|
||
<diff>
|
||
{code_diff}
|
||
</diff>
|
||
```
|
||
|
||
### Шаблон 3: Классификация текста
|
||
|
||
```xml
|
||
<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: Генерация тест-кейсов
|
||
|
||
```xml
|
||
<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>
|
||
- 5–8 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. Антипаттерн-аудит.** Соберите 5–10 промптов из вашей кодовой базы. Для каждого проверьте по списку антипаттернов из §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 (2024–2026).
|
||
- Anthropic. "Tool Use" and "Prompt Caching." Claude Documentation (2024–2026).
|
||
- Google. "Structured Output with Gemini." Vertex AI Documentation (2025–2026).
|
||
- 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 (2024–2026).
|
||
- Beurer-Kellner, L. et al. "LMQL: Programming Large Language Models." (2023–2025).
|
||
- Anthropic. "Model Context Protocol (MCP)." https://modelcontextprotocol.io (2024–2026).
|
||
|
||
---
|
||
|
||
**Навигация:**
|
||
- Назад: [Глава 5. Длинный контекст — иллюзия, что модель «видит всё»](05_long_context.md)
|
||
- Далее: [Глава 7. Разметка, теги и архитектура сложного промпта](07_markup_tags_and_prompt_architecture.md)
|