11 KiB
11 KiB
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) │
└──────────┘ └──────────┘ └───────────┘ └──────────┘
- Форкнуть активный промпт → новый
id,is_active=false - Изменить тело
- Прогнать на golden-наборе (3-5 реальных ДС с известным результатом)
- Сравнить метрики (precision/recall по ops)
- Если лучше — активировать. Если хуже — оставить как эксперимент или удалить.
Г. Текущее состояние (v1.0.109)
Промпты жёстко зашиты в llm_prompt.py:
_build_initial()— для первого документа_build_diff()— для сравнения (основной)
Что уже хорошо:
- JSON-контракт вывода описан
target_id(r1..rN) вместоtarget_hash- Правила: mode full_replace, UPDATE только изменённые поля, UNRESOLVED для неоднозначного
Чего не хватает (по приоритету):
- Few-shot примеры на домене ЦОД — самый большой рычаг качества
- Доменный глоссарий — чтобы модель понимала единицы измерения и термины
source_quoteв контракте вывода — для provenanceconfidenceв контракте вывода — самооценка модели- 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.