vk_hackathon/doc/todo_and_pipeline.md
2026-04-18 11:19:27 +03:00

223 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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