This commit is contained in:
“Naeel”
2026-06-23 09:07:26 +04:00
parent c4861e17c1
commit e49a094121
7 changed files with 569 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
# 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`.