add todo and pipeline

This commit is contained in:
q 2026-04-18 11:19:27 +03:00
parent 33f8880807
commit 20b401cf01
3 changed files with 255 additions and 0 deletions

0
.codex Normal file
View file

32
doc/todo.md Normal file
View file

@ -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`

223
doc/todo_and_pipeline.md Normal file
View file

@ -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