Files
contracts/History/topics/prompt-strategy.md
T

169 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Prompt Management Strategy
Дата: 2026-06-23 | Версия: v1.0 | Контекст: v1.0.109, обсуждение архитектуры промптов
---
## А. Несколько промптов — зачем и как
Смешиваются **две разные потребности** — их нельзя путать:
### 1. Версионирование одного промпта (ОБЯЗАТЕЛЬНО)
Это не «несколько промптов», а история эволюции одного.
**Почему критично:**
- `prompt_version` уже кладётся в `spec_events` (provenance, Opus раунд 2).
- Значит промпт **обязан быть иммутабельным** — правка «на месте» сломает трассировку: старые события будут ссылаться на текст, которого больше нет.
- **Правка = новая версия**, а не перезапись. Откат, аудит «какая версия породила какой результат», сравнение версий — бесплатно.
### 2. Несколько РАЗНЫХ промптов одновременно
| Сценарий | Вердикт |
|----------|---------|
| **По роли в пайплайне** — разные LLM-вызовы (извлечение услуг vs сравнение vs резолв имён) | ✅ У каждого свой промпт. Настоящая причина «несколько» — по **задаче**. |
| **Варианты для эксперимента** — форкнул активный, сделал строже, прогнал на тестах, сравнил | ✅ Loop «дублировать → изменить → протестировать → активировать» — правильно. |
| **Каталог из 20 промптов «на всякий случай»** | ❌ Ловушка. Для узкого домена (colocation/ЦОД) нужен **ОДИН хороший** промпт на задачу. |
### Рекомендуемая модель: prompt-as-version (не prompt-as-document)
Одна таблица, один активный на `role`, вся история — строки:
| Поле | Тип | Смысл |
|------|-----|-------|
| `id` | UUID PK | Версия (иммутабельная) |
| `role` | VARCHAR | Шаг пайплайна: `extract`, `diff` |
| `name` | VARCHAR | Человеческое имя («default-v3», «strict-prices») |
| `body` | TEXT | Тело промпта с плейсхолдерами |
| `parent_id` | UUID FK→prompts.id | От какой версии форкнут |
| `is_active` | BOOLEAN | UNIQUE(role, is_active) с частичным индексом WHERE is_active |
| `created_at` | TIMESTAMP | |
| `created_by` | VARCHAR | Мульти-юзер |
| `notes` | TEXT | Что изменили и зачем |
**Операции:**
- **Править** = `INSERT` новой строки с `parent_id` на предыдущую. Не `UPDATE`.
- **Активировать** = переключить `is_active` (старая теряет флаг).
- **Дублировать** = копия под новым `name` (форк).
- **Откат** = активировать старую версию.
**Никогда не UPDATE текста на месте.**
---
## Б. Как составлять промпт
### Стартовый шаблон — ОБЯЗАТЕЛЕН
Пустое поле ввода — катастрофа для качества. Всегда форкать от выверенного дефолта, не с нуля.
### Структура (8 секций, порядок важен)
Для доменной задачи **без structural output** (gpt-oss-120b):
```
┌─────────────────────────────────────────────┐
│ 1. РОЛЬ / КОНТЕКСТ │
│ «ты эксперт по договорам colocation │
│ и облачных услуг ЦОД» │
├─────────────────────────────────────────────┤
│ 2. ЗАДАЧА │
│ «сравни текущую спецификацию с документом,│
│ верни операции изменений» │
├─────────────────────────────────────────────┤
│ 3. ДОМЕННЫЙ ГЛОССАРИЙ │
│ стойко-место, кВт мощности, colocation, │
│ единицы измерения, типы услуг. │
│ Без этого модель путает домен. │
├─────────────────────────────────────────────┤
│ 4. ФОРМАТ ВХОДА │
│ Как подаётся spec_current (с ID r1..rN), │
│ как подаётся текст документа. │
├─────────────────────────────────────────────┤
│ 5. КОНТРАКТ ВЫВОДА │
│ Строгая JSON-схема ops: поля, типы, │
│ обязательность. КРИТИЧНО — structural │
│ output нет, схема держится промптом. │
├─────────────────────────────────────────────┤
│ 6. ПРАВИЛА / ОГРАНИЧЕНИЯ │
│ «ссылайся только на id из spec_current», │
│ «верни source_quote», «верни confidence», │
│ «name существующих строк не переписывай» │
├─────────────────────────────────────────────┤
│ 7. FEW-SHOT ПРИМЕРЫ (2-3) │
│ ⭐ САМЫЙ МОЩНЫЙ РЫЧАГ для бесплатной │
│ модели без fine-tune. Примеры учат и │
│ формату, и поведению лучше инструкций. │
│ Вход→Выход на ВАШЕМ домене (ЦОД). │
├─────────────────────────────────────────────┤
│ 8. EDGE-CASES │
│ Как помечать неоднозначность, что делать │
│ при low-confidence. │
└─────────────────────────────────────────────┘
```
### Принцип разделения: статика vs рантайм
```
[версионируемая часть — в БД] [рантайм-инъекция — в коде]
роль + глоссарий + правила + {spec_current} + {document_text}
+ few-shot + контракт вывода
```
- Статическая инструкция (секции 1-8) → **версионируется** в `prompts.body`.
- Динамические данные (`{spec_current}`, `{document_text}`, `{date}`) → **подставляются в рантайме** через плейсхолдеры.
- В БД хранится тело промпта **с плейсхолдерами**, не с конкретными данными.
---
## В. Prompt CI — как с этим работать
```
┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐
│ Дублировать │ → │ Изменить │ → │ Прогнать на │ → │ Активировать │
│ (форк) │ │ (новая v) │ │ golden-наборе│ │ (is_active) │
└──────────┘ └──────────┘ └───────────┘ └──────────┘
```
1. Форкнуть активный промпт → новый `id`, `is_active=false`
2. Изменить тело
3. Прогнать на golden-наборе (3-5 реальных ДС с известным результатом)
4. Сравнить метрики (precision/recall по ops)
5. Если лучше — активировать. Если хуже — оставить как эксперимент или удалить.
---
## Г. Текущее состояние (v1.0.109)
Промпты жёстко зашиты в `llm_prompt.py`:
- `_build_initial()` — для первого документа
- `_build_diff()` — для сравнения (основной)
**Что уже хорошо:**
- JSON-контракт вывода описан
- `target_id` (r1..rN) вместо `target_hash`
- Правила: mode full_replace, UPDATE только изменённые поля, UNRESOLVED для неоднозначного
**Чего не хватает (по приоритету):**
1. Few-shot примеры на домене ЦОД — самый большой рычаг качества
2. Доменный глоссарий — чтобы модель понимала единицы измерения и термины
3. `source_quote` в контракте вывода — для provenance
4. `confidence` в контракте вывода — самооценка модели
5. Edge-cases инструкция — как вести себя при неоднозначности
---
## Д. План миграции (если делать)
| Шаг | Что | Сложность |
|-----|-----|-----------|
| 1 | Таблица `prompts` в PostgreSQL (Lucee datasource `baza`) | Низкая |
| 2 | CRUD в `api.cfm` для prompts (list, get, create, set_active) | Средняя |
| 3 | `llm_prompt.py` → читает активный промпт из Lucee API вместо хардкода | Средняя |
| 4 | `prompt_version` в `spec_events` — ссылка на `prompts.id` | Низкая |
| 5 | UI для prompts в `index.cfm` (список версий, редактор, активация) | Высокая |
| 6 | Golden-набор: 3-5 ДС + эталонные ops | Ручная работа |
| 7 | Prompt CI: скрипт прогона на golden-наборе + сравнение метрик | Средняя |
**Первый практический шаг** (максимум пользы, минимум кода): добавить few-shot примеры в текущие промпты прямо в `llm_prompt.py`, не дожидаясь таблицы `prompts`.