feat(sprint7.5): обновление промптов 4 веток + eval-каркас и тест-кейсы в UI Настроек
Промпты веток (по docs/BRANCH_MAP_AND_PROMPTS_v1.md):
- reschedule.md — полная замена. Одношаговый сценарий из 6 пунктов:
action (cancel/reschedule), patient_name, patient_phone, original_time,
preferred_new_time. Слоты хранит вызывающая система, STATE_JSON не используется.
- price_question.md — добавлены 3 пункта: эндоскопия 1000₽ при первичном
ЛОР-приёме, лечебные процедуры доплачиваются, ОМС только сурдолог
(последний пункт работает только при подтверждении в базе).
- medical_question.md — расширена карта жалоб → специалист (ЛОР / сурдолог /
аллерголог / иммунолог / пульмонолог); добавлен пункт про беременность,
онкологию, психиатрию — мягко сказать «специализированная клиника»,
не предлагать запись.
- general_info.md — добавлены разделы «Отзывы и социальное доказательство»,
«Преимущества клиники», «Сокращения». Условия выхода расширены до 5 интентов.
escalate_human и new_booking не трогаем (escalate — карта говорит «не менять»;
new_booking — отдельный Спринт 7.6 по docs/OPTIMIZATION_CONVERSION_v1.md).
Применение в БД — вручную через UI «Настройки» (вариант A): оператор копирует
текст из .md, сохраняет как новую версию + активирует. Файлы — только seed.
Eval-каркас (заготовка под Спринт 8):
- eval/router_cases_booking.jsonl (875 кейсов new_booking) и
eval/router_cases_other.jsonl (698 кейсов: general_info 295, price 165,
escalate 139, medical 59, reschedule 40). CSV-исходники рядом.
- eval/README.md — формат, глоссарий, что это и зачем.
- routers/eval.py: GET /eval/router-cases?intent_code=...&limit=...
Lazy-кэш, сортировка по count desc, фильтр по expected_intent.
UI Настроек — выбор готового кейса в тест-блоке:
- Полоса «Готовый кейс:» с datalist (поиск по началу строки) + кнопка
«🎲 Случайный» + счётчик кейсов для активной ветки.
- При выборе — текст подставляется в textarea вопроса.
- Загружается при выборе ветки. Если кейсов 0 (для _router, _debug) — скрыто.
- Полная подсистема прогона (run.py, отчёты, baseline) — Спринт 8.
SPRINTS.md:
- Спринт 7 (мульти-RAG, часть A) → ✅ Закрыт (коммит 52b46bc).
- Заведён Спринт 7.5 «Обновление промптов 4 веток» (этот спринт).
- Заведён Спринт 7.6 «Оптимизация воронки new_booking до 4 шагов»
по OPTIMIZATION_CONVERSION_v1.md.
- В идеи на потом: сквозные правила всех веток (BRANCH_MAP §2),
отложенная документация Спринта 7 (docs.html карточка термина,
GRAPH_ARCHITECTURE_v5, README про мульти-RAG).
Также: docs/COMPETITOR_ALEXANDRA_top100.md — рабочие материалы пользователя
по конкурентному боту (NEXTBOT/Александра), используется как baseline для
оптимизации воронки в Спринте 7.6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+101
-33
@@ -426,38 +426,101 @@
|
||||
|
||||
**Подход** — A (M:N через document_id, не префиксы путей и не теги). Причины: `vectorstore.query()` уже умеет фильтровать по `document_ids` (нечего переписывать); нулевая миграция Chroma; на текущем масштабе (~30 документов, 6 веток) ручная подписка — 3-минутная задача один раз при загрузке; дисциплина именования путей — слабое место в проектах с >1 оператором, а галочки понятны без инструкции.
|
||||
|
||||
### Статус: ⏳ Запланирован
|
||||
### Статус: ✅ Закрыт (коммит `52b46bc`, 2026-04-27)
|
||||
|
||||
### Задачи
|
||||
|
||||
**Бэкенд:**
|
||||
- [ ] Миграция Alembic: новая таблица `intent_documents` с полями `intent_id` (FK на `intents.id`), `document_id` (varchar 36, тип как в metadata Chroma), `created_at`. PK составной (`intent_id`, `document_id`). Индекс по `document_id` для обратного поиска.
|
||||
- [ ] Модель `db/models/intent_document.py` (`IntentDocument`).
|
||||
- [ ] Сервис `services/intent_document_service.py` — функции `list_documents_for_intent(intent_code)`, `list_intents_for_document(document_id)`, `set_documents_for_intent(intent_code, document_ids)`, `set_intents_for_document(document_id, intent_codes)`.
|
||||
- [ ] API:
|
||||
- `GET /intents/{code}/documents` — список `document_id`, привязанных к ветке.
|
||||
- `PUT /intents/{code}/documents` — перезаписать список (body: `{ "document_ids": [...] }`).
|
||||
- `GET /documents/{id}/intents` — список кодов веток конкретного документа.
|
||||
- `PUT /documents/{id}/intents` — перезаписать список (body: `{ "intent_codes": [...] }`).
|
||||
- [ ] Retrieval-фильтр в `services/chat_service.py`: перед `vectorstore.query()` подтянуть список `document_id` для активной ветки. Передать как `document_ids=...`. **Дефолт пустой подписки — `document_ids=[]` (= 0 чанков), не «вся коллекция»**: пустая подписка означает «ветка не настроена», подмешивать случайное хуже, чем не подмешивать ничего.
|
||||
- [x] Миграция Alembic `i5c8b3a45f12_add_intent_documents`: новая таблица `intent_documents` с полями `intent_id` (FK на `intents.id`), `document_id` (varchar 36, тип как в metadata Chroma), `created_at`. PK составной, индекс по `document_id`.
|
||||
- [x] Модель `db/models/intent_document.py` (`IntentDocument`) с каскадом удаления.
|
||||
- [x] Сервис `services/intent_document_service.py` — функции `list_documents_for_intent_code`, `list_intents_for_document`, `set_documents_for_intent`, `set_intents_for_document`.
|
||||
- [x] API: `GET/PUT /intents/{code}/documents` и `GET/PUT /documents/{id}/intents` с PUT-семантикой «полный список», атомарно.
|
||||
- [x] Retrieval-фильтр в `services/chat_service.py` + `vectorstore.query()` различает `None` (нет фильтра, вся коллекция) и `[]` (пустая подписка, 0 чанков). Дефолт для пациентских веток — `[]`. Для `_debug` — `None` (отладка работает из коробки).
|
||||
|
||||
**UI:**
|
||||
- [ ] «Настройки» → страница ветки: новый блок «Документы базы знаний» — список всех загруженных документов с галочками, заголовок «подписано N из M», кнопка «Сохранить подписки».
|
||||
- [ ] «Отладка» → рядом с каждым документом (или в разворачиваемой панели) — компактный список веток с галочками, чтобы быстро подписать прямо на месте загрузки.
|
||||
- [ ] «Отладка» → кнопка «редактировать» рядом с «привязка»/«удалить»: разворачивает большой `<textarea>` с извлечённым `raw_text` документа. Кнопка «Сохранить и переиндексировать» делает `PUT /documents/{id}/raw` (обновляет `documents.raw_text` + переразметка + замена чанков в Chroma). С confirm перед сохранением. Подпись: правится извлечённый текст, для PDF/docx исходник теряется.
|
||||
- [ ] Системный промпт страницы «Отладка» переехал в обычную ветку `_debug` («Страница отладки»). Удалён `prompts/system_prompt.md` и логика `DEFAULT_SYSTEM_PROMPT` в `services/llm_client.py`. `routers/query.py` подтягивает активный конфиг ветки `_debug` (через `config_service`) и её подписки на документы (через `intent_document_service`). Дефолт пустой подписки в `_debug` — вся коллекция, чтобы Отладка работала «из коробки» (для пациентских веток дефолт другой — 0 чанков). На странице Отладки info-bar показывает активную версию и счётчик подписок, ссылка → Настройки. В `QueryResponse` добавлены `intent_code`, `config_version`, `rag_subscription`.
|
||||
- [ ] Песочница, отладочная панель: новый блок «Срез RAG: подписано N из M документов для ветки `<код>`». В «Найденных фрагментах» в каждой карточке — лейбл с `document_name`. Если подписка пуста и retrieval вернул 0 чанков — явная пометка «у ветки нет подписок, RAG-контекст пустой».
|
||||
- [x] «Настройки» → блок «Документы базы знаний» в правом сайдбаре, всегда видим (независимо от вкладки), сортировка по имени, счётчик «N из M».
|
||||
- [x] «Отладка» → кнопка «привязка» рядом с «удалить» → раскрывашка со списком веток, быстрая привязка прямо на месте.
|
||||
- [x] «Отладка» → кнопка «редактировать» → большой textarea с raw_text, `PUT /documents/{id}/raw` обновляет текст и переиндексирует в Chroma. С confirm.
|
||||
- [x] Системный промпт страницы «Отладка» переехал в ветку `_debug`. Удалён `prompts/system_prompt.md` и `DEFAULT_SYSTEM_PROMPT` в `llm_client.py`. info-bar на странице Отладки: версия + подписки + ссылка в Настройки.
|
||||
- [x] Песочница: блок «Срез RAG», поле `rag_subscription` в `ChatResponse`, ворнинг при пустой подписке.
|
||||
- [x] Тест-блок «Тест-вопрос от пациента» в центре Настроек (для любой выбранной ветки): textarea черновика → `/query` с `intent_code`, `system_prompt` (override), `disable_rag` для `_router`. Промпт-секция в `<details open>`, можно свернуть.
|
||||
|
||||
**Документация:**
|
||||
- [ ] `static/docs.html` — карточка термина «Подписка ветки на документы», упоминание в разделе «Что происходит на каждой реплике».
|
||||
- [ ] `docs/architecture/GRAPH_ARCHITECTURE_v5.md` — переписать §6 под подход A (M:N через `document_id`, без путей и без тегов). На v4 — шапка «устарело». Changelog v4→v5.
|
||||
- [ ] `README.md` — раздел про мульти-RAG.
|
||||
- [ ] (отложено в идеи на потом) `static/docs.html` — карточка термина «Подписка ветки на документы».
|
||||
- [ ] (отложено в идеи на потом) `docs/architecture/GRAPH_ARCHITECTURE_v5.md` — переписать §6 под подход A.
|
||||
- [ ] (отложено в идеи на потом) `README.md` — раздел про мульти-RAG.
|
||||
|
||||
### Критерий готовности
|
||||
- [ ] Документ, привязанный к `price_question`, появляется в retrieval только когда активна именно эта ветка. При переключении на `new_booking` — те же запросы возвращают другие чанки.
|
||||
- [ ] Ветка без подписок (например, свежесозданная) получает в retrieval 0 чанков — модель отвечает по промпту без RAG-контекста.
|
||||
- [ ] В Песочнице видно «подписано N из M», в найденных фрагментах — название документа.
|
||||
- [ ] Подписка работает в обе стороны UI: можно настроить и со страницы ветки (Настройки), и со страницы документа (Отладка).
|
||||
- [x] Документ, привязанный к `price_question`, появляется в retrieval только когда активна именно эта ветка.
|
||||
- [x] Ветка без подписок получает в retrieval 0 чанков (для пациентских) или вся коллекция (для `_debug`).
|
||||
- [x] В Песочнице видно «подписано N из M», в найденных фрагментах — название документа.
|
||||
- [x] Подписка работает в обе стороны UI: и со страницы ветки (Настройки), и со страницы документа (Отладка).
|
||||
|
||||
---
|
||||
|
||||
## Спринт 7.5. Обновление промптов 4 веток (без `new_booking`)
|
||||
|
||||
### Цель
|
||||
Применить предложения из `docs/BRANCH_MAP_AND_PROMPTS_v1.md` к четырём веткам — `reschedule`, `price_question`, `medical_question`, `general_info`. Промпты заменяются на новые более развёрнутые тексты по карте. Все 4 ветки остаются **одношаговыми** (без state machine, без слотов) — карта явно говорит, что только `new_booking` пошаговая. `escalate_human` и `_router` не трогаем.
|
||||
|
||||
### Статус: ⏳ Запланирован
|
||||
|
||||
### Задачи
|
||||
|
||||
**Тексты промптов (правка `prompts/intents/{code}.md`):**
|
||||
- [ ] `reschedule.md` — полная замена на сценарий из 6 пунктов (BRANCH_MAP §5): сначала `action` (cancel/reschedule), потом ФИО + телефон + старое время + желаемый интервал. Условия выхода: `new_booking` / `escalate_human` / `price_question`.
|
||||
- [ ] `price_question.md` — добавить 3 пункта (`+++`, BRANCH_MAP §6): про эндоскопию 1000₽ при первичном ЛОР-приёме, про лечебные процедуры (доплата), про ОМС только сурдолог.
|
||||
- [ ] `medical_question.md` — добавить 1 пункт (`+++`, BRANCH_MAP §7): беременность / онкология / психиатрия → специализированная клиника, передать администратору, не предлагать запись.
|
||||
- [ ] `general_info.md` — добавить 3 раздела (BRANCH_MAP §8): «Отзывы и социальное доказательство», «Преимущества клиники», «Сокращения».
|
||||
|
||||
**Применение в БД (вручную через UI):**
|
||||
- [ ] Оператор в Настройках для каждой из 4 веток: загрузить текст из обновлённого `.md` в textarea «Системный промпт ветки» → «Сохранить как новую версию» с галочкой «Сразу сделать активной».
|
||||
- [ ] Прогнать в тест-блоке «Тест-вопрос» по 1-2 кейса на каждую ветку, чтобы убедиться, что новый промпт работает с подписанными документами.
|
||||
|
||||
**Не делаем в этом спринте:**
|
||||
- `escalate_human` — карта явно говорит «рабочий и хороший, не менять».
|
||||
- `new_booking` — отдельный Спринт 7.6.
|
||||
- Сквозные правила (BRANCH_MAP §2) — в идеи на потом.
|
||||
- Поле `description` в `SEED_INTENTS` — текущие описания лучше карточных, не меняем.
|
||||
|
||||
### Критерий готовности
|
||||
- [ ] 4 файла `prompts/intents/*.md` обновлены и закоммичены.
|
||||
- [ ] В БД для каждой из 4 веток есть свежая активная версия с обновлённым текстом.
|
||||
- [ ] Тест-блок в Настройках для каждой из 4 веток отвечает корректно на 1-2 кейса.
|
||||
|
||||
---
|
||||
|
||||
## Спринт 7.6. Оптимизация воронки `new_booking` до 4 шагов
|
||||
|
||||
### Цель
|
||||
Сжать воронку `new_booking` с 6 шагов (`intro → qualify → present → offer_time → book → close`) до 4 (`intro → qualify → book → close`), переписать содержимое `qualify` под 5-пунктовый шаблон ответа (эмпатия → 2-3 ЛОР-причины → специалист → услуга/цена → CTA), перенести сбор имени с `intro` на `book`. Полная спецификация — в `docs/OPTIMIZATION_CONVERSION_v1.md`.
|
||||
|
||||
### Статус: ⏳ Запланирован
|
||||
|
||||
### Задачи
|
||||
|
||||
См. полный план в `docs/OPTIMIZATION_CONVERSION_v1.md`. Кратко:
|
||||
|
||||
**Блок A — сжатие воронки:**
|
||||
- [ ] `intro.md` — приветствие + открытый вопрос, имя НЕ спрашиваем.
|
||||
- [ ] `book.md` — телефон + имя в одной реплике.
|
||||
- [ ] Расширить `allowed_next` шага `intro`.
|
||||
|
||||
**Блок B — содержательный `qualify`:**
|
||||
- [ ] `qualify.md` — обязательный 5-пунктовый шаблон (эмпатия → гипотеза → специалист → услуга/цена → CTA).
|
||||
- [ ] Сохранить три «особые ситуации» (ребёнок, конкретный врач, жалобы на слух).
|
||||
|
||||
**Блок C — `present`:**
|
||||
- [ ] Решить (с пользователем): убрать как самостоятельный шаг или переписать в одну фразу-подтверждение. Спецификация рекомендует вариант 2 (убрать).
|
||||
|
||||
**Блок D — регрессия:**
|
||||
- [ ] 5 контрольных конверсионных кейсов (храп, боль в горле, тугоухость, насморк, звон в ушах) в `eval/MANUAL_CASES.md`.
|
||||
- [ ] Прогнать 8 ручных сценариев из блока H Спринта 6b — все должны проходить.
|
||||
|
||||
### Критерий готовности
|
||||
- [ ] На контрольном кейсе из спецификации `new_booking` отвечает по 5-пунктовому шаблону, до запроса телефона ≤ 3 реплик бота.
|
||||
- [ ] Все 8 ручных сценариев из блока H Спринта 6b проходят.
|
||||
- [ ] Промпты `intro.md`, `qualify.md`, `book.md` обновлены и активированы в БД.
|
||||
|
||||
---
|
||||
|
||||
@@ -471,17 +534,20 @@
|
||||
### Задачи
|
||||
|
||||
**Eval-наборы (отдельные файлы в репозитории, без БД):**
|
||||
- [ ] `eval/router_cases.csv` — 20–30 фраз на каждую из 6 веток: типичные, пограничные (ловушечные), злые (короткие, эмоциональные, с опечатками). Колонки: `text, expected_intent, note`.
|
||||
- [ ] `eval/handoff_cases.yaml` — 5–10 многошаговых мини-диалогов: реплики пациента по порядку + ожидаемый intent на каждую.
|
||||
- [ ] `eval/resumable_cases.yaml` — 3–5 сценариев detour-и-возврат: реплики + ожидаемые `current_intent`, `current_step`, ключевые слоты на каждом шаге.
|
||||
- [ ] `eval/loop_cases.yaml` — 1–2 сценария искусственной петли с проверкой `reason=routing_loop`.
|
||||
- [ ] `eval/guard_cases.yaml` — сценарии на guards (ребёнок, waitlist).
|
||||
- [ ] `eval/rag_cases.yaml` — сценарии на мульти-RAG: реплика внутри ветки → проверка, что retrieved-чанки из правильного раздела вики.
|
||||
|
||||
Все наборы в **JSONL** (одна строка = один кейс). Унифицированный формат, единый парсер. Схема описана в `eval/README.md`. Историческое замечание: в первой версии плана одношаговые кейсы были в CSV, многошаговые в YAML — отказались от зоопарка форматов в пользу одного JSONL.
|
||||
|
||||
- [x] `eval/router_cases_booking.jsonl` + `eval/router_cases_other.jsonl` — одношаговые кейсы маршрутизатора (875 + 698, собраны из реальных диалогов конкурента, см. `eval/README.md`). Схема: `{text, expected_intent, expected_reason?, count, note?}`. CSV-версии сохранены рядом для совместимости.
|
||||
- [ ] `eval/handoff_cases.jsonl` — 5–10 многошаговых мини-диалогов: реплики пациента по порядку + ожидаемая активная ветка / решение маршрутизатора / приостановленная ветка / счётчик переключений на каждом шаге.
|
||||
- [ ] `eval/resumable_cases.jsonl` — 3–5 сценариев detour-и-возврат: реплики + ожидаемые `current_intent`, `current_step`, ключевые слоты на каждом шаге.
|
||||
- [ ] `eval/loop_cases.jsonl` — 1–2 сценария искусственной петли с проверкой `reason=routing_loop`.
|
||||
- [ ] `eval/guard_cases.jsonl` — сценарии на защитные условия (ребёнок, waitlist).
|
||||
- [ ] `eval/rag_cases.jsonl` — сценарии на мульти-RAG: реплика внутри ветки → проверка, что в retrieved-чанках есть фразы из ожидаемого документа (или ожидаемые `document_id`).
|
||||
|
||||
**Запускалка (CLI, не часть сервиса):**
|
||||
- [ ] `eval/run.py` — читает наборы, прогоняет через живой сервис. Режимы:
|
||||
- `router` — прямой вызов `RouterClient.classify()` на фразах из CSV (быстро).
|
||||
- `dialog` — полный `/chat` на чистых тредах, сверка intent + step + slots + handoff_count + reason + источники.
|
||||
- [ ] `eval/run.py` — читает JSONL-наборы, прогоняет через живой сервис. Режимы:
|
||||
- `router` — прямой вызов `RouterClient.classify()` на одношаговых кейсах (быстро).
|
||||
- `dialog` — полный `/chat` на чистых тредах, сверка по каждому шагу: активная ветка + решение маршрутизатора + текущий шаг + слоты + счётчик переключений + причина эскалации + retrieved-источники.
|
||||
- [ ] Вывод: per-ветка accuracy, confusion matrix, список расхождений с текстом реплики.
|
||||
- [ ] Отчёт: stdout + `eval/reports/{timestamp}.md` (добавлять в git для сравнения во времени).
|
||||
|
||||
@@ -490,8 +556,8 @@
|
||||
- [ ] Договорённость: перед правкой промпта роутера / ветки / `wiki_sources` — прогнать eval, зафиксировать baseline; после — сравнить.
|
||||
|
||||
### Критерий готовности
|
||||
- [ ] `eval/run.py` работает одной командой, полный набор проходит за ≤ 3 минуты.
|
||||
- [ ] Отчёт покрывает все 8 сценариев из блока H Спринта 6 + базовые kv-тесты роутера + RAG-проверки Спринта 7.
|
||||
- [ ] `eval/run.py` работает одной командой, режим `router` проходит за ≤ 30 секунд (на `count >= 2`), режим `dialog` — за ≤ 3 минуты.
|
||||
- [ ] Отчёт покрывает все 8 сценариев из блока H Спринта 6 + одношаговые кейсы маршрутизатора + RAG-проверки Спринта 7.
|
||||
- [ ] Baseline зафиксирован в `eval/reports/{date}_baseline.md` и добавлен в git.
|
||||
|
||||
---
|
||||
@@ -528,6 +594,8 @@
|
||||
## Бэклог
|
||||
|
||||
### Дальнейшие идеи
|
||||
- **Сквозные правила всех веток** (из `docs/BRANCH_MAP_AND_PROMPTS_v1.md` §2): тон, что нельзя говорить, обработка сокращений, обязательное предупреждение про доп. расходы. Сейчас этого механизма нет — каждая ветка хранит свои `rules_text` отдельно. Завести «глобальный» промпт-префикс (например, поле `Intent.global_prefix_id` или общая запись в `agent_configs` с зарезервированным `intent_code = "_global"`), подмешивать в системный промпт каждой ветки до её собственного. Альтернатива — продолжать копипастить общие правила в `rules_text` каждой ветки, что хуже для поддержки.
|
||||
- **Документация Спринта 7** — отложено: карточка термина «Подписка ветки на документы» в `static/docs.html`; обновление архитектуры до `GRAPH_ARCHITECTURE_v5.md` (§6 переписать под подход A — M:N через `document_id`); раздел про мульти-RAG в `README.md`. Закроется одним заходом, когда станет понятна часть Б Спринта 7 (внешняя вики).
|
||||
- **Спринт 7, часть Б: автосинхронизация с внешней вики операторов.** Часть A Спринта 7 — ручная подписка через UI: оператор сам загружает документы и сам ставит галочки. Часть Б — подключение к внешней системе ведения вики (которая «тщательно ведётся операторами»): автоматическое обновление документов, привязка подписок к источникам в той системе, версионирование. Конкретика появится, когда будет известно, что за внешняя система.
|
||||
- **Per-step `wiki_sources`** (из v4 §3.4): отдельная подписка на уровне шага машины состояний (например, на `book` подмешивать только документы про подготовку к приёму, на `qualify` — про услуги и врачей). Сейчас не нужно — все шаги `new_booking` логически работают с одной и той же базой. Возвращаться, когда увидим, что какой-то шаг подбирает не те чанки.
|
||||
- **Превью markdown в редакторе документа** (страница «Отладка», кнопка «редактировать»): сейчас в textarea виден сырой markdown с символами `#`, `**`. Добавить split-view (слева исходник, справа отрендеренный markdown через уже подключённые `marked.js` + `DOMPurify` из Песочницы). На узких экранах — вертикальный стек. Альтернативы: вкладки «редактор/превью» (проще, но с переключением) или WYSIWYG (TipTap / EasyMDE — +500 KB и риск кривого экранирования). Рекомендация на момент записи — split-view.
|
||||
|
||||
Reference in New Issue
Block a user