223 lines
9.6 KiB
Markdown
223 lines
9.6 KiB
Markdown
# 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
|