Files
RAG_helper/SPRINTS.md
T
AR 15 M4 2e2f2321c3 docs: перекройка плана спринтов под графовую архитектуру
После Спринта 3 и обсуждения с пользователем зафиксирован разворот
на графовую архитектуру (GRAPH_ARCHITECTURE.md, коммит 907fdbe).
Обновляем SPRINTS.md под новое направление.

Статусы закрытых спринтов уже стояли ; здесь главное — план 4–7:

- Вставлена секция «Архитектурный разворот после Спринта 3» сразу
  после Спринта 3. В ней зафиксированы принятые решения по 5
  открытым вопросам из GRAPH_ARCHITECTURE.md: фреймворк (вручную,
  без LangGraph/n8n), модель роутера (DeepSeek + отдельный
  RouterClient), exit conditions (свободный текст + роутер на каждой
  реплике как подстраховка), эскалация (одна ветка escalate_human с
  полем reason), confidence score (не в первый спринт).

- Старые Спринт 4 (сценарии) и Спринт 5 (экспорт) заменены новыми
  четырьмя спринтами:
  - Спринт 4. Фундамент графа — intents + роутер + переключение веток.
    Задачи: таблица intents, миграция agent_configs с intent_id, seed
    6 стартовых веток, router_client, оркестрация в chat_service,
    UI настроек по веткам, отображение intent в отладке Песочницы.
  - Спринт 5. State machine + exit conditions (bouncing). Первая
    реальная state machine — в ветке new_booking (6 шагов). Парсер
    [INTENT_CHANGE: ...] + роутер на каждой реплике как подстраховка.
    UI показывает состояние треда и timeline переходов.
  - Спринт 6. Мульти-RAG. Фабрика коллекций в vectorstore, привязка
    коллекции к intent, селектор ветки при загрузке документа.
  - Спринт 7. Сценарии + экспорт графа. Старый план, но адаптирован:
    сценарии фиксируют ожидаемый intent на реплику; экспорт —
    снапшот всего графа (intents + активные промпты + коллекции).

- Бэклог дополнен: confidence score, визуализация графа, вынесение
  роутера на более дешёвую модель, структурированные exit conditions.
  Пункт про «раздельные правила по доменам» помечен как перекрытый
  архитектурой.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:28:04 +05:00

