forked from zovos/vk_hackathon
290 lines
14 KiB
Markdown
290 lines
14 KiB
Markdown
# AI Update по ТЗ
|
||
|
||
Дата: 2026-04-18
|
||
|
||
## Что просмотрено
|
||
|
||
- `doc/ТЗ_на_хакатон_Индексация_и_поиск_по_сообщениям.pdf`
|
||
- `README.md`
|
||
- `docker-compose.yml`
|
||
- `index/main.py`, `search/main.py`
|
||
- `index/Dockerfile`, `search/Dockerfile`
|
||
- `index/Makefile`, `search/Makefile`
|
||
- `data/Go Nova.json`
|
||
|
||
По примеру данных:
|
||
|
||
- всего сообщений: `25`
|
||
- с `parts`: `14`
|
||
- с цитатами: `5`
|
||
- с пересланными сообщениями: `2`
|
||
- с `mentions`: `4`
|
||
- с `file_snippets`: `1`
|
||
- системных сообщений: `1`
|
||
|
||
Это важно, потому что в текущем коде часть этих сигналов либо не используется вообще, либо теряет смысл при индексации.
|
||
|
||
## Что уже соответствует ТЗ
|
||
|
||
1. В репозитории есть оба требуемых сервиса: `index` и `search`.
|
||
2. Обязательные endpoints реализованы:
|
||
- `GET /health`, `POST /index`, `POST /sparse_embedding` в `index/main.py`
|
||
- `GET /health`, `POST /search` в `search/main.py`
|
||
3. Контракты request/response по основным endpoint'ам не менялись и в целом совпадают с шаблоном и ТЗ.
|
||
4. `search` использует `Qdrant`, dense endpoint и reranker через HTTP.
|
||
5. В обоих Dockerfile sparse-модель предзагружается внутрь образа, что соответствует оффлайн-ограничению контейнеров.
|
||
6. `HOST` и `PORT` читаются из env, как требует ТЗ.
|
||
|
||
## Что отсутствует или реализовано частично
|
||
|
||
### 1. Обогащенный вопрос из ТЗ почти не используется
|
||
|
||
В `search/main.py:90-100` описаны поля:
|
||
|
||
- `search_text`
|
||
- `variants`
|
||
- `hyde`
|
||
- `keywords`
|
||
- `entities`
|
||
- `date_mentions`
|
||
- `date_range`
|
||
- `asker`
|
||
|
||
Но в реальном поиске используется только `question.text`:
|
||
|
||
- `search/main.py:307-316`
|
||
|
||
Это главный недобор относительно ТЗ. Само ТЗ явно дает эти поля как сигналы для retrieval, а код их сейчас просто игнорирует.
|
||
|
||
### 2. Метаданные чанков объявлены, но не участвуют в поиске
|
||
|
||
В `README.md:52-58` отдельно сказано, что в metadata чанка сохраняются:
|
||
|
||
- `participants`
|
||
- `mentions`
|
||
- `contains_forward`
|
||
- `contains_quote`
|
||
|
||
В `search/main.py:132-145` есть модель `ChunkMetadata`, но дальше она никак не используется в `query_points`. Поиск не делает:
|
||
|
||
- фильтрацию по `mentions`
|
||
- фильтрацию по `participants`
|
||
- учет `thread_sn`
|
||
- учет временного диапазона через `start`/`end`
|
||
- отдельную обработку quote/forward чанков
|
||
|
||
То есть сильный канал улучшения качества уже предусмотрен схемой, но сейчас не задействован.
|
||
|
||
### 3. Реранк отбрасывает часть кандидатов
|
||
|
||
Сейчас:
|
||
|
||
- retrieval берет до `20` чанков: `search/main.py:174-177`
|
||
- rerank берет только первые `10`: `search/main.py:278-296`
|
||
- после rerank возвращаются только эти `10`, а хвост `11-20` теряется: `search/main.py:321-328`
|
||
|
||
Это не нарушение контракта, но это реальная потеря recall.
|
||
|
||
### 4. Выдача не дедуплицируется и не ограничивается по полезному top-K
|
||
|
||
Сейчас `message_ids` просто конкатенируются:
|
||
|
||
- `search/main.py:323-328`
|
||
|
||
Проблемы:
|
||
|
||
- дубликаты message id не удаляются
|
||
- результаты не агрегируются по лучшему score сообщения
|
||
- нет явного ограничения на топ полезных `50`, хотя именно `K=50` участвует в метрике из ТЗ
|
||
|
||
Если один и тот же `message_id` попал в несколько чанков, он тратит место в выдаче.
|
||
|
||
### 5. Индексация пока очень базовая: фиксированные символьные чанки
|
||
|
||
В `index/main.py:118-190` чанки строятся просто по длине строки:
|
||
|
||
- `CHUNK_SIZE = 512`
|
||
- `OVERLAP_SIZE = 256`
|
||
- разбиение идет по символам, а не по сообщениям, тайм-гепам, тредам или смысловым блокам
|
||
|
||
Из-за этого:
|
||
|
||
- длинные пересланные сообщения и цитаты могут резаться в неудобных местах
|
||
- один и тот же смысловой блок может быть разнесен по чанкам неестественно
|
||
- overlap строится по хвосту текста, а не по границе сообщений
|
||
|
||
### 6. `page_content`, `dense_content` и `sparse_content` сейчас одинаковые
|
||
|
||
См. `index/main.py:180-186`.
|
||
|
||
ТЗ прямо оставляет это место как точку оптимизации качества, но пока этот резерв не используется.
|
||
|
||
### 7. Индексация берет только `text` и `parts[*].text`, остальное почти теряется
|
||
|
||
См. `index/main.py:99-115`.
|
||
|
||
Сейчас не используются как поисковые сигналы:
|
||
|
||
- `sender_id`
|
||
- `mentions`
|
||
- `file_snippets`
|
||
- `member_event`
|
||
- `thread_sn`
|
||
- `is_hidden`
|
||
- `is_system`
|
||
- явное различение `quote` и `forward`
|
||
|
||
Особенно важные пробелы:
|
||
|
||
- `member_event` у системных сообщений сейчас фактически пропадает, если обычного текста нет
|
||
- `file_snippets` не разбирается, хотя там могут быть имена файлов, URL и служебные поля
|
||
- запросы вида "кто писал", "кого упоминали", "какой файл/документ кидали" сейчас поддержаны слабо
|
||
|
||
### 8. Смысл `quote` и `forward` не маркируется
|
||
|
||
В `index/main.py:105-113` текст из `parts` просто подшивается в общий текст без явных маркеров вида:
|
||
|
||
- "цитата:"
|
||
- "пересланное сообщение:"
|
||
- "автор цитаты:"
|
||
|
||
В итоге dense/sparse видят просто общий текстовый комок. Для поиска по обсуждениям это ощутимая потеря контекста.
|
||
|
||
### 9. Есть расхождение между локальной инфраструктурой и ТЗ
|
||
|
||
По ТЗ для `search` ожидается `API_KEY`.
|
||
|
||
В коде это поддержано:
|
||
|
||
- `search/main.py:21-30`
|
||
- `search/main.py:42-47`
|
||
- `search/Makefile:10-15`
|
||
|
||
Но локальный `docker-compose.yml:57-60` требует `OPEN_API_LOGIN` и `OPEN_API_PASSWORD`.
|
||
|
||
Итог:
|
||
|
||
- сам сервис гибче ТЗ
|
||
- локальный compose не повторяет боевую схему из ТЗ один в один
|
||
|
||
Это не ломает контракт, но может запутать при локальной отладке.
|
||
|
||
### 10. Есть еще одна инфраструктурная несостыковка со сдачей
|
||
|
||
В `doc/upload_to_docker.md:45-48` явно сказано собирать образы с `--platform linux/amd64`.
|
||
|
||
Но `index/Makefile:16-18` и `search/Makefile:26-28` собирают без `--platform linux/amd64`.
|
||
|
||
На x86 это может пройти незаметно, а на ARM-машине дать неправильный образ для отправки.
|
||
|
||
### 11. Есть неоднозначность между README и PDF по sparse в `search`
|
||
|
||
- `README.md:211` говорит, что sparse-модель для `search` должна быть локально внутри образа
|
||
- PDF в формулировке требований к `Search Service` делает акцент, что обращения к dense/sparse/rerank идут через проверяющую систему
|
||
|
||
Текущий код следует логике README/example: sparse считается локально в `search/main.py:148-151` и `search/main.py:198-207`.
|
||
|
||
Я бы это не считал блокером, но как минимум это место стоит держать в голове как неоднозначное требование.
|
||
|
||
## Что улучшать в первую очередь
|
||
|
||
### Приоритет 1. Начать использовать все поля `question`
|
||
|
||
Минимально стоит задействовать:
|
||
|
||
- `search_text` как основной нормализованный запрос
|
||
- `variants` как дополнительные формулировки
|
||
- `hyde` как дополнительные dense-запросы
|
||
- `keywords` как основу для sparse
|
||
- `entities` для фильтров и lexical boost
|
||
- `date_range` и `date_mentions` для ограничения по времени
|
||
- `asker` как сигнал по людям и email
|
||
|
||
Самый логичный путь без смены стэка: несколько dense/sparse запросов + fusion в `Qdrant`.
|
||
|
||
### Приоритет 2. Перестроить chunking под структуру чата, а не под символы
|
||
|
||
Нужны чанки по:
|
||
|
||
- окнам сообщений
|
||
- временным разрывам
|
||
- границам thread/forward/quote
|
||
- ограничению на размер по сообщениям, а не только по символам
|
||
|
||
Для чатов это обычно дает больше пользы, чем любые косметические тюнинги rerank.
|
||
|
||
### Приоритет 3. Развести `page_content`, `dense_content`, `sparse_content`
|
||
|
||
Хорошая схема:
|
||
|
||
- `page_content`: читабельный исходный текст чанка
|
||
- `dense_content`: нормализованный текст с ролями, автором, маркерами quote/forward
|
||
- `sparse_content`: keyword-heavy версия с email, mentions, именами файлов, ссылками, документами, леммами
|
||
|
||
Сейчас эта возможность не используется вообще.
|
||
|
||
### Приоритет 4. Нормально собирать финальную выдачу
|
||
|
||
Нужно:
|
||
|
||
- не терять кандидатов после rerank
|
||
- удалять дубликаты `message_id`
|
||
- агрегировать по лучшему score сообщения или чанка
|
||
- отдавать осмысленный top-50
|
||
|
||
Это прямой выигрыш по Recall@50 и nDCG@50.
|
||
|
||
### Приоритет 5. Начать использовать metadata в `Qdrant`
|
||
|
||
Особенно полезно для:
|
||
|
||
- `mentions`
|
||
- `participants`
|
||
- `contains_quote`
|
||
- `contains_forward`
|
||
- `thread_sn`
|
||
- `start` / `end`
|
||
|
||
Для многих вопросов это позволит не просто "лучше ранжировать", а сразу отрезать нерелевантный шум.
|
||
|
||
### Приоритет 6. Превратить скрытые сигналы в индексируемый текст
|
||
|
||
Стоит отдельно материализовать:
|
||
|
||
- `member_event` в текст вида "пользователь X добавил Y"
|
||
- `file_snippets` в текст вида "файл: NAME, url: ..."
|
||
- автора сообщения
|
||
- список упомянутых пользователей
|
||
|
||
Сейчас эти сигналы либо не попадают в индекс, либо попадают слишком слабо.
|
||
|
||
## Что можно добавить по Python-библиотекам, не меняя стек
|
||
|
||
Стек `Qdrant` менять не нужно. Самые полезные добавки я бы смотрел такие:
|
||
|
||
- `pymorphy3` для лемматизации русских слов при подготовке `sparse_content`
|
||
- `razdel` для аккуратной токенизации русского текста
|
||
- `rapidfuzz` для точного lexical match по именам, email, документам, ссылкам и названиям
|
||
- `python-dateutil` или `dateparser` для нормализации дат, если захотите усиливать работу с `date_mentions`
|
||
- `tenacity` для аккуратных retry/timeout-оберток вокруг dense/rerank HTTP вызовов
|
||
|
||
Что не нужно делать:
|
||
|
||
- менять `Qdrant`
|
||
- тащить внешние LLM/API
|
||
- усложнять архитектуру ради "модности", пока не использованы базовые сигналы из самого ТЗ
|
||
|
||
## Короткий вывод
|
||
|
||
Сейчас репозиторий соответствует ТЗ как рабочий базовый шаблон, но почти не использует те сигналы, ради которых это ТЗ вообще интересно:
|
||
|
||
- обогащение вопроса
|
||
- metadata чанков
|
||
- структуру chat messages
|
||
- сигналы автора, упоминаний, файлов, системных событий, цитат и пересылок
|
||
|
||
Самый большой потенциал улучшения здесь не в замене базы или модели, а в трех вещах:
|
||
|
||
1. умный chunking
|
||
2. multi-query hybrid retrieval
|
||
3. использование metadata и нормальной сборки финального top-50
|