835c0b3cc3
По итогам Спринтов 1–2 накопился технический долг и несколько косяков, которые видно при живой работе в «Песочнице». Выделяем промежуточный спринт-доработок перед заходом на Спринт 3: - Качество RAG: чанкер тащит в базу markdown-ссылки, блоки навигации вроде «Вернуться на:», дубликаты меню — из-за этого в LLM уходит мусор вместо полезного контекста. - UI: ответ ассистента рендерится plain text с сырыми ** и дефисами списков — в чате это читать тяжело. - Системный промпт зашит в services/llm_client.py — перед Спринтом 3 (редактор промпта) его логично вынести в отдельный файл. - Логи: logger.exception сейчас съедается конфигом uvicorn, traceback при 500-х не виден — диагностика вслепую. - README не обновлялся с init-коммита, не отражает ни Спринта 1, ни 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
200 lines
17 KiB
Markdown
200 lines
17 KiB
Markdown
# Спринты — 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)`, блоки навигации `**Вернуться на:**`, дубликаты меню; добавить нормализацию/переиндексацию текущей базы (или documented reindex procedure)
|
||
|
||
**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) — чтобы переиндексировать без повторной загрузки и показывать оператору оригинал документа
|