300 lines
26 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.
# Спринты — Chat Agent for Patients (инструмент настройки)
Поэтапный план MVP: RAG-ядро + веб-инструмент для настройки агента операторами. Подключение реальных каналов (приложение, МАКС) — вне скоупа, это задача другого разработчика.
---
## Спринт 1. RAG-ядро, загрузка документов и тестовая страница
### Цель
Поднять FastAPI-сервис с ChromaDB и сразу получить воспроизводимый «пайплайн в действии»: на одной тестовой странице видно, какие файлы загружены, можно задать одиночный вопрос от лица пациента и увидеть одновременно три вещи — какие чанки нашёл RAG, какой промпт собрался, какой ответ вернул DeepSeek. Аналог Debug UI из `work-pcs-dr-cdss`.
### Статус: ⏳ Запланирован
### Задачи
**RAG-ядро:**
- [ ] Инициализация проекта (main.py, config.py, requirements.txt, Dockerfile, docker-compose, .env.example)
- [ ] Переиспользовать паттерны из `work-pcs-dr-cdss`: `services/embeddings.py`, `vectorstore.py`, `document_processor.py`, `llm_client.py`
- [ ] Адаптировать чанкер под wiki-статьи (не клинреки)
**Эндпоинты:**
- [ ] `GET /health` — статус, кол-во документов и чанков
- [ ] `POST /documents/upload` — загрузка + превью первых 3 чанков в ответе
- [ ] `GET /documents` — список загруженных
- [ ] `DELETE /documents/{id}` — удаление
- [ ] `POST /query` — одиночный вопрос от лица пациента → ответ + источники со `score` + `assembled_prompt` (как RAG for Doctors, но без полей карты — только текст вопроса)
**Тестовая страница (одна HTML-страница, vanilla JS):**
- [ ] Шапка со статусом сервиса (auto-refresh `/health`, счётчики документов и чанков)
- [ ] Блок «База знаний»: drag & drop загрузка, таблица документов с превью первых чанков, кнопка удаления
- [ ] Блок «Тест-вопрос от пациента»: поле ввода вопроса, поле `top_k`, кнопка «Отправить»
- [ ] 3-колоночный результат ответа: релевантные фрагменты (текст + document, section, page, score) | собранный промпт | ответ LLM
### Критерий готовности
- [ ] Оператор открывает `http://localhost:PORT/` → видит Debug UI со статусом сервиса
- [ ] Загружает wiki-статью → она появляется в таблице, превью чанков отображается
- [ ] Пишет вопрос «как записать ребёнка к лору?» → получает ответ DeepSeek с указанием источников
- [ ] В средней колонке виден собранный промпт, в левой — какие чанки подтянулись со score
- [ ] Может удалить статью, счётчики в шапке обновляются
---
## Спринт 2. Многошаговый диалог с памятью треда
### Цель
Перейти от одиночного `/query` к полноценному диалогу: агент помнит историю, оператор ведёт разговор из 5+ реплик. Текущую страницу отладки (одиночный вопрос) оставляем без изменений, добавляем **вторую отладочную страницу** — «Песочница» со списком всех сохранённых диалогов.
### Статус: ✅ Закрыт
### Задачи
**Хранилище:**
- [ ] Стек: SQLite + SQLAlchemy 2.0 (async, ORM-стиль) + Alembic для миграций
- [ ] Таблицы:
- `threads` (id, name, user_id nullable, agent_config_id nullable, created_at, updated_at)
- `messages` (id, thread_id FK, role, text, sources_json, assembled_prompt, created_at)
- Колонки `user_id` и `agent_config_id` заводим сразу nullable — под будущие Спринты 3+ (мульти-пользователи, мульти-промпты), чтобы не тащить миграции задним числом
- [ ] Первая миграция Alembic с этими двумя таблицами
- [ ] Все диалоги сохраняются навсегда (никакого авто-удаления)
- [ ] Имя треда генерируется автоматически по первой реплике пациента + дата; оператор может переименовать вручную
**Эндпоинты:**
- [ ] `POST /chat` — принимает `thread_id` (или создаёт новый если не передан) + `text` → возвращает ответ агента + источники со score + `assembled_prompt`
- [ ] `GET /threads` — список всех диалогов (id, name, created_at, messages_count, превью первой реплики)
- [ ] `GET /threads/{id}` — тред целиком с историей сообщений
- [ ] `PATCH /threads/{id}` — переименовать тред
- [ ] `DELETE /threads/{id}` — удалить тред со всеми сообщениями
**Сборка ответа:**
- [ ] Базовый системный промпт (хардкод для старта): роль агента, тон клиники, что можно и нельзя
- [ ] Сборка контекста для LLM: системный промпт + история треда + RAG-чанки по последней реплике
**Веб-интерфейс:**
- [ ] В шапке обеих страниц — ссылки «Отладка» (текущая `/`) / «Песочница» (новая `/sandbox`)
- [ ] Текущий `static/index.html` остаётся без изменений
- [ ] Новая страница `static/sandbox.html` на отдельном маршруте `/sandbox`:
- [ ] левая колонка — список сохранённых диалогов: превью, дата, кнопка «переименовать», кнопка «удалить», кнопка «новый тред»
- [ ] центральная колонка — сам чат (оператор пишет как пациент, видит ответы агента, история подгружается при клике на тред из списка)
- [ ] правая колонка — retrieved-чанки со score + собранный промпт по последней реплике
### Критерий готовности
- [ ] Оператор может провести диалог из 5+ реплик, агент помнит контекст
- [ ] Все диалоги сохраняются и видны в левой колонке после перезагрузки страницы
- [ ] Оператор может открыть старый диалог, переименовать его, удалить
- [ ] В правой колонке видно, что нашёл RAG и что улетело в LLM на последнем шаге
- [ ] Старая страница отладки (`/`) работает как раньше, ничего не сломано
---
## Спринт 2.5. Доработки после пилота Спринтов 1–2
### Цель
Закрыть технический долг, накопленный за первые два спринта: почистить чанки от markdown-мусора, сделать ответ агента читаемым в UI, подготовить системный промпт к вынесению в редактор (Спринт 3) и навести порядок в логах и README.
### Статус: ✅ Закрыт
### Задачи
**Качество RAG:**
- [ ] Почистить чанки: убрать markdown-ссылки `[текст](url)`, блоки навигации `**Вернуться на:**`, дубликаты меню
- [ ] Эндпоинт `POST /documents/{id}/reindex` — переразметить существующий документ с новыми правилами чанкера (без повторной загрузки файла — но у нас пока нет хранения исходников, поэтому надо хранить исходный текст в метаданных чанков или сохранять оригинал при `upload`); решение по способу — в рамках задачи
- [ ] Эндпоинт `POST /documents/reindex-all` — прогнать переиндексацию по всей базе
**UI:**
- [ ] Markdown-рендер ответов ассистента в «Песочнице» (жирный, курсив, списки, код); реплики пациента оставить plain text
**Системность:**
- [ ] Вынести системный промпт из `services/llm_client.py` в отдельный файл (например, `prompts/system_prompt.md`), загружать при старте — задел под Спринт 3
- [ ] Привести логи в порядок: настроить root-logger так, чтобы `logger.exception` писался в stderr/файл; не ломать uvicorn access/error
- [ ] Обновить `README.md` под текущее состояние: две страницы, `/chat` + `/threads`, SQLite + Alembic, как запустить и как мигрировать
### Критерий готовности
- [ ] Загружаем свежую wiki-статью → в её чанках нет markdown-ссылок и блоков «Вернуться на:»
- [ ] На «Песочнице» ответ агента рендерится с жирным/курсивом/списками
- [ ] Системный промпт хранится в отдельном файле, правится без трогания кода
- [ ] При ошибке в `/chat` в логах виден читаемый traceback
- [ ] README описывает актуальное состояние (две страницы, эндпоинты, запуск, миграции)
---
## Спринт 3. Настройки агента: системный промпт и правила
### Цель
Дать операторам веб-редактор системного промпта и списка правил («если спрашивают про X — отвечай так-то», «если пациент злится — делай то-то»). Версионирование: можно сохранить конфигурацию и откатиться.
### Статус: ✅ Закрыт
### Задачи
- [ ] Хранилище (SQLite): `agent_configs` (version, created_at, system_prompt, rules_text, is_active)
- [ ] Эндпоинты: `GET /configs`, `POST /configs` (создать новую версию), `POST /configs/{id}/activate`
- [ ] Песочница использует активную версию при каждом `/chat`
- [ ] Веб-страница «Настройки агента»:
- [ ] редактор системного промпта (textarea)
- [ ] редактор правил (отдельным блоком; на старте — просто textarea, позже — список записей)
- [ ] кнопка «Сохранить как новую версию»
- [ ] список версий с кнопкой «Сделать активной» и пометкой активной
- [ ] Показ активной версии в шапке песочницы
### Критерий готовности
- [ ] Оператор меняет промпт → сохраняет как v2 → активирует → тестирует в песочнице → при желании откатывается к v1
- [ ] Правила реально влияют на ответы агента (проверяется вручную через песочницу)
---
## Архитектурный разворот после Спринта 3 (2026-04-23)
После пилота Спринтов 1–3 решили уходить от одного «мега-промпта» ко графовой архитектуре: **роутер намерений + изолированные ветки + state machine + exit conditions**. Подробности — в `GRAPH_ARCHITECTURE.md`.
**Принятые решения по открытым вопросам:**
- **Фреймворк оркестровки:** пишем вручную на Python. LangGraph/n8n не берём — проект компактный, свой стек работает, не тянем лишних зависимостей.
- **Модель для роутера:** остаёмся на DeepSeek, но `RouterClient` делаем отдельным классом от `LLMClient` — потом сменим модель в одном месте, если станет дорого.
- **Exit conditions:** свободный текст в промпте ветки + независимый роутер на каждой реплике. Если ветка пропустит триггер — роутер подстрахует.
- **Эскалация на человека:** одна ветка `escalate_human` с полем `reason` (`acute_pain` / `surgery` / `angry` / `explicit_request`). Отдельная маршрутизация «куда именно» — задача смежного разработчика при подключении каналов.
- **Confidence score:** не тянем в первый спринт. Роутер всегда возвращает один из intent'ов, при сомнении — `general_info`. После первого живого прогона посмотрим на реальные ошибки.
Старые Спринт 4 (сценарии) и Спринт 5 (экспорт) не удалены — они переехали в Спринт 7 с дополнением под граф (прогон сценариев проверяет маршрутизацию, экспорт — снапшот графа).
---
## Спринт 4. Фундамент графа — `intents` + роутер + переключение веток
### Цель
Заменить «один активный промпт на всё» на «свой промпт на каждую ветку + роутер выбирает ветку на каждой реплике». Это первый шаг к графовой архитектуре из `GRAPH_ARCHITECTURE.md`.
### Статус: ⏳ Запланирован
### Задачи
**Данные:**
- [ ] Новая таблица `intents` (code, name, description, is_enabled, order_index)
- [ ] Миграция Alembic + в `agent_configs` добавить `intent_id` (nullable для обратной совместимости)
- [ ] Сид при первом запуске: 6 стартовых веток — `new_booking`, `reschedule`, `price_question`, `medical_question`, `general_info`, `escalate_human`
- [ ] Перенос текущего v1 конфига в ветку `general_info` как стартовый промпт
**Роутер:**
- [ ] `services/router_client.py` — отдельный класс под DeepSeek, метод `classify(history, text) → intent_code`
- [ ] Короткий промпт-классификатор с фиксированным перечнем категорий
- [ ] При сомнении возвращает `general_info` (без confidence score на этом спринте)
**Оркестрация:**
- [ ] В `chat_service.send_message`: сначала `router.classify()` → активный конфиг выбранной ветки → `llm.chat()` с этим промптом
- [ ] В таблице `messages` сохраняется `intent_id` каждого обмена
**API:**
- [ ] `GET /intents` — список веток
- [ ] `PATCH /intents/{code}` — включить/выключить
- [ ] `POST /configs` принимает `intent_id`; создание новой версии — всегда в рамках ветки
**UI:**
- [ ] «Настройки»: слева список веток, справа редактор промпта/правил активной версии выбранной ветки
- [ ] В «Песочнице» в отладке показывать: решение роутера + выбранный intent + какая ветка ответила
### Критерий готовности
- [ ] «У меня острая боль» → `medical_question`
- [ ] «Сколько стоит приём» → `price_question`
- [ ] «Как доехать» → `general_info`
- [ ] В отладочной панели «Песочницы» виден intent и какая ветка дала ответ
- [ ] Для каждой ветки можно отдельно править промпт и сохранять версии
---
## Спринт 5. State machine + exit conditions (bouncing)
### Цель
Научить ветки вести многошаговые скрипты и бесшовно передавать тред в другую ветку, если пациент сменил тему.
### Статус: ⏳ Запланирован
### Задачи
**Данные:**
- [ ] Таблица `thread_state` (thread_id, current_intent, current_step, slots JSON)
**State machine (первая ветка — `new_booking`):**
- [ ] 6-шаговый скрипт: приветствие → перехват инициативы → мини-интервью по симптому → презентация двух слотов → подтверждение → запись
- [ ] Модель на каждой реплике видит текущий шаг + собранные слоты (имя, симптом, выбранный слот)
- [ ] Переход шагов управляется правилами в промпте ветки («если на шаге 3 пациент назвал время — перейди к шагу 5»)
**Exit conditions и bouncing:**
- [ ] В промпт каждой ветки добавляется блок условий выхода
- [ ] Парсер ответа ассистента ловит служебный сигнал `[INTENT_CHANGE: <code>]` → останавливает ветку
- [ ] Роутер на каждой реплике: если классификация ≠ текущему `thread_state.current_intent``thread_state` сбрасывается, тред идёт в новую ветку с полной историей
**UI:**
- [ ] В «Песочнице» новый блок «состояние треда»: текущий intent, шаг, собранные слоты
- [ ] История переходов между ветками в рамках треда (timeline)
### Критерий готовности
- [ ] Сценарий из `GRAPH_ARCHITECTURE.md` («запись → пациент упомянул операцию → хирургия/оператор») проходит без сброса контекста
- [ ] Ветка `new_booking` уверенно ведёт 6-шаговый скрипт на 3+ тестовых диалогах
- [ ] В отладке видна вся цепочка: начальный intent → шаги → смена ветки → финальный intent
---
## Спринт 6. Мульти-RAG
### Цель
Дать каждой ветке свою коллекцию в Chroma, чтобы детская wiki не засоряла ответы общей записи, а скрипты возражений — ответы по ценам.
### Статус: ⏳ Запланирован
### Задачи
- [ ] Рефакторинг `services/vectorstore.py`: фабрика коллекций, `collection_by_intent(intent_code)` вместо единственной `operators_wiki`
- [ ] В `intents` — поле `collection_name` (nullable; если пусто — используется общая `common_wiki`)
- [ ] В UI загрузки документа — селектор «в какую ветку залить (или в общую)»
- [ ] `POST /documents/upload` принимает `intent_code` как опциональный параметр
- [ ] `reindex-all` учитывает коллекции (одна команда — все коллекции)
- [ ] В «Отладке» — фильтр по веткам для просмотра документов
### Критерий готовности
- [ ] Документ, загруженный в ветку «детский приём», не появляется в retrieval для других веток
- [ ] Общая коллекция `common_wiki` — fallback для веток без собственной базы (например, `general_info`)
- [ ] После переключения ветки в диалоге retrieved-чанки берутся из нужной коллекции
---
## Спринт 7. Сценарии + экспорт графа
### Цель
То, что изначально планировалось как Спринты 4 + 5 до архитектурного разворота. Теперь встроено в граф: прогон сценария проверяет не только текст ответов, но и правильность маршрутизации; экспорт — снапшот всего графа (intents + промпты + коллекции).
### Статус: ⏳ Запланирован
### Задачи
**Сценарии:**
- [ ] Таблица `scenarios` (id, name, note, label, messages_json, expected_intents_json, config_snapshot_id)
- [ ] `POST /scenarios` — сохранить текущий тред «Песочницы» как сценарий, зафиксировать ожидаемый intent на каждую реплику пациента
- [ ] `POST /scenarios/{id}/run` — прогнать реплики пациента на текущих активных конфигах всех веток; вернуть новые ответы + распознанные intents
- [ ] Веб-страница «Сценарии»: список + открытая карточка со side-by-side (старый ответ / новый), подсветка «маршрутизация совпала / разошлась»
- [ ] Счётчик «ок / расхождение» по всей базе сценариев после последнего прогона
**Экспорт:**
- [ ] `GET /configs/export` — JSON-снапшот графа: все intents, для каждого — активный промпт и правила, список коллекций RAG и документов в них
- [ ] Документация API в README: `POST /chat`, `GET /health`, контракт ответов
- [ ] Инструкция «Как подключить канал» + пример curl / минимальный webhook-адаптер
- [ ] docker-compose поднимается одной командой, внешний разработчик получает рабочий `/chat`
### Критерий готовности
- [ ] После изменения промпта в одной из веток — прогон сценариев показывает расхождения именно в этой ветке
- [ ] Виден общий счётчик «ок / изменилось» по базе сценариев
- [ ] В README готов раздел «Как подключить канал», работает docker-compose-запуск
---
## Бэклог
- Раздельные правила по доменам — **перекрыто архитектурой: теперь это ветки (`intents`)**
- A/B сравнение двух версий промпта на одном тест-наборе (в рамках одной ветки или между ветками)
- Метрики качества ответов (MRR, CSAT по сценариям)
- Подсветка цитат источников в ответе агента
- Автосинхронизация wiki
- Перевод правил из свободного текста в структурированный список (pattern → instruction)
- Мультипользовательский режим (несколько операторов одновременно настраивают)
- Хранение исходных файлов (`./data/uploads/{document_id}.{ext}` + `source_path` в метаданных Chroma) — чтобы переиндексировать без повторной загрузки и показывать оператору оригинал документа
- Confidence score роутера + clarifying question при низкой уверенности — включить после реального прогона, если будет много ошибок классификации
- Визуализация графа (веток и переходов между ними) — возможно, в виде отдельной панели
- Вынесение роутера на отдельную более дешёвую модель (gpt-4o-mini, локальная Qwen) — когда вызовов станет много
- Структурированные exit conditions (список триггеров с keyword-match) — если свободный текст в промпте будет пропускать реальные случаи смены темы