# 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