diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/doc/todo.md b/doc/todo.md new file mode 100644 index 0000000..3d60579 --- /dev/null +++ b/doc/todo.md @@ -0,0 +1,32 @@ +# TODO + +- [ ] P0: Переключить основной query на `question.search_text` с fallback на `question.text` +- [ ] P0: Подключить `question.variants` как дополнительные query-варианты +- [ ] P0: Подключить `question.hyde` как дополнительные dense-запросы +- [ ] P0: Подключить `question.keywords` как основу для sparse-запросов +- [ ] P0: Перестать терять retrieval-кандидатов после rerank +- [ ] P0: Дедуплицировать `message_ids` перед ответом +- [ ] P0: Ограничить финальную выдачу до `top-50` +- [ ] P0: Агрегировать score по `message_id` +- [ ] P1: Перейти с символьного chunking на chunking по сообщениям +- [ ] P1: Учитывать time gap при сборке чанков +- [ ] P1: Маркировать в тексте `quote`, `forward`, автора сообщения и автора цитаты +- [ ] P1: Развести `page_content`, `dense_content`, `sparse_content` +- [ ] P1: Материализовать `sender_id` и `mentions` в индексируемый текст +- [ ] P1: Разбирать `file_snippets` и вытаскивать имя файла, mime и url +- [ ] P1: Разбирать `member_event` и превращать его в индексируемый текст +- [ ] P2: Использовать `entities.people` и `entities.emails` для boost или фильтрации +- [ ] P2: Использовать `entities.documents`, `entities.names`, `entities.links` для lexical boost +- [ ] P2: Использовать `date_range` для фильтрации по `metadata.start` и `metadata.end` +- [ ] P2: Использовать `contains_quote` и `contains_forward` как сигналы ранжирования +- [ ] P2: Добавить multi-query fusion в `Qdrant` +- [ ] P2: Подобрать `prefetch`, `retrieve_k`, `rerank_limit` +- [ ] P3: Привести локальный `docker-compose.yml` к схеме с `API_KEY` +- [ ] P3: Добавить `--platform linux/amd64` в сборку образов +- [ ] P3: Добавить retry и timeout политику для dense/rerank HTTP вызовов +- [ ] P3: Зафиксировать набор локальных тестовых вопросов для проверки регрессий +- [ ] P4: Добавить `razdel` +- [ ] P4: Добавить `pymorphy3` +- [ ] P4: Добавить `rapidfuzz` +- [ ] P4: Добавить `python-dateutil` или `dateparser` +- [ ] P4: Добавить `tenacity` diff --git a/doc/todo_and_pipeline.md b/doc/todo_and_pipeline.md new file mode 100644 index 0000000..2963944 --- /dev/null +++ b/doc/todo_and_pipeline.md @@ -0,0 +1,223 @@ +# To-Do и целевой pipeline + +Дата: 2026-04-18 + +## Цель + +Поднять `Recall@50` и `nDCG@50` без смены стэка: + +- оставить `Qdrant` +- оставить внешний dense endpoint +- оставить внешний reranker +- усиливать только индекс, retrieval, rerank и post-processing + +## Приоритетный to-do + +### P0. Быстрые и самые окупаемые правки + +- [ ] Переключить основной запрос в `search` на `question.search_text` с fallback на `question.text` +- [ ] Подключить `question.variants` как дополнительные query-формулировки +- [ ] Подключить `question.hyde` как дополнительные dense-запросы +- [ ] Подключить `question.keywords` как основу для sparse-запроса +- [ ] Перестать терять кандидатов после rerank: возвращать не только top-10 rerank, но и хвост retrieval +- [ ] Дедуплицировать `message_ids` перед ответом +- [ ] Ограничить финальную выдачу осмысленным `top-50` +- [ ] Агрегировать score по `message_id`, а не просто конкатенировать ids из чанков + +### P1. Улучшение индексации + +- [ ] Перейти с символьного chunking на chunking по сообщениям +- [ ] Учитывать временные разрывы между сообщениями при сборке чанка +- [ ] Не смешивать в одном чанке слишком далекие по смыслу блоки +- [ ] Отдельно маркировать `quote`, `forward`, автора сообщения и автора цитаты +- [ ] Развести `page_content`, `dense_content`, `sparse_content` +- [ ] Материализовать `mentions` в текст и metadata +- [ ] Материализовать `sender_id` в индексируемый текст +- [ ] Разбирать `file_snippets` и вытаскивать имя файла, mime, url +- [ ] Разбирать `member_event` и превращать его в индексируемый текст + +### P2. Улучшение retrieval и фильтрации + +- [ ] Использовать `entities.people` и `entities.emails` для boost или фильтрации по `participants` и `mentions` +- [ ] Использовать `entities.documents`, `entities.names`, `entities.links` для lexical boost +- [ ] Использовать `date_range` для фильтрации по `metadata.start` и `metadata.end` +- [ ] Использовать `contains_quote` и `contains_forward` как дополнительные сигналы ранжирования +- [ ] Добавить multi-query fusion в `Qdrant` для dense и sparse запросов +- [ ] Подобрать новые значения `prefetch`, `retrieve_k`, `rerank_limit` + +### P3. Инфраструктура и надежность + +- [ ] Привести локальный `docker-compose.yml` к схеме с `API_KEY`, чтобы локальный запуск был ближе к ТЗ +- [ ] Добавить `--platform linux/amd64` в сборку образов +- [ ] Добавить retry и timeout политику для dense/rerank HTTP вызовов +- [ ] Зафиксировать набор локальных тестовых запросов для регрессии качества + +### P4. Библиотеки, которые можно добавить без смены стэка + +- [ ] `razdel` для токенизации русского текста +- [ ] `pymorphy3` для лемматизации при подготовке `sparse_content` +- [ ] `rapidfuzz` для точного match по именам, email, документам и ссылкам +- [ ] `python-dateutil` или `dateparser` для нормализации дат +- [ ] `tenacity` для retry вокруг внешних HTTP запросов + +## Целевой pipeline индексации + +### 1. Подготовка сообщения + +На входе каждое сообщение должно раскладываться на сигналы: + +- основной текст сообщения +- `parts[*].text` +- тип части: `text`, `quote`, `forward` +- `sender_id` +- `mentions` +- `file_snippets` +- `member_event` +- `thread_sn` +- флаги `is_system`, `is_quote`, `is_forward` + +### 2. Нормализация и разметка + +Перед chunking сообщение стоит приводить к структурированному виду, например: + +- `author: ...` +- `mentions: ...` +- `quote: ...` +- `forwarded: ...` +- `file: ...` +- `system_event: ...` + +Смысл не в красивом выводе, а в том, чтобы dense и sparse видели роль каждого куска текста. + +### 3. Chunking + +Целевой принцип: + +- базовая единица не символ, а сообщение +- чанк собирается как окно из нескольких соседних сообщений +- окно режется по лимиту размера +- окно закрывается на большом time gap +- `forward` и длинные `quote` не должны ломать соседний контекст +- overlap должен работать по границам сообщений, а не по хвосту строки + +### 4. Формирование трех видов текста + +`page_content`: + +- человекочитаемый текст чанка для payload + +`dense_content`: + +- нормализованный текст с автором, role-маркерами, quote/forward маркерами + +`sparse_content`: + +- keyword-heavy текст +- леммы +- email +- mentions +- имена файлов +- ссылки +- названия документов и сервисов + +### 5. Metadata для Qdrant + +В metadata стоит стабильно сохранять: + +- `message_ids` +- `participants` +- `mentions` +- `thread_sn` +- `start` +- `end` +- `contains_quote` +- `contains_forward` +- `chat_id` +- `chat_type` + +## Целевой pipeline поиска + +### 1. Подготовка query + +Собирать query не из одного поля, а из набора: + +- основной запрос: `search_text` или `text` +- дополнительные dense-query: `variants` и `hyde` +- дополнительные sparse-query: `keywords` +- entity-сигналы: `people`, `emails`, `documents`, `names`, `links` +- time constraints: `date_range`, `date_mentions` + +### 2. Query builder + +Нужно строить несколько представлений запроса: + +- dense-query для смысла +- sparse-query для точных слов и терминов +- filter/boost по metadata + +### 3. Retrieval в Qdrant + +Практическая схема: + +1. Выполнить несколько dense prefetch по разным вариантам запроса +2. Выполнить несколько sparse prefetch по keyword-heavy запросам +3. Добавить filters по датам, mentions, participants, если это явно следует из вопроса +4. Объединить результаты через fusion +5. Забрать расширенный пул кандидатов для rerank + +### 4. Rerank + +Rerank должен работать не на слишком маленьком пуле. Целевой принцип: + +- retrieval дает расширенный пул +- rerank сортирует top-N кандидатов +- хвост retrieval не теряется полностью + +### 5. Агрегация к `message_id` + +После rerank: + +- собрать `message_ids` из чанков +- удалить дубликаты +- агрегировать лучший score на сообщение +- собрать финальный `top-50` + +Это особенно важно, потому что метрики в ТЗ считаются именно по `message_id`, а не по chunk id. + +## Порядок внедрения + +### Этап 1. Quick wins + +- использовать `search_text`, `variants`, `hyde`, `keywords` +- перестать терять кандидатов после rerank +- добавить dedup и top-50 + +### Этап 2. Пересборка индекса + +- новый renderer сообщения +- новый chunking по сообщениям +- разные `page_content`, `dense_content`, `sparse_content` + +### Этап 3. Metadata-aware retrieval + +- filters по дате +- boost по mentions/participants +- учет `contains_quote` и `contains_forward` + +### Этап 4. Тюнинг + +- подобрать размеры чанков +- подобрать `retrieve_k` +- подобрать `rerank_limit` +- прогнать локальный набор контрольных вопросов + +## Минимальный критерий готовности + +Можно считать, что pipeline собран в рабочем виде, если: + +- `search` использует не только `question.text` +- индексация не режет чанки посреди сообщения как основной механизм +- `message_ids` дедуплицируются +- финальная выдача ограничивается top-50 +- retrieval умеет использовать хотя бы часть metadata +- локальная сборка и запуск не расходятся с ТЗ по критичным env и platform