# Спринты — 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 - [ ] Правила реально влияют на ответы агента (проверяется вручную через песочницу) --- ## Спринт 4. Сценарии: сохранение и прогон ### Цель Позволить операторам сохранять отработанные диалоги из песочницы как «сценарии» (с пометкой «эталон / ок / не ок» и заметкой), а потом прогонять их на текущей конфигурации, чтобы сразу увидеть, не сломалось ли что-то после правок. ### Статус: ⏳ Запланирован ### Задачи - [ ] Хранилище (SQLite): `scenarios` (id, name, note, label, messages_json, reference_answers_json, config_version_id) - [ ] Эндпоинты: - [ ] `POST /scenarios` — сохранить текущий тред песочницы как сценарий - [ ] `GET /scenarios`, `GET /scenarios/{id}` - [ ] `POST /scenarios/{id}/run` — прогнать реплики пациента на текущей активной конфигурации, вернуть новые ответы агента - [ ] Возможность пометить «правильный ответ оператора» для каждой реплики пациента (эталон) - [ ] Веб-страница «Сценарии»: - [ ] список сценариев с метками и датой - [ ] открытая карточка: реплики пациента, ответы агента при сохранении, опциональные «эталонные ответы» - [ ] кнопка «Прогнать на текущей конфигурации» → показывает side-by-side: старый ответ / новый ответ - [ ] счётчик «сколько сценариев остались в статусе ок» после последнего прогона ### Критерий готовности - [ ] Оператор может сохранить диалог как сценарий, добавить эталонные ответы, пометить «ок» - [ ] После изменения промпта прогон сценариев показывает, где ответы расходятся - [ ] Виден общий счётчик «ок / изменилось» по всей базе сценариев --- ## Спринт 5. Экспорт конфигурации для внешней интеграции ### Цель Зафиксировать API-контракт и упаковать активную конфигурацию так, чтобы другой разработчик мог подключить чат-канал (приложение / МАКС-бот) без обращений к нам. ### Статус: ⏳ Запланирован ### Задачи - [ ] Документация API: `POST /chat` (с `channel_id`, `user_id`, `thread_id`, `text`), `GET /health` - [ ] Эндпоинт `GET /configs/active/export` — JSON со снапшотом: активный промпт + правила + список документов RAG - [ ] Инструкция «как подключиться» в README (пример curl-запроса + минимальный webhook-адаптер) - [ ] Проверка: внешний разработчик может поднять сервис по docker-compose и получить валидный ответ от `/chat` ### Критерий готовности - [ ] README раздел «Как подключить канал» готов - [ ] Docker-compose поднимается одной командой - [ ] На заданном тестовом запросе `/chat` возвращает ответ, который мы видим и в веб-песочнице --- ## Бэклог - Раздельные правила по доменам (детский приём / ДМС / взрослый приём) - A/B сравнение двух версий промпта на одном тест-наборе - Метрики качества ответов (MRR, CSAT по сценариям) - Подсветка цитат источников в ответе агента - Автосинхронизация wiki - Перевод правил из свободного текста в структурированный список (pattern → instruction) - Мультипользовательский режим (несколько операторов одновременно настраивают) - Хранение исходных файлов (`./data/uploads/{document_id}.{ext}` + `source_path` в метаданных Chroma) — чтобы переиндексировать без повторной загрузки и показывать оператору оригинал документа