По итогам Спринтов 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>
17 KiB
Спринты — 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_promptGET /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) — чтобы переиндексировать без повторной загрузки и показывать оператору оригинал документа