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

26 KiB
Raw Blame History

Спринты — 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_intentthread_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) — если свободный текст в промпте будет пропускать реальные случаи смены темы