Files
RAG_helper/SPRINTS.md
T
AR 15 M4 4e45b8b181 feat(sprint2.5): логи, вынесение системного промпта, markdown-рендер
Три подряд доработки по плану Спринта 2.5.

1) Логи. Проблема: uvicorn ставит handlers на root-logger до того, как
отработает наш lifespan, поэтому logging.basicConfig там был no-op, и
logger.exception ничего не писал. Переносим basicConfig на уровень
импорта main.py с force=True — наш StreamHandler перебивает
uvicorn-овский root, остальные логгеры (uvicorn.access, uvicorn.error,
alembic, chromadb) остаются со своими форматами. В lifespan
basicConfig больше не зовётся.

2) Системный промпт вынесен из services/llm_client.py в
prompts/system_prompt.md. LLMClient читает файл при импорте модуля
через _load_system_prompt(); если файла нет — пустая строка + warning.
Это задел под Спринт 3, где промпт будет редактируемым и
версионируемым — физически положить его как файл дешевле, чем
держать в исходниках.

3) Markdown в ответах ассистента. Подключены marked и DOMPurify с
CDN в sandbox.html. Рендер через renderMd(text): marked.parse +
DOMPurify.sanitize — защищает от <script> на случай, если LLM вернёт
сырой HTML. Реплики пациента остаются plain text (esc). Добавлены
стили для p/ul/ol/code/pre/a/h1-h3/blockquote внутри .msg.assistant,
чтобы всё выглядело уместно в пузыре. Обёртка msg-body введена,
чтобы разделить контент и msg-meta.

План в SPRINTS.md уточнён по переиндексации — будет отдельный
endpoint.

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

202 lines
18 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
- [ ] Правила реально влияют на ответы агента (проверяется вручную через песочницу)
---
## Спринт 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) — чтобы переиндексировать без повторной загрузки и показывать оператору оригинал документа