docs: GRAPH_ARCHITECTURE v2 + разбивка Спринта 6 на 6a/6b с UI-чекпойнтами
Добавлен GRAPH_ARCHITECTURE_v2.md (уточнения v1 после анализа скрипта записи в вике клиники: различение soft-insertion и hard-handoff, защита от петель через handoff_count, resumable state, guards в new_booking, подписка ветки на разделы вики как альтернатива отдельным коллекциям Chroma, per-step RAG, eval-набор до Спринта 5, разбивка Спринта 5 на 5a/5b). SPRINTS.md переработан: - Спринт 5 закрыт как «ядро v1» с явным списком того, что не вошло из v2. - Спринт 6 разбит на 6a (структурированный выход + intent_steps + валидатор переходов + exit_conditions_text + handoff_count + suspended/resumable) и 6b (soft-insertion + guards + reason в escalate_human + умный роутер + 8 ручных сценариев). - В каждом блоке — UI-чекпойнт и явное «что проверяем глазами», чтобы можно было смотреть результат после каждого шага, а не в конце спринта. - Мульти-RAG (Спринт 7) делается до мини-eval (Спринт 8), чтобы наборы в eval проверяли поведение уже с per-intent retrieval. - Зафиксированы 4 принятых решения: момент обновления current_step, cap на soft-insertion, сверка шагов new_booking с вики, формат структурированного выхода — JSON-блок в хвосте ответа. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+224
-26
@@ -204,57 +204,253 @@
|
||||
### Цель
|
||||
Научить ветки вести многошаговые скрипты и бесшовно передавать тред в другую ветку, если пациент сменил тему.
|
||||
|
||||
### Статус: ⏳ Запланирован
|
||||
### Статус: ✅ Закрыт (ядро; дотяжка до GRAPH_ARCHITECTURE v2 — в бэклоге)
|
||||
|
||||
### Задачи
|
||||
|
||||
**Данные:**
|
||||
- [ ] Таблица `thread_state` (thread_id, current_intent, current_step, slots JSON)
|
||||
- [x] Таблица `thread_state` (thread_id, current_intent_code, current_step, slots_json, updated_at) + миграция Alembic (batch-режим под SQLite)
|
||||
|
||||
**State machine (первая ветка — `new_booking`):**
|
||||
- [ ] 6-шаговый скрипт: приветствие → перехват инициативы → мини-интервью по симптому → презентация двух слотов → подтверждение → запись
|
||||
- [ ] Модель на каждой реплике видит текущий шаг + собранные слоты (имя, симптом, выбранный слот)
|
||||
- [ ] Переход шагов управляется правилами в промпте ветки («если на шаге 3 пациент назвал время — перейди к шагу 5»)
|
||||
- [x] 6-шаговый скрипт: приветствие → повод → специалист → удобное время → подтверждение → запись
|
||||
- [x] Модель на каждой реплике видит блок `[ТЕКУЩЕЕ СОСТОЯНИЕ]` с `step` и `slots`
|
||||
- [x] Переход шагов управляется служебным тегом `[STATE: step=N; slots={...}]` в ответе модели (строковый тег, парсится балансировкой фигурных скобок)
|
||||
|
||||
**Exit conditions и bouncing:**
|
||||
- [ ] В промпт каждой ветки добавляется блок условий выхода
|
||||
- [ ] Парсер ответа ассистента ловит служебный сигнал `[INTENT_CHANGE: <code>]` → останавливает ветку
|
||||
- [ ] Роутер на каждой реплике: если классификация ≠ текущему `thread_state.current_intent` → `thread_state` сбрасывается, тред идёт в новую ветку с полной историей
|
||||
- [x] В промпт `new_booking` добавлен блок условий выхода с сигналом `[INTENT_CHANGE: <code>]`
|
||||
- [x] Парсер в `services/chat_service._parse_assistant_signals` вырезает служебные теги из ответа
|
||||
- [x] Bouncing: одна итерация (`MAX_BOUNCES=1`) — ветка может передать управление другой, делаем повторный вызов LLM
|
||||
- [x] Роутер на каждой реплике: если классификация ≠ `thread_state.current_intent_code` → сброс `step` и `slots`
|
||||
|
||||
**UI:**
|
||||
- [ ] В «Песочнице» новый блок «состояние треда»: текущий intent, шаг, собранные слоты
|
||||
- [ ] История переходов между ветками в рамках треда (timeline)
|
||||
- [x] В «Песочнице» блок «Состояние треда»: intent, шаг, слоты (JSON), список переходов в текущей реплике
|
||||
- [x] В отладке роутера — пометка, если ветка «передала управление»
|
||||
|
||||
### Критерий готовности
|
||||
- [ ] Сценарий из `GRAPH_ARCHITECTURE.md` («запись → пациент упомянул операцию → хирургия/оператор») проходит без сброса контекста
|
||||
- [ ] Ветка `new_booking` уверенно ведёт 6-шаговый скрипт на 3+ тестовых диалогах
|
||||
- [ ] В отладке видна вся цепочка: начальный intent → шаги → смена ветки → финальный intent
|
||||
- [x] Сценарий new_booking проходит: ФИО → повод → специалист → время → подтверждение собираются в `thread_state.slots`
|
||||
- [x] Переключение ветки через роутер: «Сколько стоит приём?» внутри записи → state сбрасывается в `price_question`
|
||||
- [x] В отладке видна вся цепочка: роутер-intent, served-intent, шаг, слоты, переходы
|
||||
|
||||
### Что НЕ вошло в этот спринт (по сравнению с GRAPH_ARCHITECTURE_v2.md)
|
||||
Реализовано ядро v1. Вся дотяжка до v2 — Спринт 6.
|
||||
|
||||
---
|
||||
|
||||
## Спринт 6. Мульти-RAG
|
||||
## Спринт 6a. State machine v2 — ядро, защита от петель, возврат в ветку
|
||||
|
||||
### Цель
|
||||
Дать каждой ветке свою коллекцию в Chroma, чтобы детская wiki не засоряла ответы общей записи, а скрипты возражений — ответы по ценам.
|
||||
Заменить строковый тег `[STATE: ...]` на структурированный выход модели с валидатором переходов по таблице `intent_steps`; добавить `handoff_count` с автовыходом в `escalate_human: routing_loop`; научить систему возобновлять прерванную ветку через `suspended_intent`. В конце Спринта 6a уже видно глазами: вкладка «Шаги» в «Настройках» для `new_booking`, в «Песочнице» — handoff_count и suspended_intent, timeline переходов первой версии.
|
||||
|
||||
### Статус: ⏳ Запланирован
|
||||
|
||||
### Принятые решения (зафиксировано 2026-04-24, действуют и для 6b)
|
||||
- **Момент обновления `current_step`** — после успешного коммита сообщения ассистента в БД.
|
||||
- **Cap на soft-insertion'ы подряд** — 3 (реализация в 6b).
|
||||
- **Шаги `new_booking` — сверить с вики клиники по ЛОР** до переписывания промпта в блоке A.
|
||||
- **Формат структурированного выхода** — JSON-блок в хвосте ответа, парсим сами балансировкой скобок + `json.loads`.
|
||||
|
||||
### Задачи и UI-чекпойнты (порядок: A → A2 → B → C)
|
||||
|
||||
**Блок A. Структурированный выход + таблица `intent_steps` + валидатор переходов (v2 §3.3)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] Новая таблица `intent_steps`: `id, intent_id FK, code (intro/qualify/present/offer_time/book/close), name, order_index, system_prompt Text, allowed_next JSON, guards JSON (пустой на этом спринте — наполняется в 6b/F)`. Миграция Alembic.
|
||||
- [ ] Сид шагов `new_booking` при старте: читает `prompts/intents/new_booking/steps/{code}.md` + `prompts/intents/new_booking/transitions.yaml`.
|
||||
- [ ] Разделить `prompts/intents/new_booking.md` на базовый промпт ветки (общие правила) + отдельные файлы на каждый из 6 шагов.
|
||||
- [ ] В `services/chat_service` — сборка промпта: `base_prompt + intent_steps[current_step].system_prompt + state_context`.
|
||||
- [ ] Парсер нового формата ответа: `{reply, state_after, slots_updated}` — JSON-блок в хвосте.
|
||||
- [ ] Валидатор: сверка `state_after` с `intent_steps.allowed_next`. Легален → применяем, иначе — остаёмся на текущем шаге + warning в лог. Слоты сливаем `{**old, **slots_updated}`.
|
||||
- [ ] `assistant_msg.text` = `reply`; служебные поля — в `assembled_prompt` для отладки.
|
||||
|
||||
*UI-чекпойнт A:*
|
||||
- [ ] В «Настройках» для ветки `new_booking` появляется вкладка **«Шаги»**: список шагов из `intent_steps` + на клик открывается редактор (textarea с `system_prompt`, чекбоксы с `allowed_next`). Кнопка «Сохранить шаг» — `PATCH /intents/{code}/steps/{step_code}` пишет сразу (без версионирования).
|
||||
- [ ] В «Песочнице» бейдж текущего шага берётся из `intent_steps.name`, а не из сырого числа. Если валидатор отклонил `state_after` — красная пометка «модель просилась в `X`, остались на `Y`».
|
||||
- [ ] **Что проверяем глазами:** открыть `new_booking` → вкладка «Шаги» видит 6 шагов; править любой промпт → применяется в новом треде; в песочнице прогнать «Здравствуйте, хочу записаться» → шаг подписан словами («Приветствие»), а не числом.
|
||||
|
||||
**Блок A2. `exit_conditions_text` — отдельное поле в `agent_configs` (v2 §UI)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] Миграция: добавить `exit_conditions_text Text NULLABLE` в `agent_configs`.
|
||||
- [ ] `compose_full_system_prompt` склеивает: `system_prompt + rules_text + exit_conditions_text`.
|
||||
- [ ] Миграция данных: при старте для существующих конфигов попытаться распарсить блок «Условия выхода» / `[INTENT_CHANGE: ...]` из хвоста `system_prompt` и перенести в новое поле. Не удалось — оставить пусто.
|
||||
|
||||
*UI-чекпойнт A2:*
|
||||
- [ ] В «Настройках» на вкладке активной версии — третья textarea `exit_conditions_text` рядом с `system_prompt` и `rules_text`.
|
||||
- [ ] **Что проверяем глазами:** у ветки `general_info` после миграции данных в поле `exit_conditions_text` лежат правила `[INTENT_CHANGE: ...]`, а не в теле промпта. В песочнице поведение не изменилось.
|
||||
|
||||
**Блок B. `handoff_count` и защита от петель (v2 §4.3)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] Миграция `thread_state`: добавить `handoff_count INT NOT NULL DEFAULT 0`.
|
||||
- [ ] В `chat_service` инкрементить при каждом hard-handoff (INTENT_CHANGE или router-инициированное переключение).
|
||||
- [ ] При `handoff_count >= 2` — авто-уход в `escalate_human` c `reason=routing_loop`. Ответ-заглушка формируется без нового вызова LLM («Передаю ваш вопрос администратору»).
|
||||
- [ ] Счётчик сбрасывается на 0 при возврате из `suspended_intent` (блок C) и при переходе в `escalate_human`.
|
||||
|
||||
*UI-чекпойнт B:*
|
||||
- [ ] В «Песочнице» в «Состоянии треда» — строка `handoff_count: N`. При автоуходе в `escalate_human: routing_loop` — явная отметка в timeline.
|
||||
- [ ] **Что проверяем глазами:** искусственная петля «хочу записаться → сколько стоит → хочу записаться → сколько стоит» → после второго-третьего handoff'а бот говорит «передаю администратору»; в песочнице `handoff_count` вырос, ветка сменилась на `escalate_human`.
|
||||
|
||||
**Блок C. `suspended_intent` + `resumable_step` + `resumable_slots` (v2 §4.4)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] Миграция: добавить колонки `suspended_intent`, `resumable_step INT`, `resumable_slots_json TEXT` (все nullable) в `thread_state`.
|
||||
- [ ] При hard-handoff из многошаговой ветки (`new_booking`) — сохранять `current_*` в `suspended_*` перед сбросом.
|
||||
- [ ] Возврат: роутер классифицировал реплику в `suspended_intent` → восстанавливаем `current_*` из `suspended_*` и очищаем поля. Альтернативный триггер — сигнал `[RESUME]` из ветки detour'а (наполняем в 6b).
|
||||
|
||||
*UI-чекпойнт C:*
|
||||
- [ ] В «Состоянии треда» — `suspended_intent` и `resumable_step` (если заполнены).
|
||||
- [ ] Timeline переходов между ветками в рамках треда: список типа `new_booking (step=4) → price_question → new_booking (step=4, восстановлено)`. Собирается на бекенде из diff'ов `intent_id` у соседних сообщений + лога handoff'ов.
|
||||
- [ ] **Что проверяем глазами:** запись до 4 шага → «сколько это стоит?» → `suspended_intent=new_booking, resumable_step=4` видно в панели → «ок, тогда бронируем» → слоты `new_booking` вернулись, шаг=4, timeline показывает три перехода.
|
||||
|
||||
### Критерий готовности 6a
|
||||
- [ ] Сценарии 1 (базовая запись), 3 (handoff с suspended), 4 (возврат из suspended), 6 (routing_loop) из блока H Спринта 6b проходят в «Песочнице».
|
||||
- [ ] `handoff_count` и `suspended_intent` видны глазами в «Состоянии треда».
|
||||
- [ ] Вкладка «Шаги» в «Настройках» работает — можно отредактировать промпт шага и увидеть эффект в песочнице без рестарта.
|
||||
- [ ] Третья textarea `exit_conditions_text` работает; данные старых веток мигрированы.
|
||||
- [ ] `current_step` пишется только после коммита `assistant_msg` — проверяется код-ревью.
|
||||
- [ ] Парсер структурированного выхода устойчив к невалидному `state_after`.
|
||||
|
||||
---
|
||||
|
||||
## Спринт 6b. Глубина сценария — soft-insertion, guards, reason, умный роутер
|
||||
|
||||
### Цель
|
||||
Поверх ядра из 6a — добавить различение soft/hard-handoff, guards в `new_booking`, структурированный reason в `escalate_human`, умный роутер, видящий `thread_state`. В конце Спринта 6b все 8 ручных сценариев из блока H проходят в «Песочнице».
|
||||
|
||||
### Статус: ⏳ Запланирован (после 6a)
|
||||
|
||||
### Задачи и UI-чекпойнты (порядок: D → F → E → G → H)
|
||||
|
||||
**Блок D. Soft-insertion vs hard-handoff (v2 §4.2)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] В промпт ветки `new_booking` (базовый + шаги `qualify/present/offer_time`) — правило «короткие боковые вопросы (цена услуги, адрес, часы, длительность приёма, требования к документам) отвечай сам, не покидая шаг». Модель возвращает `state_after=текущий_шаг`, `slots_updated={}`.
|
||||
- [ ] Миграция `thread_state`: добавить `soft_insertion_count INT NOT NULL DEFAULT 0`.
|
||||
- [ ] На soft-insertion счётчик инкрементится; на продвижение по шагу — сбрасывается в 0.
|
||||
- [ ] При `soft_insertion_count >= 3` — ветка в промпте получает явное указание «вернуть пациента к вопросу шага».
|
||||
|
||||
*UI-чекпойнт D:*
|
||||
- [ ] В «Состоянии треда» — `soft_insertion_count: N`.
|
||||
- [ ] В timeline переходов помечать soft-insertion как `new_booking · soft-answer (price)` — без смены ветки.
|
||||
- [ ] **Что проверяем глазами:** запись до шага 3 → «а сколько стоит?» → ответ по цене, шаг=3 сохранился, `soft_insertion_count=1`. Повторить 3 раза → на 3-м ответе бот возвращает к вопросу шага.
|
||||
|
||||
**Блок F. Guards в `new_booking` (v2 §3.2)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] В `intent_steps.guards` наполняем условия для `new_booking`: ребёнок → `legal_rep_name+legal_rep_phone` до перехода из `qualify`; запрос конкретного врача с листом ожидания → рукав `waitlist`; жалоба на слух без предварительного сурдолога → сначала `surgologist` в `specialist`.
|
||||
- [ ] Слоты: `is_child`, `legal_rep_name`, `legal_rep_phone`, `requested_doctor`, `waitlist_flag`, `needs_surgologist_first`.
|
||||
- [ ] Валидатор переходов (блок A 6a) проверяет `guards`: если не пройден — блокирует `state_after`, оставляет на шаге, возвращает пациенту ответ модели как есть.
|
||||
- [ ] Обновить промпты шагов под сценарии guard'ов.
|
||||
|
||||
*UI-чекпойнт F:*
|
||||
- [ ] На вкладке «Шаги» — отдельная textarea для `guards` (JSON) с валидацией формата.
|
||||
- [ ] В «Состоянии треда» — если валидатор заблокировал переход guard'ом, явная отметка «guard `require_legal_rep` не пройден, ждём `legal_rep_phone`».
|
||||
- [ ] **Что проверяем глазами:** сценарий 7 (ребёнок) — на шаге `qualify` после «это для сына, 5 лет» бот спрашивает ФИО и телефон родителя; пока не заполнены — не переходит; в песочнице видна причина блокировки. Сценарий 8 (конкретный врач) — переход в рукав `waitlist`.
|
||||
|
||||
**Блок E. `reason` в `escalate_human` (v2 §1, §5)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] Обновить промпт `_router`: при `escalate_human` возвращать пару `code + reason` (`acute_pain / surgery / angry / explicit_request / routing_loop`).
|
||||
- [ ] `RouterClient.classify` парсит reason, дефолт при неразобранном — `explicit_request`.
|
||||
- [ ] Ветка `escalate_human.md` и шаги (если есть) — reason влияет на текст первой реплики.
|
||||
- [ ] В `messages` — колонка `escalation_reason NULLABLE` (миграция). В API-ответе `/chat` поле `escalation_reason`.
|
||||
- [ ] Заготовка саммари для оператора: при эскалации формируется `{reason, history, slots_from_suspended}`, логируется в файл/консоль (канал передачи — Спринт 9).
|
||||
|
||||
*UI-чекпойнт E:*
|
||||
- [ ] В «Состоянии треда» — при активной эскалации показывать `reason`.
|
||||
- [ ] В «Отладке ответа» под блоком роутера — сгенерированное саммари оператора (read-only preview).
|
||||
- [ ] **Что проверяем глазами:** сценарий 5 («упомянул хирургию») → эскалация с `reason=surgery`, превью саммари содержит всю историю + собранные слоты. Сценарий 6 (петля) → эскалация с `reason=routing_loop`.
|
||||
|
||||
**Блок G. Умный роутер (видит `thread_state`)**
|
||||
|
||||
*Бекенд:*
|
||||
- [ ] `RouterClient.classify` принимает снимок `thread_state` (intent, step, slots, suspended_intent, handoff_count). Вставляет в системный промпт роутера блок «Сейчас идёт сценарий X на шаге Y, слоты Z. Если реплика — реакция или ответ на вопрос шага, скорее всего intent тот же».
|
||||
- [ ] Обновить `prompts/intents/_router.md` под новый формат.
|
||||
- [ ] Это снимает проблему Спринта 5: «Меня Алексей зовут» внутри `new_booking` сейчас уходит в `general_info`.
|
||||
|
||||
*UI-чекпойнт G:*
|
||||
- [ ] В «Отладке ответа» → блок «Решение роутера» — свернуть/развернуть кнопкой просмотр промпта, который ушёл в роутер (включая блок состояния треда). Полезно для отладки.
|
||||
- [ ] **Что проверяем глазами:** тот же сценарий 1 (базовая запись) прогнать повторно — «Меня Алексей зовут» остаётся в `new_booking`, не сбрасывается в `general_info`. В развёрнутом промпте роутера видно блок `[ТЕКУЩЕЕ СОСТОЯНИЕ]`.
|
||||
|
||||
**Блок H. Финальный прогон 8 ручных сценариев (прокси-eval)**
|
||||
- [ ] Зафиксировать в `eval/MANUAL_CASES.md` полный список 8 сценариев (уже описан в этом документе выше, просто консолидируем).
|
||||
- [ ] Прогнать в «Песочнице». Для каждого сценария — в `eval/MANUAL_REPORT.md` фиксируем результат (ок / расхождение + детали).
|
||||
1. Базовая запись (6 шагов → `confirmed=true`).
|
||||
2. Запись → вопрос про цену (soft-insertion, без смены шага).
|
||||
3. Запись → перенос старой записи (hard-handoff в `reschedule`, `suspended=new_booking`).
|
||||
4. Запись → detour → возврат «бронируем на четверг» (восстановление из `suspended`).
|
||||
5. Запись → упоминание хирургии (`escalate_human: surgery`, саммари).
|
||||
6. Искусственная петля (`routing_loop` после cap).
|
||||
7. Запись ребёнка (guard блокирует переход).
|
||||
8. Конкретный врач (waitlist-рукав).
|
||||
|
||||
### Критерий готовности 6b
|
||||
- [ ] Все 8 сценариев из блока H проходят в «Песочнице» без ручной правки state. `MANUAL_REPORT.md` закоммичен.
|
||||
- [ ] Все UI-чекпойнты (D, F, E, G) проверены глазами.
|
||||
- [ ] Роутер при активной state machine не сбрасывает intent на коротких репликах внутри сценария.
|
||||
- [ ] Саммари оператору формируется и логируется при эскалации — пусть пока и без канала передачи.
|
||||
|
||||
---
|
||||
|
||||
## Спринт 7. Мульти-RAG (вариант Б из v2: подписка ветки на разделы вики)
|
||||
|
||||
### Цель
|
||||
Дать каждой ветке собственный срез базы знаний, чтобы детская wiki не засоряла ответы по записи, а скрипты возражений — ответы по ценам. Согласно `GRAPH_ARCHITECTURE_v2.md` §6 — **Вариант Б** предпочтительнее отдельных коллекций: одна общая коллекция + фильтр по разделам вики в метаданных чанков. Делаем **до** мини-eval, чтобы наборы в Спринте 8 проверяли поведение уже с реальным per-intent retrieval.
|
||||
|
||||
### Статус: ⏳ Запланирован
|
||||
|
||||
### Задачи
|
||||
- [ ] Рефакторинг `services/vectorstore.py`: фабрика коллекций, `collection_by_intent(intent_code)` вместо единственной `operators_wiki`
|
||||
- [ ] В `intents` — поле `collection_name` (nullable; если пусто — используется общая `common_wiki`)
|
||||
- [ ] В UI загрузки документа — селектор «в какую ветку залить (или в общую)»
|
||||
- [ ] `POST /documents/upload` принимает `intent_code` как опциональный параметр
|
||||
- [ ] `reindex-all` учитывает коллекции (одна команда — все коллекции)
|
||||
- [ ] В «Отладке» — фильтр по веткам для просмотра документов
|
||||
- [ ] В `intents` — поле `wiki_sources: list[str]` (префиксы путей или doc-ID). Миграция.
|
||||
- [ ] В метаданные чанка при загрузке записывать `doc_path` / раздел вики.
|
||||
- [ ] В `services/vectorstore.py` — where-фильтр по `doc_path` на основе `wiki_sources` активной ветки при query.
|
||||
- [ ] UI «Настройки» — редактор `wiki_sources` у ветки (список префиксов).
|
||||
- [ ] Если `wiki_sources` пуст — дефолт: вся коллекция (для `general_info`).
|
||||
- [ ] Задел под v2 §3.4: опциональный `wiki_sources_by_step` (на уровне шага state machine) — сделать именно здесь, раз у нас уже есть state machine из Спринта 6.
|
||||
|
||||
### Критерий готовности
|
||||
- [ ] Документ, загруженный в ветку «детский приём», не появляется в retrieval для других веток
|
||||
- [ ] Общая коллекция `common_wiki` — fallback для веток без собственной базы (например, `general_info`)
|
||||
- [ ] После переключения ветки в диалоге retrieved-чанки берутся из нужной коллекции
|
||||
- [ ] Документ раздела `/wiki/pricing/*` автоматически используется только в `price_question` (без ручного дублирования).
|
||||
- [ ] При переключении ветки в диалоге retrieval берёт нужный срез.
|
||||
- [ ] В «Отладке» видно: какие префиксы активны, какие чанки пришли из каких разделов.
|
||||
- [ ] Для шага `offer_time` в `new_booking` отдельный per-step срез работает (если ветка его заполнила).
|
||||
|
||||
---
|
||||
|
||||
## Спринт 7. Сценарии + экспорт графа
|
||||
## Спринт 8. Мини-eval: роутер, handoff, resumable
|
||||
|
||||
### Цель
|
||||
После дотяжки v2 (Спринт 6) и мульти-RAG (Спринт 7) — зафиксировать автоматизированный тест-набор, чтобы следующие правки промптов и `wiki_sources` не ломали собранное. Формализует ручные сценарии из блока H Спринта 6.
|
||||
|
||||
### Статус: ⏳ Запланирован
|
||||
|
||||
### Задачи
|
||||
|
||||
**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-чанки из правильного раздела вики.
|
||||
|
||||
**Запускалка (CLI, не часть сервиса):**
|
||||
- [ ] `eval/run.py` — читает наборы, прогоняет через живой сервис. Режимы:
|
||||
- `router` — прямой вызов `RouterClient.classify()` на фразах из CSV (быстро).
|
||||
- `dialog` — полный `/chat` на чистых тредах, сверка intent + step + slots + handoff_count + reason + источники.
|
||||
- [ ] Вывод: per-ветка accuracy, confusion matrix, список расхождений с текстом реплики.
|
||||
- [ ] Отчёт: stdout + `eval/reports/{timestamp}.md` (добавлять в git для сравнения во времени).
|
||||
|
||||
**Документация:**
|
||||
- [ ] В `README.md` — раздел «Как прогнать eval» (одна команда).
|
||||
- [ ] Договорённость: перед правкой промпта роутера / ветки / `wiki_sources` — прогнать eval, зафиксировать baseline; после — сравнить.
|
||||
|
||||
### Критерий готовности
|
||||
- [ ] `eval/run.py` работает одной командой, полный набор проходит за ≤ 3 минуты.
|
||||
- [ ] Отчёт покрывает все 8 сценариев из блока H Спринта 6 + базовые kv-тесты роутера + RAG-проверки Спринта 7.
|
||||
- [ ] Baseline зафиксирован в `eval/reports/{date}_baseline.md` и добавлен в git.
|
||||
|
||||
---
|
||||
|
||||
## Спринт 9. Сценарии + экспорт графа
|
||||
|
||||
### Цель
|
||||
То, что изначально планировалось как Спринты 4 + 5 до архитектурного разворота. Теперь встроено в граф: прогон сценария проверяет не только текст ответов, но и правильность маршрутизации; экспорт — снапшот всего графа (intents + промпты + коллекции).
|
||||
@@ -285,6 +481,7 @@
|
||||
|
||||
## Бэклог
|
||||
|
||||
### Дальнейшие идеи
|
||||
- Раздельные правила по доменам — **перекрыто архитектурой: теперь это ветки (`intents`)**
|
||||
- A/B сравнение двух версий промпта на одном тест-наборе (в рамках одной ветки или между ветками)
|
||||
- Метрики качества ответов (MRR, CSAT по сценариям)
|
||||
@@ -293,7 +490,8 @@
|
||||
- Перевод правил из свободного текста в структурированный список (pattern → instruction)
|
||||
- Мультипользовательский режим (несколько операторов одновременно настраивают)
|
||||
- Хранение исходных файлов (`./data/uploads/{document_id}.{ext}` + `source_path` в метаданных Chroma) — чтобы переиндексировать без повторной загрузки и показывать оператору оригинал документа
|
||||
- Confidence score роутера + clarifying question при низкой уверенности — включить после реального прогона, если будет много ошибок классификации
|
||||
- Confidence score роутера + clarifying question при низкой уверенности — включить после реального прогона eval'а, если будет много ошибок классификации
|
||||
- Визуализация графа (веток и переходов между ними) — возможно, в виде отдельной панели
|
||||
- Вынесение роутера на отдельную более дешёвую модель (gpt-4o-mini, локальная Qwen) — когда вызовов станет много
|
||||
- Структурированные exit conditions (список триггеров с keyword-match) — если свободный текст в промпте будет пропускать реальные случаи смены темы
|
||||
- `routing_log` (таблица решений роутера по каждой реплике) — для офлайн-анализа и тюнинга, когда eval покажет, что нужно
|
||||
|
||||
Reference in New Issue
Block a user