Операторы получают веб-редактор: правят системный промпт и правила,
сохраняют как новую версию, активируют, откатываются. Активная версия
используется в «Песочнице» на каждый /chat.
Принципы, согласованные заранее:
- Сохранённые версии не редактируются — только создание новой. Честный
откат: v1 всегда та же, что была при создании.
- Правила на этом этапе — свободный markdown (textarea). Переход на
структурированные правила (pattern → instruction) — в бэклог.
- Файл prompts/system_prompt.md становится сид-источником: при первом
старте, если таблица agent_configs пустая, из него создаётся v1 и
активируется. Дальше правда идёт из БД, файл не трогаем.
- rules_text конкатенируется с system_prompt в один system-message
через compose_full_system_prompt: "{prompt}\n\nДополнительные
правила:\n{rules}".
- Активную версию удалить нельзя — сначала активируют другую.
Модель и миграция:
- db/models/AgentConfig: id, version (unique/indexed), name (nullable),
system_prompt, rules_text, is_active (indexed), created_at.
Без updated_at — версии неизменяемы.
- Миграция b4450e33664d_add_agent_configs_table.
Сервисы и роутеры:
- services/config_service: ensure_seed (seed v1 из файла),
list/get/get_active/create (version=max+1, при activate атомарно
сбрасывает is_active у остальных и ставит новой),
activate_config (та же схема), delete_config (возвращает причину
отказа: not_found / active), compose_full_system_prompt.
- services/chat_service.send_message: берёт active_cfg, собирает
system_prompt через compose_full_system_prompt, пишет
thread.agent_config_id при создании треда (колонка была nullable
ещё со Спринта 2 — пригодилась именно здесь).
- routers/configs: GET /configs, GET /configs/active, GET /configs/{id},
POST /configs (activate-флаг), POST /configs/{id}/activate,
DELETE /configs/{id} (404 / 400 если активная).
- Pydantic: AgentConfigCreateRequest, AgentConfigInfo, ListResponse,
DeleteResponse.
- main.py: ensure_seed в lifespan после инициализации БД/Chroma/LLM.
UI:
- static/settings.html — трёхблочная страница: имя версии, textarea
промпта, textarea правил, «Сохранить как новую» + галка
«Сразу активировать», «Загрузить активную в редактор». Справа —
список версий с бейджем «активная», действиями «Активировать» /
«Удалить» (disabled у активной) / «Загрузить в редактор». При
первом заходе активная версия автоматом подгружается в редактор.
- В nav на index.html и sandbox.html добавлена ссылка «Настройки».
- В шапке «Песочницы» — зелёный кликабельный бейдж «активная: vN · имя»
(ведёт на /settings.html), обновляется раз в 15 с.
E2E проверено: создана v2 с правилом «ВСЕГДА начинай со слов СПАСИБО
ЗА ВОПРОС», активирована; следующий /chat вернул ответ, начинающийся
ровно с этой фразы; assembled_prompt содержит блок «Дополнительные
правила». После отката на v1 тест-v2 удалена.
SPRINTS.md: Спринт 3 помечен закрытым.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Чанкер тащил в базу markdown-мусор: навигационные блоки «Вернуться на:»
со списками ссылок, инлайн-ссылки [текст](url) в теле, служебные
пометки _Источник: .../file.md_, лишние пустые строки. Всё это ело
контекст LLM и засоряло правую панель отладки.
- services/text_cleanup: clean_markdown_text — удаляет навигационные
строки, строки-только-ссылки (обычно это меню), служебные _Источник:_,
раскрывает инлайн-ссылки [x](url) → x, сжимает 3+ переносов до 2.
- services/document_processor: process_document теперь возвращает
(id, raw_text, sections, chunks); чистку применяем к заголовкам и
телам секций; чанки короче 20 символов выбрасываем с пересчётом
индексов. Вспомогательная rechunk_raw_text — для переиндексации.
Чтобы переиндексировать без повторной загрузки файла, нужен исходный
текст. Вводим отдельный слой:
- новая таблица SQLite documents (id, name, file_type, raw_text,
created_at, updated_at) + миграция Alembic 7ee7296ccd6d.
- db/models/Document + регистрация в db.models.__init__.
- services/document_service: save/get/list/delete для raw_text.
- routers/documents.upload: сохраняет raw_text в SQLite перед
индексацией в Chroma; delete убирает и из SQLite, и из Chroma.
- Новые эндпоинты POST /documents/{id}/reindex и
POST /documents/reindex-all — берут raw_text из SQLite, пропускают
через rechunk_raw_text, заменяют чанки в Chroma.
Существующие 4 документа были перезалиты вручную (решение: не делать
одноразовый backfill, проще залить заново). Старая Chroma очищена,
новые чанки прошли через чистку — мусор ушёл.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>